ADX DI
Стратегия на основе индикаторов ADX и направленного движения
Тестирование показывает среднегодичную доходность около 103%. Стратегию лучше запускать на фондовом рынке.
ADX DI отслеживает пересечение +DI и -DI при растущем ADX. Бычйий крест +DI над -DI и высокий ADX открывают длинную позицию, обратное пересечение — короткую. Закрытие происходит при ослаблении ADX или противоположном пересечении.
Такое сочетание позволяет избегать торговли по каждому пересечению DI, требуя подтверждения от ADX. Система стремится ловить устойчивые тренды, а не краткосрочные колебания.
Детали
- Условия входа: сигналы на основе ADX, ATR.
- Длинные/короткие: в обе стороны.
- Условия выхода: противоположный сигнал или стоп.
- Стопы: да.
- Значения по умолчанию:
AdxPeriod= 14AdxThreshold= 25mAtrMultiplier= 2mCandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Тренд
- Направление: Оба
- Индикаторы: ADX, ATR
- Стопы: Да
- Сложность: Базовая
- Таймфрейм: Внутридневной (5м)
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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()