Тренд по индексу ADX
Стратегия основана на индексе среднего направленного движения (ADX).
Тестирование показывает среднегодичную доходность около 46%. Стратегию лучше запускать на фондовом рынке.
Система оценивает силу рынка по значению ADX. Когда ADX выше порога и цена находится по соответствующую сторону от своей скользящей средней, открывается позиция в этом направлении. Закрытие происходит, когда ADX слабеет или появляется противоположный сигнал.
Ожидание уверенного значения ADX позволяет торговать только при устойчивом импульсе. Стопы обычно вычисляются как кратное ATR, чтобы риск учитывал волатильность.
Детали
- Критерии входа: сигналы на основе MA, ADX, ATR.
- Длинные/короткие: оба направления.
- Критерии выхода: противоположный сигнал или стоп.
- Стопы: да.
- Значения по умолчанию:
AdxPeriod= 14MaPeriod= 50AtrMultiplier= 2mAdxExitThreshold= 20CandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Тренд
- Направление: Оба
- Индикаторы: MA, ADX, ATR
- Стопы: Да
- Сложность: Базовая
- Таймфрейм: Внутридневной (5m)
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on Average Directional Index (ADX) trend.
/// It enters long position when ADX > 25 and price > MA, and short position when ADX > 25 and price < MA.
/// </summary>
public class AdxTrendStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _adxExitThreshold;
private readonly StrategyParam<DataType> _candleType;
// Current trend state
private bool _adxAboveThreshold;
private decimal _prevAdxValue;
private decimal _prevMaValue;
/// <summary>
/// ADX period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Moving Average period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// ATR multiplier for stop loss.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// ADX threshold to exit position.
/// </summary>
public int AdxExitThreshold
{
get => _adxExitThreshold.Value;
set => _adxExitThreshold.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize the ADX Trend strategy.
/// </summary>
public AdxTrendStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 50)
.SetDisplay("ADX Period", "Period for calculating ADX indicator", "Indicators")
.SetOptimize(10, 30, 2);
_maPeriod = Param(nameof(MaPeriod), 200)
.SetDisplay("MA Period", "Period for calculating Moving Average", "Indicators")
.SetOptimize(20, 100, 10);
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetDisplay("ATR Multiplier", "Multiplier for stop-loss based on ATR", "Risk parameters")
.SetOptimize(1, 3, 0.5m);
_adxExitThreshold = Param(nameof(AdxExitThreshold), 20)
.SetDisplay("ADX Exit Threshold", "ADX level below which to exit position", "Exit parameters")
.SetOptimize(15, 25, 1);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).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();
_adxAboveThreshold = default;
_prevAdxValue = default;
_prevMaValue = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var ma = new SMA { Length = MaPeriod };
var currentAdxMa = 0m;
var currentMaValue = 0m;
// Create subscription and bind indicators
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, (candle, adxVal) =>
{
if (adxVal is AverageDirectionalIndexValue adxTyped && adxTyped.MovingAverage is decimal adxMaVal)
currentAdxMa = adxMaVal;
})
.Bind(ma, (candle, maVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
currentMaValue = maVal;
ProcessCandle(candle, currentAdxMa, currentMaValue);
})
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, adx);
DrawIndicator(area, ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal adxMa, decimal maValue)
{
if (adxMa == 0 || maValue == 0)
{
_prevMaValue = maValue;
_prevAdxValue = adxMa;
return;
}
var isPriceAboveMa = candle.ClosePrice > maValue;
var wasPriceAboveMa = _prevMaValue != 0 && candle.OpenPrice > _prevMaValue;
var isAdxStrong = adxMa > 25;
// Only trade on MA crossover when ADX is strong
if (_prevMaValue != 0 && isAdxStrong && wasPriceAboveMa != isPriceAboveMa)
{
if (isPriceAboveMa && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
}
else if (!isPriceAboveMa && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
}
}
// Update previous values
_prevAdxValue = adxMa;
_prevMaValue = maValue;
_adxAboveThreshold = isAdxStrong;
}
}
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
class adx_trend_strategy(Strategy):
"""
Strategy based on Average Directional Index (ADX) trend.
Enters long when ADX > 25 and price crosses above MA.
Enters short when ADX > 25 and price crosses below MA.
"""
def __init__(self):
super(adx_trend_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 50).SetDisplay("ADX Period", "Period for calculating ADX indicator", "Indicators")
self._ma_period = self.Param("MaPeriod", 200).SetDisplay("MA Period", "Period for calculating Moving Average", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetDisplay("ATR Multiplier", "Multiplier for stop-loss based on ATR", "Risk parameters")
self._adx_exit_threshold = self.Param("AdxExitThreshold", 20).SetDisplay("ADX Exit Threshold", "ADX level below which to exit position", "Exit parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._adx_above_threshold = False
self._prev_adx_value = 0.0
self._prev_ma_value = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(adx_trend_strategy, self).OnReseted()
self._adx_above_threshold = False
self._prev_adx_value = 0.0
self._prev_ma_value = 0.0
def OnStarted2(self, time):
super(adx_trend_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = self._adx_period.Value
ma = SimpleMovingAverage()
ma.Length = self._ma_period.Value
self._current_adx_ma = 0.0
subscription = self.SubscribeCandles(self.candle_type)
subscription \
.BindEx(adx, self._process_adx) \
.Bind(ma, self._process_ma) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, adx)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _process_adx(self, candle, adx_val):
if hasattr(adx_val, 'MovingAverage') and adx_val.MovingAverage is not None:
self._current_adx_ma = float(adx_val.MovingAverage)
def _process_ma(self, candle, ma_val):
if candle.State != CandleStates.Finished:
return
adx_ma = self._current_adx_ma
ma_value = float(ma_val)
if adx_ma == 0 or ma_value == 0:
self._prev_ma_value = ma_value
self._prev_adx_value = adx_ma
return
is_price_above_ma = float(candle.ClosePrice) > ma_value
was_price_above_ma = self._prev_ma_value != 0 and float(candle.OpenPrice) > self._prev_ma_value
is_adx_strong = adx_ma > 25
if self._prev_ma_value != 0 and is_adx_strong and was_price_above_ma != is_price_above_ma:
if is_price_above_ma and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif not is_price_above_ma and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._prev_adx_value = adx_ma
self._prev_ma_value = ma_value
self._adx_above_threshold = is_adx_strong
def CreateClone(self):
return adx_trend_strategy()