ADX 突破策略
该策略监控 ADX 指标的迅速上升。当读数明显超出常态时,价格往往开始新的趋势。
测试表明年均收益约为 97%,该策略在加密市场表现最佳。
当指标突破由近期数据和偏差倍数形成的通道时开仓,可做多亦可做空,并设置止损。ADX 回到均值附近即平仓。
适合动量交易者捕捉早期突破机会。
详细信息
- 入场条件: Indicator exceeds average by deviation multiplier.
- Long/Short: 双向 directions.
- 退出条件: Indicator reverts to average.
- 止损: 是
- 默认值:
ADXPeriod= 14AvgPeriod= 20Multiplier= 0.1CandleType= TimeSpan.FromMinutes(5)StopLossPercent= 2 Avg和StdDev为最近AvgPeriod个ADX值(含当前值)的平均值和标准差。止损为入场价的固定StopLossPercent百分比,在K线之间同样监控;设为0则关闭。通道为平均值加Multiplier倍标准差:ADX在阳线上突破通道时做多,在阴线上突破时做空;ADX回落到平均值以下时平仓。与持仓方向相反的入场信号会反转持仓。
- 筛选条件:
- 类别: 突破
- 方向: 双向
- 指标: ADX
- 止损: 是
- 复杂度: 中等
- 时间框架: 短期
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// ADX Breakout strategy.
/// The bands lie Multiplier standard deviations around the average of the last AvgPeriod ADX values, the current one included.
/// ADX above the upper band on a rising candle goes long and on a falling candle goes short, reversing an opposite position.
/// A position closes once ADX falls back below its average, and a percent stop limits the loss.
/// </summary>
public class ADXBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _aDXPeriod;
private readonly StrategyParam<int> _avgPeriod;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _values = [];
/// <summary>
/// Period of ADX.
/// </summary>
public int ADXPeriod
{
get => _aDXPeriod.Value;
set => _aDXPeriod.Value = value;
}
/// <summary>
/// Values of ADX the average and the standard deviation span.
/// </summary>
public int AvgPeriod
{
get => _avgPeriod.Value;
set => _avgPeriod.Value = value;
}
/// <summary>
/// Standard deviations between the average and a band.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ADXBreakoutStrategy()
{
_aDXPeriod = Param(nameof(ADXPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Period of ADX", "Indicators");
_avgPeriod = Param(nameof(AvgPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Average Period", "Values of ADX the average and the standard deviation span", "Indicators");
_multiplier = Param(nameof(Multiplier), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Multiplier", "Standard deviations between the average and a band", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
_values.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_values.Clear();
var adx = new AverageDirectionalIndex { Length = ADXPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, adx);
}
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue adxValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!adxValue.IsFormed || adxValue is not AverageDirectionalIndexValue { MovingAverage: decimal value })
return;
_values.Enqueue(value);
if (_values.Count > AvgPeriod)
_values.Dequeue();
if (_values.Count < AvgPeriod)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var mean = _values.Average();
var deviation = (decimal)Math.Sqrt((double)_values.Average(v => (v - mean) * (v - mean)));
var upper = mean + Multiplier * deviation;
if (value > upper && candle.ClosePrice > candle.OpenPrice && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (value > upper && candle.ClosePrice < candle.OpenPrice && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && value < mean)
SellMarket(Position);
else if (Position < 0 && value < mean)
BuyMarket(-Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import AverageDirectionalIndex
from StockSharp.Algo.Strategies import Strategy
class adx_breakout_strategy(Strategy):
"""
ADX Breakout strategy.
The bands lie Multiplier standard deviations around the average of the last AvgPeriod ADX values, the current one included.
ADX above the upper band on a rising candle goes long and on a falling candle goes short, reversing an opposite position.
A position closes once ADX falls back below its average, and a percent stop limits the loss.
"""
def __init__(self):
super(adx_breakout_strategy, self).__init__()
self._a_d_x_period = self.Param("ADXPeriod", 14).SetGreaterThanZero().SetDisplay("ADX Period", "Period of ADX", "Indicators")
self._avg_period = self.Param("AvgPeriod", 20).SetGreaterThanZero().SetDisplay("Average Period", "Values of ADX the average and the standard deviation span", "Indicators")
self._multiplier = self.Param("Multiplier", 0.1).SetGreaterThanZero().SetDisplay("Multiplier", "Standard deviations between the average and a band", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._values = []
def OnReseted(self):
super(adx_breakout_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(adx_breakout_strategy, self).OnStarted2(time)
self._reset_state()
adx = AverageDirectionalIndex()
adx.Length = self._a_d_x_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, adx)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, adx_value):
if candle.State != CandleStates.Finished:
return
if not adx_value.IsFormed or adx_value.MovingAverage is None:
return
value = adx_value.MovingAverage
period = self._avg_period.Value
self._values.append(value)
if len(self._values) > period:
self._values.pop(0)
if len(self._values) < period:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
total = Decimal(0)
for item in self._values:
total += item
mean = total / Decimal(period)
squares = Decimal(0)
for item in self._values:
squares += (item - mean) * (item - mean)
deviation = Decimal(Math.Sqrt(Decimal.ToDouble(squares / Decimal(period))))
multiplier = Decimal(self._multiplier.Value)
upper = mean + multiplier * deviation
if value > upper and candle.ClosePrice > candle.OpenPrice and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif value > upper and candle.ClosePrice < candle.OpenPrice and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and value < mean:
self.SellMarket(self.Position)
elif self.Position < 0 and value < mean:
self.BuyMarket(-self.Position)
def CreateClone(self):
return adx_breakout_strategy()