Стратегия ADX Crossing
Стратегия ADX Crossing основана на индикаторе Average Directional Index (ADX). Она анализирует пересечения линий +DI и -DI для определения возможной смены тенденции.
Когда +DI пересекает -DI снизу вверх, считается, что начинается восходящий тренд. Стратегия может открыть длинную позицию и при необходимости закрыть открытую короткую. Если +DI пересекает -DI сверху вниз, это трактуется как сигнал на продажу: открывается короткая позиция и при необходимости закрывается длинная. Опциональные уровни стоп-лосса и тейк-профита реализованы через встроенную систему риск‑менеджмента.
Индикатор
Используется индикатор AverageDirectionalIndex из StockSharp. Для принятия решений задействуются только линии направленного движения; значение ADX не участвует в логике.
Параметры
ADX Period – период расчёта ADX. По умолчанию 50.
Candle Type – таймфрейм свечей. По умолчанию 1 час.
Allow Buy Open – разрешение на открытие длинных позиций. По умолчанию true.
Allow Sell Open – разрешение на открытие коротких позиций. По умолчанию true.
Allow Buy Close – разрешение закрывать длинные позиции при сигнале на продажу. По умолчанию true.
Allow Sell Close – разрешение закрывать короткие позиции при сигнале на покупку. По умолчанию true.
Stop Loss – расстояние до стоп‑лосса в абсолютных ценовых единицах. По умолчанию 1000.
Take Profit – расстояние до тейк‑профита в абсолютных ценовых единицах. По умолчанию 2000.
Логика торговли
- Подписка на свечи выбранного таймфрейма и вычисление индикатора ADX.
- Сохранение предыдущих значений +DI и -DI для поиска пересечений.
- При пересечении +DI вверх:
- Закрытие короткой позиции, если включён параметр
Allow Sell Close.
- Открытие длинной позиции, если включён параметр
Allow Buy Open.
- При пересечении +DI вниз:
- Закрытие длинной позиции при активном
Allow Buy Close.
- Открытие короткой позиции при активном
Allow Sell Open.
- Уровни стоп-лосса и тейк-профита устанавливаются через
StartProtection.
Примечания
- Обрабатываются только завершённые свечи (
CandleStates.Finished).
- За исполнение стопов отвечает встроенный механизм StockSharp.
- Позиции закрываются отправкой противоположной рыночной заявки.
Стратегия предназначена для обучения и требует дополнительной настройки перед использованием на реальных рынках.
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 crossovers of the +DI and -DI lines of the ADX indicator.
/// It opens or closes positions when directional lines cross each other.
/// </summary>
public class AdxCrossingStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<bool> _allowBuyOpen;
private readonly StrategyParam<bool> _allowSellOpen;
private readonly StrategyParam<bool> _allowBuyClose;
private readonly StrategyParam<bool> _allowSellClose;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _trendThreshold;
private decimal _prevPlusDi;
private decimal _prevMinusDi;
private bool _isInitialized;
/// <summary>
/// ADX period.
/// </summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Permission to open long positions.
/// </summary>
public bool AllowBuyOpen
{
get => _allowBuyOpen.Value;
set => _allowBuyOpen.Value = value;
}
/// <summary>
/// Permission to open short positions.
/// </summary>
public bool AllowSellOpen
{
get => _allowSellOpen.Value;
set => _allowSellOpen.Value = value;
}
/// <summary>
/// Permission to close long positions.
/// </summary>
public bool AllowBuyClose
{
get => _allowBuyClose.Value;
set => _allowBuyClose.Value = value;
}
/// <summary>
/// Permission to close short positions.
/// </summary>
public bool AllowSellClose
{
get => _allowSellClose.Value;
set => _allowSellClose.Value = value;
}
/// <summary>
/// Stop-loss in absolute price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Take-profit in absolute price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Minimal ADX strength required to trade.
/// </summary>
public decimal TrendThreshold
{
get => _trendThreshold.Value;
set => _trendThreshold.Value = value;
}
/// <summary>
/// Initialize strategy parameters.
/// </summary>
public AdxCrossingStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 50)
.SetDisplay("ADX Period", "Period of ADX indicator", "Indicators")
.SetOptimize(10, 100, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for calculations", "General");
_allowBuyOpen = Param(nameof(AllowBuyOpen), true)
.SetDisplay("Allow Buy Open", "Enable opening long positions", "Permissions");
_allowSellOpen = Param(nameof(AllowSellOpen), true)
.SetDisplay("Allow Sell Open", "Enable opening short positions", "Permissions");
_allowBuyClose = Param(nameof(AllowBuyClose), true)
.SetDisplay("Allow Buy Close", "Enable closing long positions", "Permissions");
_allowSellClose = Param(nameof(AllowSellClose), true)
.SetDisplay("Allow Sell Close", "Enable closing short positions", "Permissions");
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetDisplay("Stop Loss", "Absolute stop-loss in price units", "Risk");
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetDisplay("Take Profit", "Absolute take-profit in price units", "Risk");
_trendThreshold = Param(nameof(TrendThreshold), 15m)
.SetDisplay("Trend Threshold", "Minimal ADX strength required to trade", "Indicators");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevPlusDi = default;
_prevMinusDi = default;
_isInitialized = false;
}
/// <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);
}
StartProtection(
stopLoss: StopLoss > 0m ? new Unit(StopLoss, UnitTypes.Absolute) : null,
takeProfit: TakeProfit > 0m ? new Unit(TakeProfit, UnitTypes.Absolute) : null);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var adx = (AverageDirectionalIndexValue)adxValue;
if (adx.Dx.Plus is not decimal plusDi || adx.Dx.Minus is not decimal minusDi)
return;
if (!_isInitialized)
{
_prevPlusDi = plusDi;
_prevMinusDi = minusDi;
_isInitialized = true;
return;
}
if (adx.MovingAverage is not decimal adxStrength || adxStrength < TrendThreshold)
{
_prevPlusDi = plusDi;
_prevMinusDi = minusDi;
return;
}
var buySignal = plusDi > minusDi && _prevPlusDi <= _prevMinusDi;
var sellSignal = plusDi < minusDi && _prevPlusDi >= _prevMinusDi;
if (buySignal)
{
if (AllowBuyOpen && Position <= 0)
BuyMarket(Position < 0 ? Volume + Math.Abs(Position) : Volume);
else if (AllowSellClose && Position < 0)
BuyMarket(Math.Abs(Position));
}
if (sellSignal)
{
if (AllowSellOpen && Position >= 0)
SellMarket(Position > 0 ? Volume + Position : Volume);
else if (AllowBuyClose && Position > 0)
SellMarket(Math.Abs(Position));
}
_prevPlusDi = plusDi;
_prevMinusDi = minusDi;
}
}
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, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
class adx_crossing_strategy(Strategy):
def __init__(self):
super(adx_crossing_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 50) \
.SetDisplay("ADX Period", "Period of ADX indicator", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles for calculations", "General")
self._allow_buy_open = self.Param("AllowBuyOpen", True) \
.SetDisplay("Allow Buy Open", "Enable opening long positions", "Permissions")
self._allow_sell_open = self.Param("AllowSellOpen", True) \
.SetDisplay("Allow Sell Open", "Enable opening short positions", "Permissions")
self._allow_buy_close = self.Param("AllowBuyClose", True) \
.SetDisplay("Allow Buy Close", "Enable closing long positions", "Permissions")
self._allow_sell_close = self.Param("AllowSellClose", True) \
.SetDisplay("Allow Sell Close", "Enable closing short positions", "Permissions")
self._stop_loss = self.Param("StopLoss", 1000.0) \
.SetDisplay("Stop Loss", "Absolute stop-loss in price units", "Risk")
self._take_profit = self.Param("TakeProfit", 2000.0) \
.SetDisplay("Take Profit", "Absolute take-profit in price units", "Risk")
self._trend_threshold = self.Param("TrendThreshold", 15.0) \
.SetDisplay("Trend Threshold", "Minimal ADX strength required to trade", "Indicators")
self._prev_plus_di = 0.0
self._prev_minus_di = 0.0
self._is_initialized = False
@property
def AdxPeriod(self):
return self._adx_period.Value
@AdxPeriod.setter
def AdxPeriod(self, value):
self._adx_period.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def AllowBuyOpen(self):
return self._allow_buy_open.Value
@AllowBuyOpen.setter
def AllowBuyOpen(self, value):
self._allow_buy_open.Value = value
@property
def AllowSellOpen(self):
return self._allow_sell_open.Value
@AllowSellOpen.setter
def AllowSellOpen(self, value):
self._allow_sell_open.Value = value
@property
def AllowBuyClose(self):
return self._allow_buy_close.Value
@AllowBuyClose.setter
def AllowBuyClose(self, value):
self._allow_buy_close.Value = value
@property
def AllowSellClose(self):
return self._allow_sell_close.Value
@AllowSellClose.setter
def AllowSellClose(self, value):
self._allow_sell_close.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def TrendThreshold(self):
return self._trend_threshold.Value
@TrendThreshold.setter
def TrendThreshold(self, value):
self._trend_threshold.Value = value
def OnStarted2(self, time):
super(adx_crossing_strategy, self).OnStarted2(time)
adx = AverageDirectionalIndex()
adx.Length = self.AdxPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription \
.BindEx(adx, self.ProcessCandle) \
.Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, adx)
self.DrawOwnTrades(area)
sl = Unit(self.StopLoss, UnitTypes.Absolute) if self.StopLoss > 0 else None
tp = Unit(self.TakeProfit, UnitTypes.Absolute) if self.TakeProfit > 0 else None
self.StartProtection(stopLoss=sl, takeProfit=tp)
def ProcessCandle(self, candle, adx_value):
if candle.State != CandleStates.Finished:
return
plus_raw = adx_value.Dx.Plus
minus_raw = adx_value.Dx.Minus
if plus_raw is None or minus_raw is None:
return
plus_di = float(plus_raw)
minus_di = float(minus_raw)
if not self._is_initialized:
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
self._is_initialized = True
return
ma_raw = adx_value.MovingAverage
if ma_raw is None:
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
return
adx_strength = float(ma_raw)
if adx_strength < float(self.TrendThreshold):
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
return
buy_signal = plus_di > minus_di and self._prev_plus_di <= self._prev_minus_di
sell_signal = plus_di < minus_di and self._prev_plus_di >= self._prev_minus_di
if buy_signal:
if self.AllowBuyOpen and self.Position <= 0:
volume = self.Volume + abs(self.Position) if self.Position < 0 else self.Volume
self.BuyMarket(volume)
elif self.AllowSellClose and self.Position < 0:
self.BuyMarket(abs(self.Position))
if sell_signal:
if self.AllowSellOpen and self.Position >= 0:
volume = self.Volume + self.Position if self.Position > 0 else self.Volume
self.SellMarket(volume)
elif self.AllowBuyClose and self.Position > 0:
self.SellMarket(abs(self.Position))
self._prev_plus_di = plus_di
self._prev_minus_di = minus_di
def OnReseted(self):
super(adx_crossing_strategy, self).OnReseted()
self._prev_plus_di = 0.0
self._prev_minus_di = 0.0
self._is_initialized = False
def CreateClone(self):
return adx_crossing_strategy()