ADX挂单模板策略
该策略演示如何利用平均趋向指数(ADX)及其方向性指标来放置止损挂单。当市场趋势足够强并且+DI与-DI交叉时,系统会在固定距离处设置买入或卖出止损单,并自动处理止损和止盈。
此示例主要用于教学,侧重于订单处理。交易者可以在此基础上加入更多过滤器或资金管理规则,构建更复杂的交易系统。
细节
- 入场条件:
- ADX 大于参数
ADX Threshold。 - 做多:
+DI大于-DI,且两根K线前+DI低于-DI。 - 做空:
+DI小于-DI,且两根K线前+DI高于-DI。 - 当前点差小于
Max Spread。
- ADX 大于参数
- 挂单设置:
- 止损挂单距离当前买价或卖价
Pips个价格步长。 - 同时只保留一个挂单;出现新信号时取消旧挂单。
- 止损挂单距离当前买价或卖价
- 出场条件:
- 多头:当
-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分钟K线
- 过滤器:
- 类别:趋势跟随
- 方向:双向
- 指标: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()