ATR Slope Mean Reversion 策略
ATR Slope Mean Reversion 策略关注指标的极端读数以捕捉均值回归。远离正常水平的情况通常不会持续太久。
当指标大幅偏离均值后开始反转时产生交易信号,可做多也可做空,并带有保护性止损。
适合预期震荡行情的交易者,当指标回归平衡时平仓。初始参数 AtrPeriod = 14.
详细信息
- 入场条件: Indicator crosses back toward mean.
- 多空: Both directions.
- 出场条件: Indicator reverts to average.
- 止损: Yes.
- 默认值:
AtrPeriod= 14LookbackPeriod= 20DeviationMultiplier= 2.0mStopLossMultiplier= 2CandleType= TimeSpan.FromMinutes(5)
- 过滤器:
- 分类: Mean Reversion
- 方向: Both
- 指标: ATR
- 止损: Yes
- 复杂度: Intermediate
- 时间框架: Short-term
- 季节性: No
- 神经网络: No
- 背离: No
- 风险级别: Medium
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>
/// ATR slope mean reversion.
/// Buys when the ATR slope is far below its average and starts turning up, sells when it is far above
/// and starts turning down. Exits when the slope returns to its average or the ATR stop is hit.
/// </summary>
public class AtrSlopeMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<decimal> _deviationMultiplier;
private readonly StrategyParam<int> _stopLossMultiplier;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _slopeAverage;
private StandardDeviation _slopeStdDev;
private decimal? _prevAtr;
private decimal? _prevSlope;
private decimal _stopPrice;
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Lookback period for slope statistics.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for extreme slope.
/// </summary>
public decimal DeviationMultiplier
{
get => _deviationMultiplier.Value;
set => _deviationMultiplier.Value = value;
}
/// <summary>
/// Stop-loss distance in ATR multiples.
/// </summary>
public int StopLossMultiplier
{
get => _stopLossMultiplier.Value;
set => _stopLossMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="AtrSlopeMeanReversionStrategy"/>.
/// </summary>
public AtrSlopeMeanReversionStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of ATR", "Indicators");
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Period for slope statistics", "Strategy");
_deviationMultiplier = Param(nameof(DeviationMultiplier), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Deviation Multiplier", "Standard deviation multiplier for extreme slope", "Strategy");
_stopLossMultiplier = Param(nameof(StopLossMultiplier), 2)
.SetNotNegative()
.SetDisplay("Stop Loss Multiplier", "Stop-loss distance in ATR multiples", "Risk Management");
_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();
_slopeAverage = null;
_slopeStdDev = null;
_prevAtr = null;
_prevSlope = null;
_stopPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrPeriod };
_slopeAverage = new SimpleMovingAverage { Length = LookbackPeriod };
_slopeStdDev = new StandardDeviation { Length = LookbackPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var atrArea = CreateChartArea();
if (atrArea != null)
DrawIndicator(atrArea, atr);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevAtr is not decimal prevAtr)
{
_prevAtr = atrValue;
return;
}
_prevAtr = atrValue;
var slope = atrValue - prevAtr;
var avgSlope = _slopeAverage.Process(slope, candle.ServerTime, true).ToDecimal();
var stdSlope = _slopeStdDev.Process(slope, candle.ServerTime, true).ToDecimal();
var prevSlope = _prevSlope;
_prevSlope = slope;
if (!_slopeAverage.IsFormed || !_slopeStdDev.IsFormed || prevSlope is not decimal prev)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (CheckStop(candle))
return;
var close = candle.ClosePrice;
var stopDistance = StopLossMultiplier * atrValue;
// Extreme reading that has started to turn back toward the average.
if (slope < avgSlope - DeviationMultiplier * stdSlope && slope > prev && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = stopDistance > 0 ? close - stopDistance : 0m;
}
else if (slope > avgSlope + DeviationMultiplier * stdSlope && slope < prev && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = stopDistance > 0 ? close + stopDistance : 0m;
}
else if ((Position > 0 && slope >= avgSlope) || (Position < 0 && slope <= avgSlope))
{
ExitPosition();
}
}
private bool CheckStop(ICandleMessage candle)
{
if (_stopPrice == 0m)
return false;
if ((Position > 0 && candle.LowPrice <= _stopPrice) || (Position < 0 && candle.HighPrice >= _stopPrice))
{
ExitPosition();
return true;
}
return false;
}
private void ExitPosition()
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
_stopPrice = 0m;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange, SimpleMovingAverage, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class atr_slope_mean_reversion_strategy(Strategy):
"""
ATR slope mean reversion.
Buys when the ATR slope is far below its average and starts turning up, sells when it is far above
and starts turning down. Exits when the slope returns to its average or the ATR stop is hit.
"""
def __init__(self):
super(atr_slope_mean_reversion_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Period", "Period of ATR", "Indicators")
self._lookback_period = self.Param("LookbackPeriod", 20) \
.SetGreaterThanZero() \
.SetDisplay("Lookback Period", "Period for slope statistics", "Strategy")
self._deviation_multiplier = self.Param("DeviationMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Deviation Multiplier", "Standard deviation multiplier for extreme slope", "Strategy")
self._stop_loss_multiplier = self.Param("StopLossMultiplier", 2) \
.SetNotNegative() \
.SetDisplay("Stop Loss Multiplier", "Stop-loss distance in ATR multiples", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._slope_average = None
self._slope_std_dev = None
self._prev_atr = None
self._prev_slope = None
self._stop_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(atr_slope_mean_reversion_strategy, self).OnReseted()
self._slope_average = None
self._slope_std_dev = None
self._prev_atr = None
self._prev_slope = None
self._stop_price = 0.0
def OnStarted2(self, time):
super(atr_slope_mean_reversion_strategy, self).OnStarted2(time)
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
self._slope_average = SimpleMovingAverage()
self._slope_average.Length = self._lookback_period.Value
self._slope_std_dev = StandardDeviation()
self._slope_std_dev.Length = self._lookback_period.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
atr_area = self.CreateChartArea()
if atr_area is not None:
self.DrawIndicator(atr_area, atr)
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
atr_value = float(atr_value)
if self._prev_atr is None:
self._prev_atr = atr_value
return
slope = atr_value - self._prev_atr
self._prev_atr = atr_value
avg_slope = float(process_float(self._slope_average, slope, candle.ServerTime, True))
std_slope = float(process_float(self._slope_std_dev, slope, candle.ServerTime, True))
prev = self._prev_slope
self._prev_slope = slope
if not self._slope_average.IsFormed or not self._slope_std_dev.IsFormed or prev is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._check_stop(candle):
return
close = float(candle.ClosePrice)
k = float(self._deviation_multiplier.Value)
stop_distance = self._stop_loss_multiplier.Value * atr_value
# Extreme reading that has started to turn back toward the average.
if slope < avg_slope - k * std_slope and slope > prev and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_distance if stop_distance > 0 else 0.0
elif slope > avg_slope + k * std_slope and slope < prev and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + stop_distance if stop_distance > 0 else 0.0
elif (self.Position > 0 and slope >= avg_slope) or (self.Position < 0 and slope <= avg_slope):
self._exit_position()
def _check_stop(self, candle):
if self._stop_price == 0.0:
return False
if (self.Position > 0 and float(candle.LowPrice) <= self._stop_price) or \
(self.Position < 0 and float(candle.HighPrice) >= self._stop_price):
self._exit_position()
return True
return False
def _exit_position(self):
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
self._stop_price = 0.0
def CreateClone(self):
return atr_slope_mean_reversion_strategy()