Back to the Future 策略
该动量策略比较当前收盘价与若干分钟前的价格。当价格相对于历史价格上升超过设定阈值时,系统开多单;当价格低于负阈值时,系统开空单。理念是价格从过去水平大幅偏离通常意味着趋势正在形成。
策略仅在完成的K线基础上运行,可用于 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()