Стратегия возврата к среднему с учётом волатильности
В этой версии возвращения к среднему пороги входа масштабируются по отношению ATR к стандартному отклонению. Когда волатильность возрастает относительно обычного шума, расстояние, необходимое для открытия сделки, увеличивается, что помогает избежать преждевременных сигналов во время хаотичных движений.
Тестирование показывает среднегодичную доходность около 115%. Стратегию лучше запускать на фондовом рынке.
Длинная позиция открывается, когда цена опускается ниже скользящей средней более чем на скорректированный порог. Короткая позиция открывается, когда цена поднимается выше средней на ту же величину. Позиции закрываются, когда цена вновь приближается к средней.
Адаптивный порог делает стратегию подходящей для рынков с меняющимися режимами волатильности. Стоп‑лосс, равный двум ATR, ограничивает риск в ожидании возврата.
Подробности
Условия входа:
Long: закрытие < MA − Multiplier * ATR / (ATR/StdDev)
Short: закрытие > MA + Multiplier * ATR / (ATR/StdDev)
Long/Short: обе стороны.
Условия выхода:
Long: выход при закрытии >= MA
Short: выход при закрытии <= MA
Стопы: да, динамический на основе ATR.
Параметры по умолчанию:
Period = 20
Multiplier = 2
MA, ATR и StdDev (стандартное отклонение цен закрытия) считаются за Period свечей; порог сводится к Multiplier × StdDev. Стоп ставится на расстоянии Multiplier ATR от цены закрытия входа, по умолчанию два ATR, и проверяется по закрытию свечей. Сигнал на вход против открытой позиции разворачивает её.
CandleType = TimeSpan.FromMinutes(5)
Фильтры:
Категория: Возврат к среднему
Направление: Обе стороны
Индикаторы: ATR, StdDev
Стопы: Да
Сложность: Средняя
Таймфрейм: Внутридневной
Сезонность: Нет
Нейросети: Нет
Дивергенция: Нет
Уровень риска: Средний
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>
/// Volatility Adjusted Mean Reversion strategy.
/// The threshold is Multiplier times the ATR divided by the ATR to standard deviation ratio, all over Period candles. A close more than the
/// threshold below the Period simple moving average goes long and one that far above it goes short, reversing an opposite position.
/// A long closes once the close is back at or above the average and a short once it is back at or below it. The stop lies Multiplier ATR
/// from the entry close and is checked on candle closes.
/// </summary>
public class VolatilityAdjustedMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _period;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal _stopPrice;
/// <summary>
/// Period of the SMA, ATR and standard deviation.
/// </summary>
public int Period
{
get => _period.Value;
set => _period.Value = value;
}
/// <summary>
/// Multiplier of the threshold and the stop.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public VolatilityAdjustedMeanReversionStrategy()
{
_period = Param(nameof(Period), 20)
.SetGreaterThanZero()
.SetDisplay("Period", "Period of the SMA, ATR and standard deviation", "Parameters");
_multiplier = Param(nameof(Multiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Multiplier", "Multiplier of the threshold and the stop", "Parameters");
_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();
_stopPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_stopPrice = default;
var sma = new SimpleMovingAverage { Length = Period };
var atr = new AverageTrueRange { Length = Period };
var stdev = new StandardDeviation { Length = Period };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(sma, atr, stdev, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, atr);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue smaValue, IIndicatorValue atrValue, IIndicatorValue stdevValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!smaValue.IsFormed || !atrValue.IsFormed || !stdevValue.IsFormed)
return;
var atr = atrValue.GetValue<decimal>();
var deviation = stdevValue.GetValue<decimal>();
if (atr <= 0 || deviation <= 0)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var sma = smaValue.GetValue<decimal>();
var threshold = Multiplier * atr / (atr / deviation);
var close = candle.ClosePrice;
if (close < sma - threshold && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - Multiplier * atr;
}
else if (close > sma + threshold && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + Multiplier * atr;
}
else if (Position > 0 && (close >= sma || close <= _stopPrice))
{
SellMarket(Position);
}
else if (Position < 0 && (close <= sma || close >= _stopPrice))
{
BuyMarket(-Position);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
class volatility_adjusted_mean_reversion_strategy(Strategy):
"""
Volatility Adjusted Mean Reversion strategy.
The threshold is Multiplier times the ATR divided by the ATR to standard deviation ratio, all over Period candles. A close more than the
threshold below the Period simple moving average goes long and one that far above it goes short, reversing an opposite position.
A long closes once the close is back at or above the average and a short once it is back at or below it. The stop lies Multiplier ATR
from the entry close and is checked on candle closes.
"""
def __init__(self):
super(volatility_adjusted_mean_reversion_strategy, self).__init__()
self._period = self.Param("Period", 20).SetGreaterThanZero().SetDisplay("Period", "Period of the SMA, ATR and standard deviation", "Parameters")
self._multiplier = self.Param("Multiplier", 2.0).SetGreaterThanZero().SetDisplay("Multiplier", "Multiplier of the threshold and the stop", "Parameters")
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._stop_price = Decimal(0)
def OnReseted(self):
super(volatility_adjusted_mean_reversion_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(volatility_adjusted_mean_reversion_strategy, self).OnStarted2(time)
self._reset_state()
sma = SimpleMovingAverage()
sma.Length = self._period.Value
atr = AverageTrueRange()
atr.Length = self._period.Value
stdev = StandardDeviation()
stdev.Length = self._period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(sma, atr, stdev, self._process_candle).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, atr)
def _process_candle(self, candle, sma_value, atr_value, stdev_value):
if candle.State != CandleStates.Finished:
return
if not sma_value.IsFormed or not atr_value.IsFormed or not stdev_value.IsFormed:
return
atr = atr_value.GetValue[Decimal](None)
deviation = stdev_value.GetValue[Decimal](None)
if atr <= 0 or deviation <= 0:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
multiplier = Decimal(self._multiplier.Value)
sma = sma_value.GetValue[Decimal](None)
threshold = multiplier * atr / (atr / deviation)
close = candle.ClosePrice
if close < sma - threshold and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - multiplier * atr
elif close > sma + threshold and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + multiplier * atr
elif self.Position > 0 and (close >= sma or close <= self._stop_price):
self.SellMarket(self.Position)
elif self.Position < 0 and (close <= sma or close >= self._stop_price):
self.BuyMarket(-self.Position)
def CreateClone(self):
return volatility_adjusted_mean_reversion_strategy()