Стратегия Back to the Future
Эта стратегия импульса сравнивает текущую цену закрытия с ценой, которая была несколько минут назад. Если цена выросла относительно исторической более чем на заданный порог, открывается длинная позиция. Если цена упала ниже отрицательного порога, открывается короткая позиция. Предполагается, что сильное отклонение от прошлой цены сигнализирует о зарождении тренда.
Стратегия работает только по завершённым свечам и подходит для любого инструмента и таймфрейма, поддерживаемых StockSharp. После входа риск контролируется фиксированными уровнями тейк‑профита и стоп‑лосса. Очередь прошлых цен поддерживает скользящую историю для оценки разницы.
Детали
- Критерии входа:
- Лонг:
Close(t) - Close(t-Δ) > BarSize. - Шорт:
Close(t) - Close(t-Δ) < -BarSize.
- Лонг:
- Длинные/короткие: обе стороны.
- Критерии выхода:
- Лонг:
Close >= Entry + TakeProfitилиClose <= Entry - StopLoss. - Шорт:
Close <= Entry - TakeProfitилиClose >= Entry + StopLoss.
- Лонг:
- Стопы: да, фиксированный тейк‑профит и стоп‑лосс в ценовых единицах.
- Значения по умолчанию:
BarSize = 0.25HistoryMinutes = 60TakeProfit = 10StopLoss = 5000
- Фильтры:
- Категория: Следование тренду
- Направление: Оба
- Индикаторы: Нет
- Стопы: Да
- Сложность: Простая
- Таймфрейм: Краткосрочный
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Compares current price with the price from a past moment and trades on large deviations.
/// </summary>
public class BackToTheFutureStrategy : Strategy
{
private readonly StrategyParam<decimal> _barSize;
private readonly StrategyParam<int> _historyMinutes;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<(DateTimeOffset time, decimal price)> _history = new();
private decimal _entryPrice;
private int _barsSinceTrade;
/// <summary>
/// Price difference threshold.
/// </summary>
public decimal BarSize
{
get => _barSize.Value;
set => _barSize.Value = value;
}
/// <summary>
/// Minutes to look back for comparison.
/// </summary>
public int HistoryMinutes
{
get => _historyMinutes.Value;
set => _historyMinutes.Value = value;
}
/// <summary>
/// Take profit distance in price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Stop loss distance in price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Bars to wait after a completed trade.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Type of candles to process.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="BackToTheFutureStrategy"/>.
/// </summary>
public BackToTheFutureStrategy()
{
_barSize = Param(nameof(BarSize), 1500m)
.SetGreaterThanZero()
.SetDisplay("Price Difference", "Threshold to trigger trades", "General")
;
_historyMinutes = Param(nameof(HistoryMinutes), 240)
.SetGreaterThanZero()
.SetDisplay("History Minutes", "Minutes back for price comparison", "General")
;
_takeProfit = Param(nameof(TakeProfit), 1500m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Distance from entry", "Risk")
;
_stopLoss = Param(nameof(StopLoss), 2000m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Distance from entry", "Risk")
;
_cooldownBars = Param(nameof(CooldownBars), 2)
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_history.Clear();
_entryPrice = 0m;
_barsSinceTrade = CooldownBars;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_history.Enqueue((candle.CloseTime, candle.ClosePrice));
var minTime = candle.CloseTime - TimeSpan.FromMinutes(HistoryMinutes);
while (_history.Count > 0 && _history.Peek().time < minTime)
_history.Dequeue();
if (_history.Count == 0)
return;
if (_barsSinceTrade < CooldownBars)
_barsSinceTrade++;
var oldest = _history.Peek().price;
var diff = candle.ClosePrice - oldest;
if (Position > 0)
{
if (candle.ClosePrice >= _entryPrice + TakeProfit || candle.ClosePrice <= _entryPrice - StopLoss)
{
SellMarket(Position);
_barsSinceTrade = 0;
}
}
else if (Position < 0)
{
if (candle.ClosePrice <= _entryPrice - TakeProfit || candle.ClosePrice >= _entryPrice + StopLoss)
{
BuyMarket(-Position);
_barsSinceTrade = 0;
}
}
else if (_barsSinceTrade >= CooldownBars)
{
if (diff > BarSize)
{
var volume = Volume + (Position < 0 ? -Position : 0m);
BuyMarket(volume);
_entryPrice = candle.ClosePrice;
_barsSinceTrade = 0;
}
else if (diff < -BarSize)
{
var volume = Volume + (Position > 0 ? Position : 0m);
SellMarket(volume);
_entryPrice = candle.ClosePrice;
_barsSinceTrade = 0;
}
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from System.Collections.Generic import Queue
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class back_to_the_future_strategy(Strategy):
def __init__(self):
super(back_to_the_future_strategy, self).__init__()
self._bar_size = self.Param("BarSize", 1500.0) \
.SetDisplay("Price Difference", "Threshold to trigger trades", "General")
self._history_minutes = self.Param("HistoryMinutes", 240) \
.SetDisplay("History Minutes", "Minutes back for price comparison", "General")
self._take_profit = self.Param("TakeProfit", 1500.0) \
.SetDisplay("Take Profit", "Distance from entry", "Risk")
self._stop_loss = self.Param("StopLoss", 2000.0) \
.SetDisplay("Stop Loss", "Distance from entry", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 2) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._history = []
self._entry_price = 0.0
self._bars_since_trade = 0
@property
def BarSize(self):
return self._bar_size.Value
@BarSize.setter
def BarSize(self, value):
self._bar_size.Value = value
@property
def HistoryMinutes(self):
return self._history_minutes.Value
@HistoryMinutes.setter
def HistoryMinutes(self, value):
self._history_minutes.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(back_to_the_future_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
close_price = float(candle.ClosePrice)
close_time = candle.CloseTime
self._history.append((close_time, close_price))
min_time = close_time - TimeSpan.FromMinutes(self.HistoryMinutes)
while len(self._history) > 0 and self._history[0][0] < min_time:
self._history.pop(0)
if len(self._history) == 0:
return
if self._bars_since_trade < self.CooldownBars:
self._bars_since_trade += 1
oldest = self._history[0][1]
diff = close_price - oldest
pos = self.Position
tp = float(self.TakeProfit)
sl = float(self.StopLoss)
bar_size = float(self.BarSize)
if pos > 0:
if close_price >= self._entry_price + tp or close_price <= self._entry_price - sl:
self.SellMarket(pos)
self._bars_since_trade = 0
elif pos < 0:
if close_price <= self._entry_price - tp or close_price >= self._entry_price + sl:
self.BuyMarket(-pos)
self._bars_since_trade = 0
elif self._bars_since_trade >= self.CooldownBars:
if diff > bar_size:
vol = float(self.Volume) + (float(-pos) if pos < 0 else 0.0)
self.BuyMarket(vol)
self._entry_price = close_price
self._bars_since_trade = 0
elif diff < -bar_size:
vol = float(self.Volume) + (float(pos) if pos > 0 else 0.0)
self.SellMarket(vol)
self._entry_price = close_price
self._bars_since_trade = 0
def OnReseted(self):
super(back_to_the_future_strategy, self).OnReseted()
self._history = []
self._entry_price = 0.0
self._bars_since_trade = self.CooldownBars
def CreateClone(self):
return back_to_the_future_strategy()