Шаблон стратегии с отложенными ордерами ADX
Эта стратегия демонстрирует, как размещать отложенные стоп-ордера, опираясь на индикатор ADX и линии направленного движения. Когда рынок показывает достаточную силу тренда и линии +DI и -DI пересекаются, система выставляет стоп на покупку или продажу на фиксированном расстоянии. Стоп-лосс и тейк-профит управляются автоматически.
Пример носит учебный характер и сосредоточен на обработке заявок. Трейдер может расширить его собственными фильтрами или правилами управления капиталом.
Подробности
- Условия входа:
- Значение ADX выше параметра
ADX Threshold. - Покупка:
+DIвыше-DI, а две свечи назад+DIбыл ниже-DI. - Продажа:
+DIниже-DI, а две свечи назад+DIбыл выше-DI. - Текущий спред меньше параметра
Max Spread.
- Значение ADX выше параметра
- Размещение ордеров:
- Отложенные стоп-ордера ставятся на расстоянии
Pipsшагов цены от текущих bid или ask. - Одновременно активен только один ордер; предыдущие отменяются при появлении нового сигнала.
- Отложенные стоп-ордера ставятся на расстоянии
- Условия выхода:
- Длинные позиции закрываются, когда
-DIподнимается выше+DI. - Короткие позиции закрываются, когда
+DIподнимается выше-DI.
- Длинные позиции закрываются, когда
- Стопы:
- Стоп-лосс и тейк-профит задаются через
StartProtectionпараметрамиStop LossиTake Profit.
- Стоп-лосс и тейк-профит задаются через
- Параметры по умолчанию:
ADX Period= 14ADX Threshold= 5Pips= 10 шагов ценыTake Profit= 1000 шагов ценыStop Loss= 500 шагов ценыMax Spread= 20 шагов ценыCandle Type= свечи 15 минут
- Фильтры:
- Категория: Следование за трендом
- Направление: Обе стороны
- Индикаторы: ADX, DMI
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Фильтр по спреду: Да
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 DMI crossovers.
/// </summary>
public class AdxStopOrderTemplateStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _adxSignal;
private readonly StrategyParam<int> _pips;
private readonly StrategyParam<int> _takeProfit;
private readonly StrategyParam<int> _stopLoss;
private readonly StrategyParam<decimal> _maxSpread;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _minDiSpread;
private readonly StrategyParam<int> _cooldownBars;
private decimal? _prevPlus;
private decimal? _prevMinus;
private int _cooldownRemaining;
/// <summary>
/// Initializes a new instance of the <see cref="AdxStopOrderTemplateStrategy"/> class.
/// </summary>
public AdxStopOrderTemplateStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Calculation period for ADX and DMI.", "Indicators");
_adxSignal = Param(nameof(AdxSignal), 20m)
.SetGreaterThanZero()
.SetDisplay("ADX Threshold", "Minimum ADX value to allow entries.", "Indicators");
_pips = Param(nameof(Pips), 10)
.SetGreaterThanZero()
.SetDisplay("Pending Offset", "Distance in price steps for stop orders.", "Orders");
_takeProfit = Param(nameof(TakeProfit), 1000)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take profit size in price steps.", "Risk");
_stopLoss = Param(nameof(StopLoss), 500)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Stop loss size in price steps.", "Risk");
_maxSpread = Param(nameof(MaxSpread), 20m)
.SetGreaterThanZero()
.SetDisplay("Max Spread", "Maximum allowed spread in price steps.", "Orders");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for analysis.", "General");
_minDiSpread = Param(nameof(MinDiSpread), 5m)
.SetDisplay("DI Spread", "Minimum spread between DI+ and DI-.", "Filters");
_cooldownBars = Param(nameof(CooldownBars), 6)
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change.", "Trading");
}
#region Parameters
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
public decimal AdxSignal
{
get => _adxSignal.Value;
set => _adxSignal.Value = value;
}
public int Pips
{
get => _pips.Value;
set => _pips.Value = value;
}
public int TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
public int StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
public decimal MaxSpread
{
get => _maxSpread.Value;
set => _maxSpread.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal MinDiSpread
{
get => _minDiSpread.Value;
set => _minDiSpread.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
#endregion
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevPlus = null;
_prevMinus = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var dmi = new DirectionalIndex { Length = AdxPeriod };
var adx = new AverageDirectionalIndex { Length = AdxPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(dmi, adx, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, adx);
DrawIndicator(area, dmi);
DrawOwnTrades(area);
}
var step = Security.PriceStep ?? 1m;
StartProtection(
new Unit(TakeProfit * step, UnitTypes.Absolute),
new Unit(StopLoss * step, UnitTypes.Absolute));
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue dmiValue, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished || !dmiValue.IsFinal || !adxValue.IsFinal)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var dmiTyped = (DirectionalIndexValue)dmiValue;
if (dmiTyped.Plus is not decimal diPlus || dmiTyped.Minus is not decimal diMinus)
return;
var adxTyped = (AverageDirectionalIndexValue)adxValue;
if (adxTyped.MovingAverage is not decimal adx)
return;
if (_prevPlus is not decimal prevPlus || _prevMinus is not decimal prevMinus)
{
_prevPlus = diPlus;
_prevMinus = diMinus;
return;
}
var crossUp = prevPlus <= prevMinus && diPlus > diMinus;
var crossDown = prevPlus >= prevMinus && diPlus < diMinus;
var diSpread = Math.Abs(diPlus - diMinus);
if (_cooldownRemaining == 0 && adx >= AdxSignal && diSpread >= MinDiSpread)
{
if (crossUp && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownBars;
}
else if (crossDown && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
else if (Position > 0 && crossDown)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
else if (Position < 0 && crossUp)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
_prevPlus = diPlus;
_prevMinus = diMinus;
}
}
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 DirectionalIndex, AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
class adx_stop_order_template_strategy(Strategy):
def __init__(self):
super(adx_stop_order_template_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14) \
.SetDisplay("ADX Period", "Calculation period for ADX and DMI.", "Indicators")
self._adx_signal = self.Param("AdxSignal", 20.0) \
.SetDisplay("ADX Threshold", "Minimum ADX value to allow entries.", "Indicators")
self._pips = self.Param("Pips", 10) \
.SetDisplay("Pending Offset", "Distance in price steps for stop orders.", "Orders")
self._take_profit = self.Param("TakeProfit", 1000) \
.SetDisplay("Take Profit", "Take profit size in price steps.", "Risk")
self._stop_loss = self.Param("StopLoss", 500) \
.SetDisplay("Stop Loss", "Stop loss size in price steps.", "Risk")
self._max_spread = self.Param("MaxSpread", 20.0) \
.SetDisplay("Max Spread", "Maximum allowed spread in price steps.", "Orders")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame for analysis.", "General")
self._min_di_spread = self.Param("MinDiSpread", 5.0) \
.SetDisplay("DI Spread", "Minimum spread between DI+ and DI-.", "Filters")
self._cooldown_bars = self.Param("CooldownBars", 6) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change.", "Trading")
self._prev_plus = None
self._prev_minus = None
self._cooldown_remaining = 0
@property
def adx_period(self):
return self._adx_period.Value
@property
def adx_signal(self):
return self._adx_signal.Value
@property
def pips(self):
return self._pips.Value
@property
def take_profit(self):
return self._take_profit.Value
@property
def stop_loss(self):
return self._stop_loss.Value
@property
def max_spread(self):
return self._max_spread.Value
@property
def candle_type(self):
return self._candle_type.Value
@property
def min_di_spread(self):
return self._min_di_spread.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
def OnReseted(self):
super(adx_stop_order_template_strategy, self).OnReseted()
self._prev_plus = None
self._prev_minus = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(adx_stop_order_template_strategy, self).OnStarted2(time)
dmi = DirectionalIndex()
dmi.Length = self.adx_period
adx = AverageDirectionalIndex()
adx.Length = self.adx_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(dmi, adx, self.process_candle).Start()
step = self.Security.PriceStep if self.Security.PriceStep is not None else 1.0
step = float(step)
self.StartProtection(
Unit(self.take_profit * step, UnitTypes.Absolute),
Unit(self.stop_loss * step, UnitTypes.Absolute))
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, adx)
self.DrawIndicator(area, dmi)
self.DrawOwnTrades(area)
def process_candle(self, candle, dmi_value, adx_value):
if candle.State != CandleStates.Finished or not dmi_value.IsFinal or not adx_value.IsFinal:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
di_plus = dmi_value.Plus
di_minus = dmi_value.Minus
if di_plus is None or di_minus is None:
return
di_plus = float(di_plus)
di_minus = float(di_minus)
adx_ma = adx_value.MovingAverage
if adx_ma is None:
return
adx_val = float(adx_ma)
if self._prev_plus is None or self._prev_minus is None:
self._prev_plus = di_plus
self._prev_minus = di_minus
return
cross_up = self._prev_plus <= self._prev_minus and di_plus > di_minus
cross_down = self._prev_plus >= self._prev_minus and di_plus < di_minus
di_spread = abs(di_plus - di_minus)
adx_threshold = float(self.adx_signal)
min_di = float(self.min_di_spread)
if self._cooldown_remaining == 0 and adx_val >= adx_threshold and di_spread >= min_di:
if cross_up and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
elif cross_down and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
elif self.Position > 0 and cross_down:
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
elif self.Position < 0 and cross_up:
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
self._prev_plus = di_plus
self._prev_minus = di_minus
def CreateClone(self):
return adx_stop_order_template_strategy()