ADX Donchian Strategy
This strategy uses ADX Donchian indicators to generate signals. Long entry occurs when ADX > AdxThreshold && Price >= upperBorder (strong trend with breakout up). Short entry occurs when ADX > AdxThreshold && Price <= lowerBorder (strong trend with breakout down). It is suitable for traders seeking opportunities in mixed markets.
Testing indicates an average annual return of about 67%. It performs best in the stocks market.
Details
- Entry Criteria:
- Long: ADX > AdxThreshold && Price >= upperBorder (strong trend with breakout up)
- Short: ADX > AdxThreshold && Price <= lowerBorder (strong trend with breakout down)
- Long/Short: Both sides.
- Exit Criteria:
- Long: Exit position when ADX falls below (threshold - 5)
- Short: Exit position when ADX falls below (threshold - 5)
- Stops: Yes.
- Default Values:
AdxPeriod= 14DonchianPeriod= 5StopLossPercent= 2CandleType= TimeSpan.FromMinutes(5)AdxThreshold= 10Multiplier= 0.1 upperBorder is the highest high of the last DonchianPeriod candles, the current one included, lowered by Multiplier percent, and lowerBorder is the lowest low raised by Multiplier percent. The stop is a fixed StopLossPercent of the entry price, watched between candles as well. An entry signal against an open position reverses it.
- Filters:
- Category: Mixed
- Direction: Both
- Indicators: ADX Donchian
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- 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>
/// ADX Donchian strategy.
/// The borders sit Multiplier percent inside the DonchianPeriod channel, which includes the current candle. With ADX above AdxThreshold
/// a close at or above the upper border goes long and one at or below the lower border goes short, reversing an opposite position.
/// The position closes once ADX falls below AdxThreshold minus 5, and a percent stop limits the loss.
/// </summary>
public class AdxDonchianStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<int> _donchianPeriod;
private readonly StrategyParam<int> _adxThreshold;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// Period of ADX.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Candles the channel spans.
/// </summary>
public int DonchianPeriod
{
get => _donchianPeriod.Value;
set => _donchianPeriod.Value = value;
}
/// <summary>
/// ADX value for strong trend detection.
/// </summary>
public int AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Percent the borders sit inside the channel.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public AdxDonchianStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Period of ADX", "Indicators");
_donchianPeriod = Param(nameof(DonchianPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Donchian Period", "Candles the channel spans", "Indicators");
_adxThreshold = Param(nameof(AdxThreshold), 10)
.SetGreaterThanZero()
.SetDisplay("ADX Threshold", "ADX value for strong trend detection", "Indicators");
_multiplier = Param(nameof(Multiplier), 0.1m)
.SetNotNegative()
.SetDisplay("Multiplier", "Percent the borders sit inside the channel", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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 OnStarted2(DateTime time)
{
base.OnStarted2(time);
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var donchian = new DonchianChannels { Length = DonchianPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, donchian, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, donchian);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, adx);
}
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue, IIndicatorValue donchianValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!adxValue.IsFormed || !donchianValue.IsFormed)
return;
if (adxValue is not AverageDirectionalIndexValue { MovingAverage: decimal strength })
return;
if (donchianValue is not IDonchianChannelsValue { UpperBand: decimal upper, LowerBand: decimal lower })
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var upperBorder = upper * (1 - Multiplier / 100);
var lowerBorder = lower * (1 + Multiplier / 100);
var strong = strength > AdxThreshold;
var close = candle.ClosePrice;
if (strong && close >= upperBorder && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (strong && close <= lowerBorder && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && strength < AdxThreshold - 5)
SellMarket(Position);
else if (Position < 0 && strength < AdxThreshold - 5)
BuyMarket(-Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import AverageDirectionalIndex, DonchianChannels
from StockSharp.Algo.Strategies import Strategy
class adx_donchian_strategy(Strategy):
"""
ADX Donchian strategy.
The borders sit Multiplier percent inside the DonchianPeriod channel, which includes the current candle. With ADX above AdxThreshold
a close at or above the upper border goes long and one at or below the lower border goes short, reversing an opposite position.
The position closes once ADX falls below AdxThreshold minus 5, and a percent stop limits the loss.
"""
def __init__(self):
super(adx_donchian_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14).SetGreaterThanZero().SetDisplay("ADX Period", "Period of ADX", "Indicators")
self._donchian_period = self.Param("DonchianPeriod", 5).SetGreaterThanZero().SetDisplay("Donchian Period", "Candles the channel spans", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 10).SetGreaterThanZero().SetDisplay("ADX Threshold", "ADX value for strong trend detection", "Indicators")
self._multiplier = self.Param("Multiplier", 0.1).SetNotNegative().SetDisplay("Multiplier", "Percent the borders sit inside the channel", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
@property
def candle_type(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(adx_donchian_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = self._adx_period.Value
donchian = DonchianChannels()
donchian.Length = self._donchian_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, donchian, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, donchian)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, adx)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, adx_value, donchian_value):
if candle.State != CandleStates.Finished:
return
if not adx_value.IsFormed or not donchian_value.IsFormed:
return
if adx_value.MovingAverage is None or donchian_value.UpperBand is None or donchian_value.LowerBand is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
strength = adx_value.MovingAverage
threshold = Decimal(self._adx_threshold.Value)
shift = Decimal(self._multiplier.Value) / Decimal(100)
upper_border = donchian_value.UpperBand * (Decimal(1) - shift)
lower_border = donchian_value.LowerBand * (Decimal(1) + shift)
strong = strength > threshold
weak = strength < threshold - Decimal(5)
close = candle.ClosePrice
if strong and close >= upper_border and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif strong and close <= lower_border and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and weak:
self.SellMarket(self.Position)
elif self.Position < 0 and weak:
self.BuyMarket(-self.Position)
def CreateClone(self):
return adx_donchian_strategy()