Прорыв объёма по ADX
Стратегия ADX Volume Breakout основана на индексе ADX с подтверждением прорыва по объёму.
Тестирование показывает среднегодичную доходность около 55%. Стратегию лучше запускать на фондовом рынке.
Сигналы формируются, когда индикаторы подтверждают возможность прорыва на внутридневных данных (5м). Такой метод подходит активным трейдерам.
Стопы рассчитываются на основе кратных ATR и параметров AdxPeriod, AdxThreshold. Настройте эти значения для баланса риска и прибыли.
Подробности
- Критерии входа: см. реализацию условий индикаторов.
- Длинные/короткие: обе стороны.
- Критерии выхода: противоположный сигнал или логика стопов.
- Стопы: да, расчёт на основе индикаторов.
- Значения по умолчанию:
AdxPeriod = 14AdxThreshold = 25mVolumeAvgPeriod = 20VolumeThresholdFactor = 2.0mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Фильтры:
- Категория: Следование за трендом
- Направление: Оба
- Индикаторы: несколько индикаторов
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной (5м)
- Сезонность: Нет
- Нейронные сети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// ADX trend strength with a volume breakout confirmation.
/// Enters in the direction of the dominant directional index when ADX is above <see cref="AdxThreshold"/>
/// and volume exceeds its average by <see cref="VolumeThresholdFactor"/>. The opposite signal reverses the position.
/// </summary>
public class AdxWithVolumeBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxThreshold;
private readonly StrategyParam<int> _volumeAvgPeriod;
private readonly StrategyParam<decimal> _volumeThresholdFactor;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeSma;
/// <summary>
/// ADX period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Minimum ADX value for a strong trend.
/// </summary>
public decimal AdxThreshold
{
get => _adxThreshold.Value;
set => _adxThreshold.Value = value;
}
/// <summary>
/// Period of the volume average.
/// </summary>
public int VolumeAvgPeriod
{
get => _volumeAvgPeriod.Value;
set => _volumeAvgPeriod.Value = value;
}
/// <summary>
/// Volume must exceed its average multiplied by this factor.
/// </summary>
public decimal VolumeThresholdFactor
{
get => _volumeThresholdFactor.Value;
set => _volumeThresholdFactor.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="AdxWithVolumeBreakoutStrategy"/>.
/// </summary>
public AdxWithVolumeBreakoutStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.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), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Avg Period", "Period for volume moving average", "Indicators");
_volumeThresholdFactor = Param(nameof(VolumeThresholdFactor), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Volume Factor", "Volume must exceed its average by this factor", "Indicators");
_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;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
_volumeSma = new SimpleMovingAverage { Length = VolumeAvgPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var adxArea = CreateChartArea();
if (adxArea != null)
DrawIndicator(adxArea, adx);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
// The average is taken over previous bars so the current bar's volume is compared with history.
var volumeAverage = _volumeSma.GetCurrentValue();
var volumeFormed = _volumeSma.IsFormed;
_volumeSma.Process(candle.TotalVolume, candle.ServerTime, true);
if (!volumeFormed || !adxValue.IsFormed)
return;
var adxTyped = (AverageDirectionalIndexValue)adxValue;
if (adxTyped.MovingAverage is not decimal adx)
return;
if (adxTyped.Dx.Plus is not decimal plusDi || adxTyped.Dx.Minus is not decimal minusDi)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (adx <= AdxThreshold || candle.TotalVolume <= volumeAverage * VolumeThresholdFactor)
return;
if (plusDi > minusDi && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (minusDi > plusDi && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
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 AverageDirectionalIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class adx_with_volume_breakout_strategy(Strategy):
"""
ADX trend strength with a volume breakout confirmation.
Enters in the direction of the dominant directional index when ADX is above AdxThreshold
and volume exceeds its average by VolumeThresholdFactor. The opposite signal reverses the position.
"""
def __init__(self):
super(adx_with_volume_breakout_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14) \
.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", 20) \
.SetGreaterThanZero() \
.SetDisplay("Volume Avg Period", "Period for volume moving average", "Indicators")
self._volume_threshold_factor = self.Param("VolumeThresholdFactor", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Volume Factor", "Volume must exceed its average by this factor", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_sma = None
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(adx_with_volume_breakout_strategy, self).OnReseted()
self._volume_sma = None
def OnStarted2(self, time):
super(adx_with_volume_breakout_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = self._adx_period.Value
self._volume_sma = SimpleMovingAverage()
self._volume_sma.Length = self._volume_avg_period.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(adx, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
adx_area = self.CreateChartArea()
if adx_area is not None:
self.DrawIndicator(adx_area, adx)
def _process_candle(self, candle, adx_value):
if candle.State != CandleStates.Finished:
return
# The average is taken over previous bars so the current bar's volume is compared with history.
volume_average = float(get_current_value(self._volume_sma))
volume_formed = self._volume_sma.IsFormed
process_float(self._volume_sma, candle.TotalVolume, candle.ServerTime, True)
if not volume_formed or not adx_value.IsFormed:
return
adx_ma = adx_value.MovingAverage
if adx_ma is None:
return
dx = adx_value.Dx
if dx.Plus is None or dx.Minus is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
adx = float(adx_ma)
plus_di = float(dx.Plus)
minus_di = float(dx.Minus)
volume = float(candle.TotalVolume)
if adx <= float(self._adx_threshold.Value) or \
volume <= volume_average * float(self._volume_threshold_factor.Value):
return
if plus_di > minus_di and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif minus_di > plus_di and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return adx_with_volume_breakout_strategy()