ADX Volume Breakout
The ADX Volume Breakout strategy is built around ADX with Volume Breakout.
Testing indicates an average annual return of about 55%. It performs best in the stocks market.
Signals trigger when its indicators confirms breakout opportunities on intraday (5m) data. This makes the method suitable for active traders.
Stops rely on ATR multiples and factors like AdxPeriod, AdxThreshold. Adjust these defaults to balance risk and reward.
Details
- Entry Criteria: see implementation for indicator conditions.
- Long/Short: Both directions.
- Exit Criteria: opposite signal or stop logic.
- Stops: Yes, using indicator-based calculations.
- Default Values:
AdxPeriod = 14AdxThreshold = 25mVolumeAvgPeriod = 20VolumeThresholdFactor = 2.0mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: multiple indicators
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Strategy based on ADX with a volume breakout confirmation.
/// </summary>
public class AdxWithVolumeBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<int> _volumeAvgPeriod;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeSma;
private int _cooldownRemaining;
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
public int VolumeAvgPeriod
{
get => _volumeAvgPeriod.Value;
set => _volumeAvgPeriod.Value = value;
}
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public AdxWithVolumeBreakoutStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators");
_adxThreshold = Param(nameof(AdxThreshold), 25m)
.SetGreaterThanZero()
.SetDisplay("ADX Threshold", "Threshold for strong trend identification", "Indicators");
_volumeAvgPeriod = Param(nameof(VolumeAvgPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Volume Avg Period", "Period for volume moving average", "Indicators");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 15)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Bars to wait between signals", "Trading");
_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 OnReseted()
{
base.OnReseted();
_volumeSma = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
_volumeSma = new SimpleMovingAverage { Length = VolumeAvgPeriod };
_cooldownRemaining = 0;
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(adx, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!adxValue.IsFinal)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
// Process volume average
var volumeAvgValue = _volumeSma.Process(new DecimalIndicatorValue(_volumeSma, candle.TotalVolume, candle.ServerTime));
var adxTyped = (AverageDirectionalIndexValue)adxValue;
if (adxTyped.MovingAverage is not decimal adx)
return;
var dx = adxTyped.Dx;
if (dx.Plus is not decimal plusDi || dx.Minus is not decimal minusDi)
return;
var volumeAverage = volumeAvgValue.IsFormed ? volumeAvgValue.ToDecimal() : 0m;
var isStrongTrend = adx > AdxThreshold;
var isVolumeBreakout = volumeAverage <= 0m || candle.TotalVolume >= volumeAverage;
var isBullish = plusDi > minusDi;
var isBearish = minusDi > plusDi;
if (_cooldownRemaining > 0)
return;
if (!isStrongTrend || !isVolumeBreakout)
return;
if (Position == 0)
{
if (isBullish)
{
BuyMarket();
_cooldownRemaining = SignalCooldownBars;
}
else if (isBearish)
{
SellMarket();
_cooldownRemaining = SignalCooldownBars;
}
}
}
}
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 AverageDirectionalIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class adx_with_volume_breakout_strategy(Strategy):
"""
Strategy based on ADX with a volume breakout confirmation.
"""
def __init__(self):
super(adx_with_volume_breakout_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 10) \
.SetGreaterThanZero() \
.SetDisplay("ADX Period", "Period for ADX calculation", "Indicators")
self._adx_threshold = self.Param("AdxThreshold", 25.0) \
.SetGreaterThanZero() \
.SetDisplay("ADX Threshold", "Threshold for strong trend identification", "Indicators")
self._volume_avg_period = self.Param("VolumeAvgPeriod", 10) \
.SetGreaterThanZero() \
.SetDisplay("Volume Avg Period", "Period for volume moving average", "Indicators")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 15) \
.SetGreaterThanZero() \
.SetDisplay("Signal Cooldown", "Bars to wait between signals", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adx_with_volume_breakout_strategy, self).OnReseted()
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(adx_with_volume_breakout_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = int(self._adx_period.Value)
self._volume_sma = SimpleMovingAverage()
self._volume_sma.Length = int(self._volume_avg_period.Value)
self._cooldown_remaining = 0
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
self.StartProtection(
Unit(2, UnitTypes.Percent),
Unit(1, UnitTypes.Percent)
)
def _process_candle(self, candle, adx_value):
if candle.State != CandleStates.Finished:
return
if not adx_value.IsFinal:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
volume_avg_result = process_float(self._volume_sma, candle.TotalVolume, candle.ServerTime, True)
adx_typed = adx_value
adx_ma = adx_typed.MovingAverage
if adx_ma is None:
return
dx = adx_typed.Dx
if dx is None:
return
plus_di = dx.Plus
minus_di = dx.Minus
if plus_di is None or minus_di is None:
return
adx_val = float(adx_ma)
plus_di_val = float(plus_di)
minus_di_val = float(minus_di)
volume_average = float(volume_avg_result) if volume_avg_result.IsFormed else 0.0
threshold = float(self._adx_threshold.Value)
is_strong_trend = adx_val > threshold
is_volume_breakout = volume_average <= 0.0 or float(candle.TotalVolume) >= volume_average
is_bullish = plus_di_val > minus_di_val
is_bearish = minus_di_val > plus_di_val
if self._cooldown_remaining > 0:
return
if not is_strong_trend or not is_volume_breakout:
return
cd = int(self._signal_cooldown_bars.Value)
if self.Position == 0:
if is_bullish:
self.BuyMarket()
self._cooldown_remaining = cd
elif is_bearish:
self.SellMarket()
self._cooldown_remaining = cd
def CreateClone(self):
return adx_with_volume_breakout_strategy()