ADX 均值回归策略
平均趋向指数(ADX)用于衡量市场趋势力度。当 ADX 值处于低位时,市场倾向于围绕均值震荡。本策略交易 ADX 相对其均值的偏离。
测试表明年均收益约为 70%,该策略在股票市场表现最佳。
当 ADX 低于平均值减 DeviationMultiplier 倍标准差且价格位于均线下方时做多;当 ADX 高于上轨并且价格在均线上方时做空。ADX 回归均值附近后平仓。
此系统适合在弱趋势环境中寻找机会的交易者,若出现新的趋势则由止损限制亏损。
详细信息
- 入场条件:
- 做多: ADX < Avg - DeviationMultiplier * StdDev && Close < MA
- 做空: ADX > Avg + DeviationMultiplier * StdDev && Close > MA
- 多空方向: 双向
- 退出条件:
- 做多: Exit when ADX > Avg
- 做空: Exit when ADX < Avg
- 止损: 是
- 默认值:
AdxPeriod= 14AveragePeriod= 20DeviationMultiplier= 2StopLossPercent= 2CandleType= TimeSpan.FromMinutes(5) Avg和StdDev为最近AveragePeriod个ADX值(含当前值)的平均值和标准差,MA为最近AveragePeriod个收盘价的简单移动平均。止损为入场价的固定StopLossPercent百分比,在K线之间同样监控;设为0则关闭。与持仓方向相反的入场信号会反转持仓。
- 筛选条件:
- 类别: 均值回归
- 方向: 双向
- 指标: 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 Mean Reversion strategy.
/// The bands lie DeviationMultiplier standard deviations around the average of the last AveragePeriod ADX values, the current one included.
/// ADX below the lower band with the close below the AveragePeriod simple moving average goes long and ADX above the upper band with the close above it goes short,
/// reversing an opposite position. A long closes once ADX is back above its average and a short once it is back below it, and a percent stop limits the loss.
/// </summary>
public class AdxMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<int> _averagePeriod;
private readonly StrategyParam<decimal> _deviationMultiplier;
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 AveragePeriod
{
get => _averagePeriod.Value;
set => _averagePeriod.Value = value;
}
/// <summary>
/// Standard deviations between the average and a band.
/// </summary>
public decimal DeviationMultiplier
{
get => _deviationMultiplier.Value;
set => _deviationMultiplier.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 AdxMeanReversionStrategy()
{
_adxPeriod = Param(nameof(AdxPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ADX Period", "Period of ADX", "Indicators");
_averagePeriod = Param(nameof(AveragePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Average Period", "Values of ADX the average and the standard deviation span", "Indicators");
_deviationMultiplier = Param(nameof(DeviationMultiplier), 2m)
.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 sma = new SimpleMovingAverage { Length = AveragePeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(adx, sma, 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);
DrawIndicator(area, sma);
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, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!smaValue.IsFormed)
return;
if (!adxValue.IsFormed || adxValue is not AverageDirectionalIndexValue { MovingAverage: decimal value })
return;
_values.Enqueue(value);
if (_values.Count > AveragePeriod)
_values.Dequeue();
if (_values.Count < AveragePeriod)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var mean = _values.Average();
var deviation = (decimal)Math.Sqrt((double)_values.Average(v => (v - mean) * (v - mean)));
var upper = mean + DeviationMultiplier * deviation;
var lower = mean - DeviationMultiplier * deviation;
var close = candle.ClosePrice;
var ma = smaValue.GetValue<decimal>();
if (value < lower && close < ma && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (value > upper && close > ma && 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, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class adx_mean_reversion_strategy(Strategy):
"""
ADX Mean Reversion strategy.
The bands lie DeviationMultiplier standard deviations around the average of the last AveragePeriod ADX values, the current one included.
ADX below the lower band with the close below the AveragePeriod simple moving average goes long and ADX above the upper band with the close above it goes short,
reversing an opposite position. A long closes once ADX is back above its average and a short once it is back below it, and a percent stop limits the loss.
"""
def __init__(self):
super(adx_mean_reversion_strategy, self).__init__()
self._adx_period = self.Param("AdxPeriod", 14).SetGreaterThanZero().SetDisplay("ADX Period", "Period of ADX", "Indicators")
self._average_period = self.Param("AveragePeriod", 20).SetGreaterThanZero().SetDisplay("Average Period", "Values of ADX the average and the standard deviation span", "Indicators")
self._deviation_multiplier = self.Param("DeviationMultiplier", 2.0).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_mean_reversion_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(adx_mean_reversion_strategy, self).OnStarted2(time)
self._reset_state()
adx = AverageDirectionalIndex()
adx.Length = self._adx_period.Value
sma = SimpleMovingAverage()
sma.Length = self._average_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(adx, sma, 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.DrawIndicator(area, sma)
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, sma_value):
if candle.State != CandleStates.Finished:
return
if not sma_value.IsFormed:
return
if not adx_value.IsFormed or adx_value.MovingAverage is None:
return
value = adx_value.MovingAverage
period = self._average_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._deviation_multiplier.Value)
upper = mean + multiplier * deviation
lower = mean - multiplier * deviation
close = candle.ClosePrice
ma = sma_value.GetValue[Decimal](None)
if value < lower and close < ma and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif value > upper and close > ma 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_mean_reversion_strategy()