ADX Stop Order Template Strategy
This strategy demonstrates how to place pending stop orders using the Average Directional Index (ADX) and its Directional Movement components. It recreates the core logic of a classic MQL template: when the market shows a strong trend and the +DI and -DI lines cross, the system places a buy stop or sell stop at a fixed distance. Protective stop-loss and take-profit levels are managed automatically.
The example is intentionally simple and focuses on order handling. Traders can extend it with additional filters or money management rules to build more advanced systems.
Details
- Entry Criteria:
- ADX value above the
ADX Thresholdparameter. - Long:
+DIgreater than-DIand two candles ago+DIwas below-DI. - Short:
+DIless than-DIand two candles ago+DIwas above-DI. - Current spread must be below the
Max Spreadparameter.
- ADX value above the
- Order Placement:
- Pending stop orders are placed
Pipsprice steps away from the current bid or ask. - Only one pending order is active at a time; older orders are cancelled when a new signal appears.
- Pending stop orders are placed
- Exit Criteria:
- Long positions are closed when
-DIrises above+DI. - Short positions are closed when
+DIrises above-DI.
- Long positions are closed when
- Stops:
- Stop-loss and take-profit are applied via
StartProtectionusingStop LossandTake Profitparameters.
- Stop-loss and take-profit are applied via
- Default Values:
ADX Period= 14ADX Threshold= 5Pips= 10 price stepsTake Profit= 1000 price stepsStop Loss= 500 price stepsMax Spread= 20 price stepsCandle Type= 15-minute candles
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: ADX, DMI
- Stops: Yes
- Complexity: Medium
- Timeframe: Intraday
- Spread filter: Yes
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>
/// Strategy based on ADX and DMI crossovers.
/// </summary>
public class AdxStopOrderTemplateStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxSignal;
private readonly StrategyParam<int> _pips;
private readonly StrategyParam<int> _takeProfit;
private readonly StrategyParam<int> _stopLoss;
private readonly StrategyParam<decimal> _maxSpread;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _minDiSpread;
private readonly StrategyParam<int> _cooldownBars;
private decimal? _prevPlus;
private decimal? _prevMinus;
private int _cooldownRemaining;
/// <summary>
/// Initializes a new instance of the <see cref="AdxStopOrderTemplateStrategy"/> class.
/// </summary>
public AdxStopOrderTemplateStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Calculation period for ADX and DMI.", "Indicators");
_adxSignal = Param(nameof(AdxSignal), 20m)
.SetGreaterThanZero()
.SetDisplay("ADX Threshold", "Minimum ADX value to allow entries.", "Indicators");
_pips = Param(nameof(Pips), 10)
.SetGreaterThanZero()
.SetDisplay("Pending Offset", "Distance in price steps for stop orders.", "Orders");
_takeProfit = Param(nameof(TakeProfit), 1000)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take profit size in price steps.", "Risk");
_stopLoss = Param(nameof(StopLoss), 500)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Stop loss size in price steps.", "Risk");
_maxSpread = Param(nameof(MaxSpread), 20m)
.SetGreaterThanZero()
.SetDisplay("Max Spread", "Maximum allowed spread in price steps.", "Orders");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for analysis.", "General");
_minDiSpread = Param(nameof(MinDiSpread), 5m)
.SetDisplay("DI Spread", "Minimum spread between DI+ and DI-.", "Filters");
_cooldownBars = Param(nameof(CooldownBars), 6)
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change.", "Trading");
}
#region Parameters
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
public decimal AdxSignal
{
get => _adxSignal.Value;
set => _adxSignal.Value = value;
}
public int Pips
{
get => _pips.Value;
set => _pips.Value = value;
}
public int TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
public int StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
public decimal MaxSpread
{
get => _maxSpread.Value;
set => _maxSpread.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal MinDiSpread
{
get => _minDiSpread.Value;
set => _minDiSpread.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
#endregion
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevPlus = null;
_prevMinus = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var dmi = new DirectionalIndex { Length = AdxPeriod };
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(dmi, adx, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, adx);
DrawIndicator(area, dmi);
DrawOwnTrades(area);
}
var step = Security.PriceStep ?? 1m;
StartProtection(
new Unit(TakeProfit * step, UnitTypes.Absolute),
new Unit(StopLoss * step, UnitTypes.Absolute));
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue dmiValue, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished || !dmiValue.IsFinal || !adxValue.IsFinal)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var dmiTyped = (DirectionalIndexValue)dmiValue;
if (dmiTyped.Plus is not decimal diPlus || dmiTyped.Minus is not decimal diMinus)
return;
var adxTyped = (AverageDirectionalIndexValue)adxValue;
if (adxTyped.MovingAverage is not decimal adx)
return;
if (_prevPlus is not decimal prevPlus || _prevMinus is not decimal prevMinus)
{
_prevPlus = diPlus;
_prevMinus = diMinus;
return;
}
var crossUp = prevPlus <= prevMinus && diPlus > diMinus;
var crossDown = prevPlus >= prevMinus && diPlus < diMinus;
var diSpread = Math.Abs(diPlus - diMinus);
if (_cooldownRemaining == 0 && adx >= AdxSignal && diSpread >= MinDiSpread)
{
if (crossUp && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownBars;
}
else if (crossDown && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
else if (Position > 0 && crossDown)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
else if (Position < 0 && crossUp)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
_prevPlus = diPlus;
_prevMinus = diMinus;
}
}
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 StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import DirectionalIndex, AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
class adx_stop_order_template_strategy(Strategy):
def __init__(self):
super(adx_stop_order_template_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14) \
.SetDisplay("ADX Period", "Calculation period for ADX and DMI.", "Indicators")
self._adx_signal = self.Param("AdxSignal", 20.0) \
.SetDisplay("ADX Threshold", "Minimum ADX value to allow entries.", "Indicators")
self._pips = self.Param("Pips", 10) \
.SetDisplay("Pending Offset", "Distance in price steps for stop orders.", "Orders")
self._take_profit = self.Param("TakeProfit", 1000) \
.SetDisplay("Take Profit", "Take profit size in price steps.", "Risk")
self._stop_loss = self.Param("StopLoss", 500) \
.SetDisplay("Stop Loss", "Stop loss size in price steps.", "Risk")
self._max_spread = self.Param("MaxSpread", 20.0) \
.SetDisplay("Max Spread", "Maximum allowed spread in price steps.", "Orders")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame for analysis.", "General")
self._min_di_spread = self.Param("MinDiSpread", 5.0) \
.SetDisplay("DI Spread", "Minimum spread between DI+ and DI-.", "Filters")
self._cooldown_bars = self.Param("CooldownBars", 6) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change.", "Trading")
self._prev_plus = None
self._prev_minus = None
self._cooldown_remaining = 0
@property
def adx_period(self):
return self._adx_period.Value
@property
def adx_signal(self):
return self._adx_signal.Value
@property
def pips(self):
return self._pips.Value
@property
def take_profit(self):
return self._take_profit.Value
@property
def stop_loss(self):
return self._stop_loss.Value
@property
def max_spread(self):
return self._max_spread.Value
@property
def candle_type(self):
return self._candle_type.Value
@property
def min_di_spread(self):
return self._min_di_spread.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnReseted(self):
super(adx_stop_order_template_strategy, self).OnReseted()
self._prev_plus = None
self._prev_minus = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(adx_stop_order_template_strategy, self).OnStarted2(time)
dmi = DirectionalIndex()
dmi.Length = self.adx_period
adx = AverageDirectionalIndex()
adx.Length = self.adx_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(dmi, adx, self.process_candle).Start()
step = self.Security.PriceStep if self.Security.PriceStep is not None else 1.0
step = float(step)
self.StartProtection(
Unit(self.take_profit * step, UnitTypes.Absolute),
Unit(self.stop_loss * step, UnitTypes.Absolute))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, adx)
self.DrawIndicator(area, dmi)
self.DrawOwnTrades(area)
def process_candle(self, candle, dmi_value, adx_value):
if candle.State != CandleStates.Finished or not dmi_value.IsFinal or not adx_value.IsFinal:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
di_plus = dmi_value.Plus
di_minus = dmi_value.Minus
if di_plus is None or di_minus is None:
return
di_plus = float(di_plus)
di_minus = float(di_minus)
adx_ma = adx_value.MovingAverage
if adx_ma is None:
return
adx_val = float(adx_ma)
if self._prev_plus is None or self._prev_minus is None:
self._prev_plus = di_plus
self._prev_minus = di_minus
return
cross_up = self._prev_plus <= self._prev_minus and di_plus > di_minus
cross_down = self._prev_plus >= self._prev_minus and di_plus < di_minus
di_spread = abs(di_plus - di_minus)
adx_threshold = float(self.adx_signal)
min_di = float(self.min_di_spread)
if self._cooldown_remaining == 0 and adx_val >= adx_threshold and di_spread >= min_di:
if cross_up and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
elif cross_down and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
elif self.Position > 0 and cross_down:
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
elif self.Position < 0 and cross_up:
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
self._prev_plus = di_plus
self._prev_minus = di_minus
def CreateClone(self):
return adx_stop_order_template_strategy()