ADX System DI Cross Strategy
Overview
The ADX System strategy is the StockSharp conversion of the MetaTrader 4 expert ADX_System.mq4. The original EA compares the
Average Directional Index (ADX) with its +DI and -DI components on the two most recent completed candles. When the +DI line
rises above the ADX value the system wants to be long; when the -DI line rises above the ADX value it wants to be short. The
StockSharp port reproduces this behaviour by storing the indicator values from the previous two finished candles so the logic
mirrors the iADX(..., shift=1/2) calls used in the MetaTrader code.
Only one position can be open at any time. The strategy submits market orders for entries and exits, matching the single-ticket
logic of MetaTrader netting accounts. Risk management mirrors the original expert advisor: fixed take-profit and stop-loss
levels are expressed in points relative to the average entry price, and an optional trailing stop can lock in profits once the
position moves favourably.
Trading logic
- Subscribe to the configured timeframe (
CandleType) and process only finished candles to avoid intra-bar decisions.
- Feed an
AverageDirectionalIndex indicator with the candle data and wait until the indicator provides its ADX, +DI, and -DI
values.
- Cache the indicator readings from the two most recent finished candles so the strategy can reference the "current" and
"previous" values exactly like the MetaTrader implementation.
- Long entry: if the older ADX (
shift = 2) is below the more recent ADX (shift = 1), the older +DI is below that older
ADX, and the more recent +DI is above the more recent ADX, send a market buy order.
- Short entry: if the same conditions appear for the -DI component (old -DI below old ADX, new -DI above new ADX), send a
market sell order.
- Long exit: close the long position when the ADX starts falling and +DI crosses back below it, when the configured
take-profit or stop-loss is hit, or when the trailing stop is breached.
- Short exit: mirror the long exit logic using -DI together with the risk controls.
- Update the cached indicator history after every candle so the next signal uses the latest
shift = 1/2 pair.
Risk management
TakeProfitPoints and StopLossPoints describe distances in MetaTrader-style points. They are converted to actual price units
using Security.PriceStep when available; otherwise the raw value is treated as an absolute price delta.
- The trailing stop (
TrailingStopPoints) activates only after the position gains at least the configured distance from the
entry price. Once active it moves in the direction of profit and closes the position when price crosses the trailing level.
- All exits (indicator reversal, take-profit, stop-loss, trailing stop) use market orders so the position is flattened
immediately, mimicking
OrderClose behaviour from the source EA.
Parameters
| Name |
Type |
Default |
Description |
CandleType |
DataType |
1-minute time frame |
Primary timeframe processed by the strategy. |
AdxPeriod |
int |
14 |
Number of candles used to compute the ADX and the DI components. |
TradeVolume |
decimal |
1 |
Lot size used for every market order. |
TakeProfitPoints |
decimal |
100 |
Take-profit distance in points relative to the entry price. |
StopLossPoints |
decimal |
30 |
Stop-loss distance in points relative to the entry price. |
TrailingStopPoints |
decimal |
0 |
Optional trailing-stop distance in points. Set to zero to disable trailing. |
- MetaTrader manages individual tickets while StockSharp works with a single net position. The conversion therefore closes the
current position before issuing a new entry order when the signal flips.
- The original EA relied on
Point to convert points into price distances. The StockSharp port uses Security.PriceStep when it
is known; otherwise the distance is treated as raw price units, so you may need to adjust the defaults for instruments with
unconventional price steps.
- MetaTrader applies trailing stops by modifying the existing order. StockSharp closes the position with a market order when the
trailing stop is violated, which is functionally equivalent but simpler within the netting model.
Usage tips
- Ensure the strategy volume (
TradeVolume) aligns with the instrument's lot step. The constructor also assigns this value to
Strategy.Volume, so helper methods use the expected trade size.
- Increase
TakeProfitPoints and StopLossPoints if you trade instruments with larger average ranges or smaller price steps.
- Add the strategy to a chart to visualise the candles, the ADX indicator, and executed trades, which helps verify that signals
occur exactly when +DI or -DI crosses above the ADX line.
Indicators
AverageDirectionalIndex (provides ADX together with +DI and -DI components).
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// ADX System DI Cross: trend-following using dual EMA crossover
/// (proxy for +DI/-DI cross) with ATR-based volatility filter.
/// </summary>
public class AdxSystemDiCrossStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrThreshold;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public AdxSystemDiCrossStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastLength = Param(nameof(FastLength), 10)
.SetDisplay("Fast EMA", "Fast EMA period (proxy for +DI).", "Indicators");
_slowLength = Param(nameof(SlowLength), 20)
.SetDisplay("Slow EMA", "Slow EMA period (proxy for -DI).", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period for trend strength.", "Indicators");
_atrThreshold = Param(nameof(AtrThreshold), 50m)
.SetDisplay("ATR Threshold", "Min ATR value for entry.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal AtrThreshold
{
get => _atrThreshold.Value;
set => _atrThreshold.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fast = new ExponentialMovingAverage { Length = FastLength };
var slow = new ExponentialMovingAverage { Length = SlowLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevFast == 0 || _prevSlow == 0)
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
var bullishCross = _prevFast <= _prevSlow && fastVal > slowVal;
var bearishCross = _prevFast >= _prevSlow && fastVal < slowVal;
// Exit on opposite cross
if (Position > 0 && bearishCross)
{
SellMarket();
_entryPrice = 0;
}
else if (Position < 0 && bullishCross)
{
BuyMarket();
_entryPrice = 0;
}
// Entry on cross + volatility filter
if (Position == 0 && atrVal >= AtrThreshold)
{
if (bullishCross)
{
_entryPrice = candle.ClosePrice;
BuyMarket();
}
else if (bearishCross)
{
_entryPrice = candle.ClosePrice;
SellMarket();
}
}
_prevFast = fastVal;
_prevSlow = slowVal;
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class adx_system_di_cross_strategy(Strategy):
"""
ADX System DI Cross: trend-following using dual EMA crossover
(proxy for +DI/-DI cross) with ATR-based volatility filter.
"""
def __init__(self):
super(adx_system_di_cross_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_length = self.Param("FastLength", 10) \
.SetDisplay("Fast EMA", "Fast EMA period (proxy for +DI).", "Indicators")
self._slow_length = self.Param("SlowLength", 20) \
.SetDisplay("Slow EMA", "Slow EMA period (proxy for -DI).", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period for trend strength.", "Indicators")
self._atr_threshold = self.Param("AtrThreshold", 50.0) \
.SetDisplay("ATR Threshold", "Min ATR value for entry.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FastLength(self):
return self._fast_length.Value
@FastLength.setter
def FastLength(self, value):
self._fast_length.Value = value
@property
def SlowLength(self):
return self._slow_length.Value
@SlowLength.setter
def SlowLength(self, value):
self._slow_length.Value = value
@property
def AtrLength(self):
return self._atr_length.Value
@AtrLength.setter
def AtrLength(self, value):
self._atr_length.Value = value
@property
def AtrThreshold(self):
return self._atr_threshold.Value
@AtrThreshold.setter
def AtrThreshold(self, value):
self._atr_threshold.Value = value
def OnReseted(self):
super(adx_system_di_cross_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def OnStarted2(self, time):
super(adx_system_di_cross_strategy, self).OnStarted2(time)
fast = ExponentialMovingAverage()
fast.Length = self.FastLength
slow = ExponentialMovingAverage()
slow.Length = self.SlowLength
atr = AverageTrueRange()
atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, slow, atr, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
if self._prev_fast == 0 or self._prev_slow == 0:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
bullish_cross = self._prev_fast <= self._prev_slow and fast_val > slow_val
bearish_cross = self._prev_fast >= self._prev_slow and fast_val < slow_val
# Exit on opposite cross
if self.Position > 0 and bearish_cross:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0 and bullish_cross:
self.BuyMarket()
self._entry_price = 0.0
# Entry on cross + volatility filter
if self.Position == 0 and atr_val >= self.AtrThreshold:
if bullish_cross:
self._entry_price = float(candle.ClosePrice)
self.BuyMarket()
elif bearish_cross:
self._entry_price = float(candle.ClosePrice)
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return adx_system_di_cross_strategy()