Стратегия возврата к среднему с учётом волатильности
В этой версии возвращения к среднему пороги входа масштабируются по отношению ATR к стандартному отклонению. Когда волатильность возрастает относительно обычного шума, расстояние, необходимое для открытия сделки, увеличивается, что помогает избежать преждевременных сигналов во время хаотичных движений.
Тестирование показывает среднегодичную доходность около 115%. Стратегию лучше запускать на фондовом рынке.
Длинная позиция открывается, когда цена опускается ниже скользящей средней более чем на скорректированный порог. Короткая позиция открывается, когда цена поднимается выше средней на ту же величину. Позиции закрываются, когда цена вновь приближается к средней.
Адаптивный порог делает стратегию подходящей для рынков с меняющимися режимами волатильности. Стоп‑лосс, равный двум ATR, ограничивает риск в ожидании возврата.
Подробности
- Условия входа:
- Long: закрытие < MA − Multiplier * ATR / (ATR/StdDev)
- Short: закрытие > MA + Multiplier * ATR / (ATR/StdDev)
- Long/Short: обе стороны.
- Условия выхода:
- Long: выход при закрытии >= MA
- Short: выход при закрытии <= MA
- Стопы: да, динамический на основе ATR.
- Параметры по умолчанию:
Period= 20Multiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- Фильтры:
- Категория: Возврат к среднему
- Направление: Обе стороны
- Индикаторы: ATR, StdDev
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Volatility Adjusted Mean Reversion strategy.
/// Uses ATR and Standard Deviation to create adaptive entry thresholds.
/// </summary>
public class VolatilityAdjustedMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _periodParam;
private readonly StrategyParam<decimal> _multiplierParam;
private readonly StrategyParam<DataType> _candleTypeParam;
private SimpleMovingAverage _sma;
private AverageTrueRange _atr;
private StandardDeviation _stdDev;
/// <summary>
/// Period for indicators.
/// </summary>
public int Period
{
get => _periodParam.Value;
set => _periodParam.Value = value;
}
/// <summary>
/// Multiplier for entry threshold.
/// </summary>
public decimal Multiplier
{
get => _multiplierParam.Value;
set => _multiplierParam.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public VolatilityAdjustedMeanReversionStrategy()
{
_periodParam = Param(nameof(Period), 20)
.SetGreaterThanZero()
.SetDisplay("Period", "Period for indicators", "Parameters")
.SetOptimize(10, 50, 10);
_multiplierParam = Param(nameof(Multiplier), 2.0m)
.SetRange(0.1m, decimal.MaxValue)
.SetDisplay("Multiplier", "Multiplier for entry threshold", "Parameters")
.SetOptimize(1.0m, 3.0m, 0.5m);
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle type for strategy", "Common");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sma = null;
_atr = null;
_stdDev = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
_sma = new SMA { Length = Period };
_atr = new AverageTrueRange { Length = Period };
_stdDev = new StandardDeviation { Length = Period };
// Create subscription and bind indicators
var subscription = SubscribeCandles(CandleType);
// First, bind SMA and ATR
subscription
.Bind(_sma, _atr, _stdDev, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _sma);
DrawIndicator(area, _atr);
DrawOwnTrades(area);
}
// Enable position protection
StartProtection(
takeProfit: new Unit(0, UnitTypes.Absolute), // No take profit
stopLoss: new Unit(2, UnitTypes.Absolute) // Stop loss at 2*ATR
);
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue, decimal atrValue, decimal stdDevValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Skip if standard deviation is too small to avoid division by zero
if (stdDevValue < 0.0001m)
return;
// Calculate volatility ratio
var volatilityRatio = atrValue / stdDevValue;
// Calculate volatility-adjusted thresholds
var threshold = Multiplier * atrValue / volatilityRatio;
var upperThreshold = smaValue + threshold;
var lowerThreshold = smaValue - threshold;
// Long setup - price below lower threshold
if (candle.ClosePrice < lowerThreshold && Position <= 0)
{
// Buy signal - price has deviated too much below average
BuyMarket(Volume + Math.Abs(Position));
}
// Short setup - price above upper threshold
else if (candle.ClosePrice > upperThreshold && Position >= 0)
{
// Sell signal - price has deviated too much above average
SellMarket(Volume + Math.Abs(Position));
}
// Exit long position when price returns to average
else if (Position > 0 && candle.ClosePrice >= smaValue)
{
// Close long position
SellMarket(Position);
}
// Exit short position when price returns to average
else if (Position < 0 && candle.ClosePrice <= smaValue)
{
// Close short position
BuyMarket(Math.Abs(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, Math
from StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange, StandardDeviation
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class volatility_adjusted_mean_reversion_strategy(Strategy):
"""
Volatility Adjusted Mean Reversion strategy.
Uses ATR and Standard Deviation to create adaptive entry thresholds.
"""
def __init__(self):
super(volatility_adjusted_mean_reversion_strategy, self).__init__()
# Period for indicators.
self._period = self.Param("Period", 20) \
.SetGreaterThanZero() \
.SetDisplay("Period", "Period for indicators", "Parameters") \
.SetCanOptimize(True) \
.SetOptimize(10, 50, 10)
# Multiplier for entry threshold.
self._multiplier = self.Param("Multiplier", 2.0) \
.SetRange(0.1, 1e6) \
.SetDisplay("Multiplier", "Multiplier for entry threshold", "Parameters") \
.SetCanOptimize(True) \
.SetOptimize(1.0, 3.0, 0.5)
# Candle type for strategy.
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Candle type for strategy", "Common")
# Internal indicators
self._sma = None
self._atr = None
self._std_dev = None
@property
def period(self):
"""Period for indicators."""
return self._period.Value
@period.setter
def period(self, value):
self._period.Value = value
@property
def multiplier(self):
"""Multiplier for entry threshold."""
return self._multiplier.Value
@multiplier.setter
def multiplier(self, value):
self._multiplier.Value = value
@property
def candle_type(self):
"""Candle type for strategy."""
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(volatility_adjusted_mean_reversion_strategy, self).OnReseted()
self._sma = None
self._atr = None
self._std_dev = None
def OnStarted2(self, time):
super(volatility_adjusted_mean_reversion_strategy, self).OnStarted2(time)
# Create indicators
self._sma = SimpleMovingAverage()
self._sma.Length = self.period
self._atr = AverageTrueRange()
self._atr.Length = self.period
self._std_dev = StandardDeviation()
self._std_dev.Length = self.period
# Create subscription and bind indicators
subscription = self.SubscribeCandles(self.candle_type)
# First, bind SMA and ATR
subscription.Bind(self._sma, self._atr, self._std_dev, self.ProcessCandle).Start()
# Setup chart visualization if available
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._sma)
self.DrawIndicator(area, self._atr)
self.DrawOwnTrades(area)
# Enable position protection
self.StartProtection(
takeProfit=Unit(0, UnitTypes.Absolute),
stopLoss=Unit(2, UnitTypes.Absolute)
)
def ProcessCandle(self, candle, sma_value, atr_value, std_dev_value):
if candle.State != CandleStates.Finished:
return
# Skip if standard deviation is too small to avoid division by zero
if std_dev_value < 0.0001:
return
# Calculate volatility ratio
volatility_ratio = atr_value / std_dev_value
# Calculate volatility-adjusted thresholds
threshold = self.multiplier * atr_value / volatility_ratio
upper_threshold = sma_value + threshold
lower_threshold = sma_value - threshold
# Long setup - price below lower threshold
if candle.ClosePrice < lower_threshold and self.Position <= 0:
# Buy signal - price has deviated too much below average
self.BuyMarket(self.Volume + Math.Abs(self.Position))
# Short setup - price above upper threshold
elif candle.ClosePrice > upper_threshold and self.Position >= 0:
# Sell signal - price has deviated too much above average
self.SellMarket(self.Volume + Math.Abs(self.Position))
# Exit long position when price returns to average
elif self.Position > 0 and candle.ClosePrice >= sma_value:
# Close long position
self.SellMarket(self.Position)
# Exit short position when price returns to average
elif self.Position < 0 and candle.ClosePrice <= sma_value:
# Close short position
self.BuyMarket(Math.Abs(self.Position))
def CreateClone(self):
"""
!! REQUIRED!! Creates a new instance of the strategy.
"""
return volatility_adjusted_mean_reversion_strategy()