Color Zerolag Momentum X2 Strategy
Dual timeframe momentum strategy using a zero lag moving average cross. The higher timeframe defines trend direction, while the lower timeframe triggers entries when momentum crosses its zero lag average in the direction of the trend.
Details
- Entry Criteria: momentum crosses its zero lag average in trend direction
- Long/Short: Both
- Exit Criteria: opposite cross or trend reversal
- Stops: No
- Default Values:
TrendCandleType= 6hTrendMomentumPeriod= 34TrendMaLength= 15SignalCandleType= 30mSignalMomentumPeriod= 34SignalMaLength= 15BuyPosOpen= trueSellPosOpen= trueBuyPosClose= trueSellPosClose= true
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Momentum, ZeroLagEMA
- Stops: No
- Complexity: Intermediate
- Timeframe: Multi-timeframe
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
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>
/// Momentum cross strategy on two timeframes using zero lag moving average.
/// </summary>
public class ColorZerolagMomentumX2Strategy : Strategy
{
private readonly StrategyParam<DataType> _trendCandleType;
private readonly StrategyParam<int> _trendMomentumPeriod;
private readonly StrategyParam<int> _trendMaLength;
private readonly StrategyParam<DataType> _signalCandleType;
private readonly StrategyParam<int> _signalMomentumPeriod;
private readonly StrategyParam<int> _signalMaLength;
private readonly StrategyParam<bool> _buyPosOpen;
private readonly StrategyParam<bool> _sellPosOpen;
private readonly StrategyParam<bool> _buyPosClose;
private readonly StrategyParam<bool> _sellPosClose;
private int _trend;
private decimal? _prevSignalMomentum;
private decimal? _prevSignalMa;
/// <summary>
/// Candle type for trend detection.
/// </summary>
public DataType TrendCandleType
{
get => _trendCandleType.Value;
set => _trendCandleType.Value = value;
}
/// <summary>
/// Momentum period on trend timeframe.
/// </summary>
public int TrendMomentumPeriod
{
get => _trendMomentumPeriod.Value;
set => _trendMomentumPeriod.Value = value;
}
/// <summary>
/// Smoothing length on trend timeframe.
/// </summary>
public int TrendMaLength
{
get => _trendMaLength.Value;
set => _trendMaLength.Value = value;
}
/// <summary>
/// Candle type for signals.
/// </summary>
public DataType SignalCandleType
{
get => _signalCandleType.Value;
set => _signalCandleType.Value = value;
}
/// <summary>
/// Momentum period on signal timeframe.
/// </summary>
public int SignalMomentumPeriod
{
get => _signalMomentumPeriod.Value;
set => _signalMomentumPeriod.Value = value;
}
/// <summary>
/// Smoothing length on signal timeframe.
/// </summary>
public int SignalMaLength
{
get => _signalMaLength.Value;
set => _signalMaLength.Value = value;
}
/// <summary>
/// Allow long entries.
/// </summary>
public bool BuyPosOpen
{
get => _buyPosOpen.Value;
set => _buyPosOpen.Value = value;
}
/// <summary>
/// Allow short entries.
/// </summary>
public bool SellPosOpen
{
get => _sellPosOpen.Value;
set => _sellPosOpen.Value = value;
}
/// <summary>
/// Close long positions on opposite signal.
/// </summary>
public bool BuyPosClose
{
get => _buyPosClose.Value;
set => _buyPosClose.Value = value;
}
/// <summary>
/// Close short positions on opposite signal.
/// </summary>
public bool SellPosClose
{
get => _sellPosClose.Value;
set => _sellPosClose.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="ColorZerolagMomentumX2Strategy"/>.
/// </summary>
public ColorZerolagMomentumX2Strategy()
{
_trendCandleType = Param(nameof(TrendCandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Trend Timeframe", "Candle type for trend", "General");
_trendMomentumPeriod = Param(nameof(TrendMomentumPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Trend Momentum Period", "Momentum length for trend", "Parameters")
;
_trendMaLength = Param(nameof(TrendMaLength), 5)
.SetGreaterThanZero()
.SetDisplay("Trend Smooth Length", "Zero lag MA length for trend", "Parameters")
;
_signalCandleType = Param(nameof(SignalCandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Signal Timeframe", "Candle type for signals", "General");
_signalMomentumPeriod = Param(nameof(SignalMomentumPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Signal Momentum Period", "Momentum length for signals", "Parameters")
;
_signalMaLength = Param(nameof(SignalMaLength), 8)
.SetGreaterThanZero()
.SetDisplay("Signal Smooth Length", "Zero lag MA length for signals", "Parameters")
;
_buyPosOpen = Param(nameof(BuyPosOpen), true)
.SetDisplay("Buy Entries", "Enable long entries", "Signals");
_sellPosOpen = Param(nameof(SellPosOpen), true)
.SetDisplay("Sell Entries", "Enable short entries", "Signals");
_buyPosClose = Param(nameof(BuyPosClose), true)
.SetDisplay("Buy Exits", "Close longs on opposite signal", "Signals");
_sellPosClose = Param(nameof(SellPosClose), true)
.SetDisplay("Sell Exits", "Close shorts on opposite signal", "Signals");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, TrendCandleType), (Security, SignalCandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_trend = 0;
_prevSignalMomentum = default;
_prevSignalMa = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent));
var trendFast = new ExponentialMovingAverage { Length = TrendMaLength };
var trendSlow = new ExponentialMovingAverage { Length = TrendMomentumPeriod };
var trendSub = SubscribeCandles(TrendCandleType);
trendSub.Bind(trendFast, trendSlow, ProcessTrend).Start();
var signalFast = new ExponentialMovingAverage { Length = SignalMaLength };
var signalSlow = new ExponentialMovingAverage { Length = SignalMomentumPeriod };
var signalSub = SubscribeCandles(SignalCandleType);
signalSub.Bind(signalFast, signalSlow, ProcessSignal).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, signalSub);
DrawIndicator(area, signalFast);
DrawIndicator(area, signalSlow);
DrawOwnTrades(area);
}
}
private void ProcessTrend(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
_trend = fast > slow ? 1 : -1;
}
private void ProcessSignal(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevSignalMomentum is null || _prevSignalMa is null)
{
_prevSignalMomentum = fast;
_prevSignalMa = slow;
return;
}
var buyOpen = BuyPosOpen && _prevSignalMomentum <= _prevSignalMa && fast > slow;
var sellOpen = SellPosOpen && _prevSignalMomentum >= _prevSignalMa && fast < slow;
if (buyOpen && Position == 0)
BuyMarket();
else if (sellOpen && Position == 0)
SellMarket();
_prevSignalMomentum = fast;
_prevSignalMa = slow;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class color_zerolag_momentum_x2_strategy(Strategy):
def __init__(self):
super(color_zerolag_momentum_x2_strategy, self).__init__()
self._trend_candle_type = self.Param("TrendCandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._trend_momentum_period = self.Param("TrendMomentumPeriod", 14)
self._trend_ma_length = self.Param("TrendMaLength", 5)
self._signal_candle_type = self.Param("SignalCandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._signal_momentum_period = self.Param("SignalMomentumPeriod", 20)
self._signal_ma_length = self.Param("SignalMaLength", 8)
self._buy_pos_open = self.Param("BuyPosOpen", True)
self._sell_pos_open = self.Param("SellPosOpen", True)
self._buy_pos_close = self.Param("BuyPosClose", True)
self._sell_pos_close = self.Param("SellPosClose", True)
self._trend = 0
self._prev_signal_momentum = None
self._prev_signal_ma = None
@property
def TrendCandleType(self):
return self._trend_candle_type.Value
@TrendCandleType.setter
def TrendCandleType(self, value):
self._trend_candle_type.Value = value
@property
def TrendMomentumPeriod(self):
return self._trend_momentum_period.Value
@TrendMomentumPeriod.setter
def TrendMomentumPeriod(self, value):
self._trend_momentum_period.Value = value
@property
def TrendMaLength(self):
return self._trend_ma_length.Value
@TrendMaLength.setter
def TrendMaLength(self, value):
self._trend_ma_length.Value = value
@property
def SignalCandleType(self):
return self._signal_candle_type.Value
@SignalCandleType.setter
def SignalCandleType(self, value):
self._signal_candle_type.Value = value
@property
def SignalMomentumPeriod(self):
return self._signal_momentum_period.Value
@SignalMomentumPeriod.setter
def SignalMomentumPeriod(self, value):
self._signal_momentum_period.Value = value
@property
def SignalMaLength(self):
return self._signal_ma_length.Value
@SignalMaLength.setter
def SignalMaLength(self, value):
self._signal_ma_length.Value = value
@property
def BuyPosOpen(self):
return self._buy_pos_open.Value
@BuyPosOpen.setter
def BuyPosOpen(self, value):
self._buy_pos_open.Value = value
@property
def SellPosOpen(self):
return self._sell_pos_open.Value
@SellPosOpen.setter
def SellPosOpen(self, value):
self._sell_pos_open.Value = value
@property
def BuyPosClose(self):
return self._buy_pos_close.Value
@BuyPosClose.setter
def BuyPosClose(self, value):
self._buy_pos_close.Value = value
@property
def SellPosClose(self):
return self._sell_pos_close.Value
@SellPosClose.setter
def SellPosClose(self, value):
self._sell_pos_close.Value = value
def OnStarted2(self, time):
super(color_zerolag_momentum_x2_strategy, self).OnStarted2(time)
self._trend = 0
self._prev_signal_momentum = None
self._prev_signal_ma = None
self.StartProtection(
Unit(2.0, UnitTypes.Percent),
Unit(1.0, UnitTypes.Percent))
trend_fast = ExponentialMovingAverage()
trend_fast.Length = self.TrendMaLength
trend_slow = ExponentialMovingAverage()
trend_slow.Length = self.TrendMomentumPeriod
trend_sub = self.SubscribeCandles(self.TrendCandleType)
trend_sub.Bind(trend_fast, trend_slow, self.ProcessTrend).Start()
signal_fast = ExponentialMovingAverage()
signal_fast.Length = self.SignalMaLength
signal_slow = ExponentialMovingAverage()
signal_slow.Length = self.SignalMomentumPeriod
signal_sub = self.SubscribeCandles(self.SignalCandleType)
signal_sub.Bind(signal_fast, signal_slow, self.ProcessSignal).Start()
def ProcessTrend(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
f = float(fast)
s = float(slow)
self._trend = 1 if f > s else -1
def ProcessSignal(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
f = float(fast)
s = float(slow)
if self._prev_signal_momentum is None or self._prev_signal_ma is None:
self._prev_signal_momentum = f
self._prev_signal_ma = s
return
buy_open = self.BuyPosOpen and self._prev_signal_momentum <= self._prev_signal_ma and f > s
sell_open = self.SellPosOpen and self._prev_signal_momentum >= self._prev_signal_ma and f < s
if buy_open and self.Position == 0:
self.BuyMarket()
elif sell_open and self.Position == 0:
self.SellMarket()
self._prev_signal_momentum = f
self._prev_signal_ma = s
def OnReseted(self):
super(color_zerolag_momentum_x2_strategy, self).OnReseted()
self._trend = 0
self._prev_signal_momentum = None
self._prev_signal_ma = None
def CreateClone(self):
return color_zerolag_momentum_x2_strategy()