Adx Stochastic Strategy
Strategy that combines ADX (Average Directional Index) for trend strength and Stochastic Oscillator for entry timing with oversold/overbought conditions.
Testing indicates an average annual return of about 172%. It performs best in the forex market.
ADX highlights trend strength while Stochastic pinpoints pullbacks. Long or short signals appear when momentum turns while ADX stays high.
It suits traders who combine trend following with oscillator timing. A protective percent stop helps control drawdowns.
Details
- Entry Criteria:
- Long:
ADX > AdxThreshold && StochK < StochOversold && Bullish - Short:
ADX > AdxThreshold && StochK > StochOverbought && Bearish
- Long:
- Long/Short: Both
- Exit Criteria:
- Exit when
ADX < AdxThreshold
- Exit when
- Stops: Percent-based at
StopLossPercent - Default Values:
AdxPeriod= 14AdxThreshold= 25mStochPeriod= 14StochK= 3StochOversold= 20mStochOverbought= 80mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame() Bullish means +DI above -DI and bearish means -DI above +DI. StochK in the rules is %K: the stochastic over StochPeriod candles smoothed over StochK candles; %D plays no part, so it has no setting. An entry against an open position reverses it.
- Filters:
- Category: Mean reversion
- Direction: Both
- Indicators: ADX, Stochastic Oscillator
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Mid-term
- 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 Stochastic strategy.
/// Bullish means +DI above -DI and bearish means -DI above +DI. While ADX is above AdxThreshold, %K below StochOversold in a bullish
/// trend goes long and %K above StochOverbought in a bearish trend goes short, reversing an opposite position; %K is the stochastic over
/// StochPeriod candles smoothed over StochK candles. The position closes once ADX falls below AdxThreshold, and a percent stop limits the loss.
/// </summary>
public class AdxStochasticStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<int> _stochK;
private readonly StrategyParam<decimal> _stochOversold;
private readonly StrategyParam<decimal> _stochOverbought;
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>
/// ADX level of a strong trend.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Lookback period of the raw stochastic.
/// </summary>
public int StochPeriod
{
get => _stochPeriod.Value;
set => _stochPeriod.Value = value;
}
/// <summary>
/// Smoothing period of %K.
/// </summary>
public int StochK
{
get => _stochK.Value;
set => _stochK.Value = value;
}
/// <summary>
/// %K level for longs.
/// </summary>
public decimal StochOversold
{
get => _stochOversold.Value;
set => _stochOversold.Value = value;
}
/// <summary>
/// %K level for shorts.
/// </summary>
public decimal StochOverbought
{
get => _stochOverbought.Value;
set => _stochOverbought.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 AdxStochasticStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Period of ADX", "ADX");
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetDisplay("ADX Threshold", "ADX level of a strong trend", "ADX");
_stochPeriod = Param(nameof(StochPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Stochastic Period", "Lookback period of the raw stochastic", "Stochastic");
_stochK = Param(nameof(StochK), 3)
.SetGreaterThanZero()
.SetDisplay("Stochastic %K", "Smoothing period of %K", "Stochastic");
_stochOversold = Param(nameof(StochOversold), 20m)
.SetDisplay("Stochastic Oversold", "%K level for longs", "Stochastic");
_stochOverbought = Param(nameof(StochOverbought), 80m)
.SetDisplay("Stochastic Overbought", "%K level for shorts", "Stochastic");
_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 };
// The D line of the core oscillator is the smoothed %K.
var stochastic = new StochasticOscillator
{
K = { Length = StochPeriod },
D = { Length = StochK },
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, stochastic, 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);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, adx);
DrawIndicator(oscillators, stochastic);
}
}
}
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 stochasticValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!adxValue.IsFormed || !stochasticValue.IsFormed)
return;
if (adxValue is not AverageDirectionalIndexValue { MovingAverage: decimal strength } typedAdx
|| typedAdx.Dx.Plus is not decimal plusDi || typedAdx.Dx.Minus is not decimal minusDi)
return;
if (stochasticValue is not IStochasticOscillatorValue { D: decimal k })
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var strong = strength > AdxThreshold;
if (strong && k < StochOversold && plusDi > minusDi && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (strong && k > StochOverbought && minusDi > plusDi && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && strength < AdxThreshold)
SellMarket(Position);
else if (Position < 0 && strength < AdxThreshold)
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, StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class adx_stochastic_strategy(Strategy):
"""
ADX Stochastic strategy.
Bullish means +DI above -DI and bearish means -DI above +DI. While ADX is above AdxThreshold, %K below StochOversold in a bullish
trend goes long and %K above StochOverbought in a bearish trend goes short, reversing an opposite position; %K is the stochastic over
StochPeriod candles smoothed over StochK candles. The position closes once ADX falls below AdxThreshold, and a percent stop limits the loss.
"""
def __init__(self):
super(adx_stochastic_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14).SetGreaterThanZero().SetDisplay("ADX Period", "Period of ADX", "ADX")
self._adx_threshold = self.Param("AdxThreshold", 25.0).SetDisplay("ADX Threshold", "ADX level of a strong trend", "ADX")
self._stoch_period = self.Param("StochPeriod", 14).SetGreaterThanZero().SetDisplay("Stochastic Period", "Lookback period of the raw stochastic", "Stochastic")
self._stoch_k = self.Param("StochK", 3).SetGreaterThanZero().SetDisplay("Stochastic %K", "Smoothing period of %K", "Stochastic")
self._stoch_oversold = self.Param("StochOversold", 20.0).SetDisplay("Stochastic Oversold", "%K level for longs", "Stochastic")
self._stoch_overbought = self.Param("StochOverbought", 80.0).SetDisplay("Stochastic Overbought", "%K level for shorts", "Stochastic")
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_stochastic_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = self._adx_period.Value
# The D line of the core oscillator is the smoothed %K.
stochastic = StochasticOscillator()
stochastic.K.Length = self._stoch_period.Value
stochastic.D.Length = self._stoch_k.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, stochastic, 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.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, adx)
self.DrawIndicator(oscillators, stochastic)
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, stochastic_value):
if candle.State != CandleStates.Finished:
return
if not adx_value.IsFormed or not stochastic_value.IsFormed:
return
if adx_value.MovingAverage is None or adx_value.Dx.Plus is None or adx_value.Dx.Minus is None or stochastic_value.D is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
strength = adx_value.MovingAverage
plus_di = adx_value.Dx.Plus
minus_di = adx_value.Dx.Minus
k = stochastic_value.D
threshold = Decimal(self._adx_threshold.Value)
strong = strength > threshold
if strong and k < Decimal(self._stoch_oversold.Value) and plus_di > minus_di and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif strong and k > Decimal(self._stoch_overbought.Value) and minus_di > plus_di and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and strength < threshold:
self.SellMarket(self.Position)
elif self.Position < 0 and strength < threshold:
self.BuyMarket(-self.Position)
def CreateClone(self):
return adx_stochastic_strategy()