ADX DI
Strategy based on ADX and Directional Movement indicators
Testing indicates an average annual return of about 103%. It performs best in the stocks market.
ADX DI focuses on the crossing of +DI and -DI with rising ADX. A bullish cross of +DI over -DI coupled with strong ADX opens longs, while the opposite opens shorts. Positions close on a weakening ADX or opposite cross.
This combination helps avoid trading every DI cross by demanding confirmation from the ADX. The system aims to capture sustainable trends rather than short-term swings.
Details
- Entry Criteria: Signals based on ADX, ATR.
- Long/Short: Both directions.
- Exit Criteria: Opposite signal or stop.
- Stops: Yes.
- Default Values:
AdxPeriod= 14AdxThreshold= 25mAtrMultiplier= 2mCandleType= TimeSpan.FromMinutes(5)
- Filters:
- Category: Trend
- Direction: Both
- Indicators: ADX, ATR
- Stops: Yes
- Complexity: Basic
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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 Directional Movement indicators.
/// Buys when +DI crosses above -DI with strong ADX.
/// Sells when -DI crosses above +DI with strong ADX.
/// </summary>
public class AdxDiStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<DataType> _candleType;
private bool _prevPlusDiAbove;
private bool _hasPrevValues;
private int _cooldown;
/// <summary>
/// ADX period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// ADX threshold for trend confirmation.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="AdxDiStrategy"/>.
/// </summary>
public AdxDiStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators")
.SetOptimize(10, 20, 2);
_adxThreshold = Param(nameof(AdxThreshold), 15m)
.SetDisplay("ADX Threshold", "ADX level to confirm trend", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevPlusDiAbove = default;
_hasPrevValues = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, adx);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (adxValue.IsEmpty)
return;
decimal adxMain, plusDi, minusDi;
try
{
var adx = (AverageDirectionalIndexValue)adxValue;
if (adx.MovingAverage is not decimal ma)
return;
if (adx.Dx.Plus is not decimal pDi)
return;
if (adx.Dx.Minus is not decimal mDi)
return;
adxMain = ma;
plusDi = pDi;
minusDi = mDi;
}
catch (IndexOutOfRangeException)
{
return;
}
var plusDiAbove = plusDi > minusDi;
if (!_hasPrevValues)
{
_hasPrevValues = true;
_prevPlusDiAbove = plusDiAbove;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevPlusDiAbove = plusDiAbove;
return;
}
// +DI crosses above -DI with strong trend = buy
if (plusDiAbove && !_prevPlusDiAbove && adxMain >= AdxThreshold && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_cooldown = 5;
}
// -DI crosses above +DI with strong trend = sell
else if (!plusDiAbove && _prevPlusDiAbove && adxMain >= AdxThreshold && Position >= 0)
{
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_cooldown = 5;
}
_prevPlusDiAbove = plusDiAbove;
}
}
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
from StockSharp.Algo.Indicators import AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
class adx_di_strategy(Strategy):
def __init__(self):
super(adx_di_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14) \
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 15.0) \
.SetDisplay("ADX Threshold", "ADX level to confirm trend", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_plus_di_above = False
self._has_prev_values = False
self._cooldown = 0
@property
def AdxPeriod(self):
return self._adx_period.Value
@AdxPeriod.setter
def AdxPeriod(self, value):
self._adx_period.Value = value
@property
def AdxThreshold(self):
return self._adx_threshold.Value
@AdxThreshold.setter
def AdxThreshold(self, value):
self._adx_threshold.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(adx_di_strategy, self).OnStarted2(time)
self._prev_plus_di_above = False
self._has_prev_values = False
self._cooldown = 0
adx = AverageDirectionalIndex()
adx.Length = self.AdxPeriod
self.SubscribeCandles(self.CandleType) \
.BindEx(adx, self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle, adx_value):
if candle.State != CandleStates.Finished:
return
if adx_value.IsEmpty:
return
adx_main = adx_value.MovingAverage
plus_di = adx_value.Dx.Plus
minus_di = adx_value.Dx.Minus
if adx_main is None or plus_di is None or minus_di is None:
return
adx_main_f = float(adx_main)
plus_di_f = float(plus_di)
minus_di_f = float(minus_di)
plus_di_above = plus_di_f > minus_di_f
if not self._has_prev_values:
self._has_prev_values = True
self._prev_plus_di_above = plus_di_above
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_plus_di_above = plus_di_above
return
threshold = float(self.AdxThreshold)
if plus_di_above and not self._prev_plus_di_above and adx_main_f >= threshold and self.Position <= 0:
volume = self.Volume + Math.Abs(self.Position)
self.BuyMarket(volume)
self._cooldown = 5
elif not plus_di_above and self._prev_plus_di_above and adx_main_f >= threshold and self.Position >= 0:
volume = self.Volume + Math.Abs(self.Position)
self.SellMarket(volume)
self._cooldown = 5
self._prev_plus_di_above = plus_di_above
def OnReseted(self):
super(adx_di_strategy, self).OnReseted()
self._prev_plus_di_above = False
self._has_prev_values = False
self._cooldown = 0
def CreateClone(self):
return adx_di_strategy()