Color Zero Lag MA Strategy
This strategy uses a zero lag moving average (ZLMA) to detect trend reversals. It opens long positions when the ZLMA turns upward and opens short positions when the ZLMA turns downward. Existing positions are closed when the indicator slope reverses.
Parameters
- Length: Period of the zero lag moving average.
- Candle Type: Timeframe for candles used by the strategy.
- Open Buy: Enable opening long positions.
- Open Sell: Enable opening short positions.
- Close Buy: Close long positions when ZLMA turns down.
- Close Sell: Close short positions when ZLMA turns up.
Logic
- Subscribe to candles of the selected timeframe.
- Calculate the zero lag moving average.
- Track the last two ZLMA values to determine slope direction.
- If the slope changes from down to up, close short positions and open a long position.
- If the slope changes from up to down, close long positions and open a short position.
This simple approach follows the color change of the zero lag moving average to capture potential trend reversals.
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trades ZLEMA slope reversals with independent long/short entry/exit switches.
/// </summary>
public class ColorZeroLagMaStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<bool> _openBuy;
private readonly StrategyParam<bool> _openSell;
private readonly StrategyParam<bool> _closeBuy;
private readonly StrategyParam<bool> _closeSell;
private decimal? _previous;
private decimal? _beforePrevious;
private Order _pendingOrder;
public int Length { get => _length.Value; set => _length.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public bool OpenBuy { get => _openBuy.Value; set => _openBuy.Value = value; }
public bool OpenSell { get => _openSell.Value; set => _openSell.Value = value; }
public bool CloseBuy { get => _closeBuy.Value; set => _closeBuy.Value = value; }
public bool CloseSell { get => _closeSell.Value; set => _closeSell.Value = value; }
public ColorZeroLagMaStrategy()
{
_length = Param(nameof(Length), 12).SetGreaterThanZero()
.SetDisplay("Length", "ZLEMA length", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_openBuy = Param(nameof(OpenBuy), true).SetDisplay("Open Buy", "Allow long entries", "Trading");
_openSell = Param(nameof(OpenSell), true).SetDisplay("Open Sell", "Allow short entries", "Trading");
_closeBuy = Param(nameof(CloseBuy), true).SetDisplay("Close Buy", "Allow long exits", "Trading");
_closeSell = Param(nameof(CloseSell), true).SetDisplay("Close Sell", "Allow short exits", "Trading");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_previous = _beforePrevious = null;
_pendingOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_previous = _beforePrevious = null;
_pendingOrder = null;
var zlma = new ZeroLagExponentialMovingAverage { Length = Length };
SubscribeCandles(CandleType).Bind(zlma, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal current)
{
if (candle.State != CandleStates.Finished)
return;
var turnUp = _beforePrevious is decimal older && _previous is decimal previous
&& previous < older && current > previous;
var turnDown = _beforePrevious is decimal olderDown && _previous is decimal previousDown
&& previousDown > olderDown && current < previousDown;
_beforePrevious = _previous;
_previous = current;
if (!IsFormedAndOnlineAndAllowTrading()
|| (_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed)))
return;
if (turnUp)
{
if (Position < 0m && CloseSell)
_pendingOrder = BuyMarket(-Position + (OpenBuy ? Volume : 0m));
else if (Position == 0m && OpenBuy)
_pendingOrder = BuyMarket(Volume);
}
else if (turnDown)
{
if (Position > 0m && CloseBuy)
_pendingOrder = SellMarket(Position + (OpenSell ? Volume : 0m));
else if (Position == 0m && OpenSell)
_pendingOrder = SellMarket(Volume);
}
}
}
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, OrderStates
from StockSharp.Algo.Indicators import ZeroLagExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class color_zero_lag_ma_strategy(Strategy):
"""ZLEMA slope reversals with independent entry and exit permissions."""
def __init__(self):
super(color_zero_lag_ma_strategy, self).__init__()
self._length = self.Param("Length", 12).SetDisplay("Length", "ZLEMA length", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._open_buy = self.Param("OpenBuy", True).SetDisplay("Open Buy", "Allow long entries", "Trading")
self._open_sell = self.Param("OpenSell", True).SetDisplay("Open Sell", "Allow short entries", "Trading")
self._close_buy = self.Param("CloseBuy", True).SetDisplay("Close Buy", "Allow long exits", "Trading")
self._close_sell = self.Param("CloseSell", True).SetDisplay("Close Sell", "Allow short exits", "Trading")
self._previous = None
self._before_previous = None
self._pending_order = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(color_zero_lag_ma_strategy, self).OnReseted()
self._previous = self._before_previous = None
self._pending_order = None
def OnStarted2(self, time):
super(color_zero_lag_ma_strategy, self).OnStarted2(time)
self._previous = self._before_previous = None
self._pending_order = None
zlma = ZeroLagExponentialMovingAverage()
zlma.Length = self._length.Value
self.SubscribeCandles(self.CandleType).Bind(zlma, self.on_process).Start()
def on_process(self, candle, value):
if candle.State != CandleStates.Finished:
return
current = float(value)
up = self._before_previous is not None and self._previous < self._before_previous and current > self._previous
down = self._before_previous is not None and self._previous > self._before_previous and current < self._previous
self._before_previous, self._previous = self._previous, current
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
if up:
if self.Position < 0 and self._close_sell.Value:
amount = -self.Position + (self.Volume if self._open_buy.Value else 0)
self._pending_order = self.BuyMarket(amount)
elif self.Position == 0 and self._open_buy.Value:
self._pending_order = self.BuyMarket(self.Volume)
elif down:
if self.Position > 0 and self._close_buy.Value:
amount = self.Position + (self.Volume if self._open_sell.Value else 0)
self._pending_order = self.SellMarket(amount)
elif self.Position == 0 and self._open_sell.Value:
self._pending_order = self.SellMarket(self.Volume)
def CreateClone(self):
return color_zero_lag_ma_strategy()