ボラティリティ平均回帰戦略
このアプローチは市場ボラティリティの変動を中心に取引します。ATRが移動平均から大きく乖離した場合、ボラティリティが異常に高くなったか低くなったことを示し、回帰する可能性があります。
テストでは平均年間リターンが約73%であることが示されています。暗号資産市場で最も良いパフォーマンスを発揮します。
ATRが平均値からDeviationMultiplier倍の標準偏差を引いた値を下回り、価格が移動平均を下回るときにロングエントリーします。ATRが上部バンドを超え、価格が平均を上回るときにショートエントリーします。ATRが平均水準に戻ったらポジションを決済します。
このようなセットアップは、価格方向ではなくボラティリティの極端な状態に逆張りするトレーダーに適しています。ボラティリティが拡大し続けた場合に備えて保護的なストップロスを使用します。
詳細
- エントリー条件:
- ロング: ATR < Avg - DeviationMultiplier * StdDev && Close < MA
- ショート: ATR > Avg + DeviationMultiplier * StdDev && Close > MA
- ロング/ショート: 両方。
- エグジット条件:
- ロング: ATR > Avg のときに決済
- ショート: ATR < Avg のときに決済
- ストップ: あり、パーセントストップロス。
- デフォルト値:
AtrPeriod= 14AveragePeriod= 20DeviationMultiplier= 2mCandleType= TimeSpan.FromMinutes(5)
- フィルター:
- カテゴリ: 平均回帰
- 方向: 両方
- インジケーター: ATR
- ストップ: あり
- 複雑さ: 中級
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
namespace StockSharp.Samples.Strategies
{
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Candles;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Volatility Mean Reversion strategy.
/// This strategy enters positions when ATR (volatility) is significantly below or above its average value.
/// </summary>
public class VolatilityMeanReversionStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _averagePeriod;
private readonly StrategyParam<decimal> _deviationMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private decimal _prevAtr;
private decimal _avgAtr;
private decimal _stdDevAtr;
private decimal _sumAtr;
private decimal _sumSquaresAtr;
private int _count;
private readonly Queue<decimal> _atrValues = [];
/// <summary>
/// ATR Period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Period for calculating mean and standard deviation of ATR.
/// </summary>
public int AveragePeriod
{
get => _averagePeriod.Value;
set => _averagePeriod.Value = value;
}
/// <summary>
/// Deviation multiplier for entry signals.
/// </summary>
public decimal DeviationMultiplier
{
get => _deviationMultiplier.Value;
set => _deviationMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public VolatilityMeanReversionStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetOptimize(10, 20, 5)
.SetDisplay("ATR Period", "Period for Average True Range indicator", "Indicators");
_averagePeriod = Param(nameof(AveragePeriod), 20)
.SetGreaterThanZero()
.SetOptimize(10, 50, 10)
.SetDisplay("Average Period", "Period for calculating ATR average and standard deviation", "Settings");
_deviationMultiplier = Param(nameof(DeviationMultiplier), 2m)
.SetGreaterThanZero()
.SetOptimize(1.5m, 3m, 0.5m)
.SetDisplay("Deviation Multiplier", "Multiplier for standard deviation", "Settings");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 1.0m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
.SetOptimize(0.5m, 2.0m, 0.5m);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevAtr = 0;
_avgAtr = 0;
_stdDevAtr = 0;
_sumAtr = 0;
_sumSquaresAtr = 0;
_count = 0;
_atrValues.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
// Create ATR indicator
var atr = new AverageTrueRange { Length = AtrPeriod };
// Create subscription and bind indicator
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(atr, ProcessCandle)
.Start();
// Setup chart visualization
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
StartProtection(
new(),
new Unit(StopLossPercent, UnitTypes.Percent),
useMarketOrders: true
);
base.OnStarted2(time);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Extract ATR value
var currentAtr = atrValue.ToDecimal();
// Update ATR statistics
UpdateAtrStatistics(currentAtr);
// If we don't have enough data yet for statistics
if (_count < AveragePeriod)
{
_prevAtr = currentAtr;
return;
}
// For volatility mean reversion, we need to use price action to determine direction
// We'll use simple momentum for direction (current price vs previous price)
var priceDirection = candle.ClosePrice > candle.OpenPrice ? Sides.Buy : Sides.Sell;
// Check for entry conditions
if (Position == 0)
{
// Low volatility expecting increase - possibly prepare for a breakout
if (currentAtr < _avgAtr - DeviationMultiplier * _stdDevAtr)
{
// In low volatility, follow the current short-term price direction
if (priceDirection == Sides.Buy)
{
BuyMarket(Volume);
LogInfo($"Long entry: ATR = {currentAtr}, Avg = {_avgAtr}, StdDev = {_stdDevAtr}, Price up");
}
else
{
SellMarket(Volume);
LogInfo($"Short entry: ATR = {currentAtr}, Avg = {_avgAtr}, StdDev = {_stdDevAtr}, Price down");
}
}
// High volatility expecting decrease - possibly looking for market exhaustion
else if (currentAtr > _avgAtr + DeviationMultiplier * _stdDevAtr)
{
// In high volatility, consider going against the short-term trend
// as excessive volatility often leads to reversals
if (priceDirection == Sides.Sell)
{
BuyMarket(Volume);
LogInfo($"Contrarian long entry: ATR = {currentAtr}, Avg = {_avgAtr}, StdDev = {_stdDevAtr}, High volatility");
}
else
{
SellMarket(Volume);
LogInfo($"Contrarian short entry: ATR = {currentAtr}, Avg = {_avgAtr}, StdDev = {_stdDevAtr}, High volatility");
}
}
}
// Check for exit conditions
else if (Position > 0) // Long position
{
if (currentAtr < _avgAtr && priceDirection == Sides.Sell)
{
ClosePosition();
LogInfo($"Long exit: ATR = {currentAtr}, Avg = {_avgAtr}, Price down");
}
}
else if (Position < 0) // Short position
{
if (currentAtr < _avgAtr && priceDirection == Sides.Buy)
{
ClosePosition();
LogInfo($"Short exit: ATR = {currentAtr}, Avg = {_avgAtr}, Price up");
}
}
// Save current ATR for next iteration
_prevAtr = currentAtr;
}
private void UpdateAtrStatistics(decimal currentAtr)
{
// Add current value to the queue
_atrValues.Enqueue(currentAtr);
_sumAtr += currentAtr;
_sumSquaresAtr += currentAtr * currentAtr;
_count++;
// If queue is larger than period, remove oldest value
if (_atrValues.Count > AveragePeriod)
{
var oldestAtr = _atrValues.Dequeue();
_sumAtr -= oldestAtr;
_sumSquaresAtr -= oldestAtr * oldestAtr;
_count--;
}
// Calculate average and standard deviation
if (_count > 0)
{
_avgAtr = _sumAtr / _count;
if (_count > 1)
{
var variance = (_sumSquaresAtr - (_sumAtr * _sumAtr) / _count) / (_count - 1);
_stdDevAtr = variance <= 0 ? 0 : (decimal)Math.Sqrt((double)variance);
}
else
{
_stdDevAtr = 0;
}
}
}
}
}
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 System.Collections.Generic import Queue
from StockSharp.Messages import DataType, Unit, UnitTypes, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class volatility_mean_reversion_strategy(Strategy):
"""
Volatility Mean Reversion strategy.
This strategy enters positions when ATR (volatility) is significantly below or above its average value.
"""
def __init__(self):
super(volatility_mean_reversion_strategy, self).__init__()
# Initialize strategy parameters
self._atrPeriod = self.Param("AtrPeriod", 14) \
.SetGreaterThanZero() \
.SetCanOptimize(True) \
.SetOptimize(10, 20, 5) \
.SetDisplay("ATR Period", "Period for Average True Range indicator", "Indicators")
self._averagePeriod = self.Param("AveragePeriod", 20) \
.SetGreaterThanZero() \
.SetCanOptimize(True) \
.SetOptimize(10, 50, 10) \
.SetDisplay("Average Period", "Period for calculating ATR average and standard deviation", "Settings")
self._deviationMultiplier = self.Param("DeviationMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetCanOptimize(True) \
.SetOptimize(1.5, 3.0, 0.5) \
.SetDisplay("Deviation Multiplier", "Multiplier for standard deviation", "Settings")
self._candleType = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._stopLossPercent = self.Param("StopLossPercent", 1.0) \
.SetNotNegative() \
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management") \
.SetCanOptimize(True) \
.SetOptimize(0.5, 2.0, 0.5)
# Statistics variables
self._prevAtr = 0.0
self._avgAtr = 0.0
self._stdDevAtr = 0.0
self._sumAtr = 0.0
self._sumSquaresAtr = 0.0
self._count = 0
self._atrValues = Queue[float]()
@property
def AtrPeriod(self):
"""ATR Period."""
return self._atrPeriod.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atrPeriod.Value = value
@property
def AveragePeriod(self):
"""Period for calculating mean and standard deviation of ATR."""
return self._averagePeriod.Value
@AveragePeriod.setter
def AveragePeriod(self, value):
self._averagePeriod.Value = value
@property
def DeviationMultiplier(self):
"""Deviation multiplier for entry signals."""
return self._deviationMultiplier.Value
@DeviationMultiplier.setter
def DeviationMultiplier(self, value):
self._deviationMultiplier.Value = value
@property
def CandleType(self):
"""Candle type."""
return self._candleType.Value
@CandleType.setter
def CandleType(self, value):
self._candleType.Value = value
@property
def StopLossPercent(self):
"""Stop-loss percentage."""
return self._stopLossPercent.Value
@StopLossPercent.setter
def StopLossPercent(self, value):
self._stopLossPercent.Value = value
def GetWorkingSecurities(self):
"""!! REQUIRED!! Return securities and candle types used."""
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(volatility_mean_reversion_strategy, self).OnReseted()
self._prevAtr = 0
self._avgAtr = 0
self._stdDevAtr = 0
self._sumAtr = 0
self._sumSquaresAtr = 0
self._count = 0
self._atrValues.Clear()
def OnStarted2(self, time):
"""Called when the strategy starts."""
super(volatility_mean_reversion_strategy, self).OnStarted2(time)
# Create ATR indicator
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
# Create subscription and bind indicator
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(atr, self.ProcessCandle).Start()
# Setup chart visualization
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
self.StartProtection(
takeProfit=Unit(0),
stopLoss=Unit(self.StopLossPercent, UnitTypes.Percent),
useMarketOrders=True
)
def ProcessCandle(self, candle, atrValue):
"""Process candle and ATR value."""
# Skip unfinished candles
if candle.State != CandleStates.Finished:
return
# Check if strategy is ready to trade
# Extract ATR value
currentAtr = float(atrValue)
# Update ATR statistics
self.UpdateAtrStatistics(currentAtr)
# If we don't have enough data yet for statistics
if self._count < self.AveragePeriod:
self._prevAtr = currentAtr
return
# For volatility mean reversion, we need to use price action to determine direction
# We'll use simple momentum for direction (current price vs previous price)
priceDirectionIsBuy = candle.ClosePrice > candle.OpenPrice
# Check for entry conditions
if self.Position == 0:
# Low volatility expecting increase - possibly prepare for a breakout
if currentAtr < self._avgAtr - self.DeviationMultiplier * self._stdDevAtr:
# In low volatility, follow the current short-term price direction
if priceDirectionIsBuy:
self.BuyMarket(self.Volume)
self.LogInfo(f"Long entry: ATR = {currentAtr}, Avg = {self._avgAtr}, StdDev = {self._stdDevAtr}, Price up")
else:
self.SellMarket(self.Volume)
self.LogInfo(f"Short entry: ATR = {currentAtr}, Avg = {self._avgAtr}, StdDev = {self._stdDevAtr}, Price down")
# High volatility expecting decrease - possibly looking for market exhaustion
elif currentAtr > self._avgAtr + self.DeviationMultiplier * self._stdDevAtr:
# In high volatility, consider going against the short-term trend
# as excessive volatility often leads to reversals
if not priceDirectionIsBuy:
self.BuyMarket(self.Volume)
self.LogInfo(f"Contrarian long entry: ATR = {currentAtr}, Avg = {self._avgAtr}, StdDev = {self._stdDevAtr}, High volatility")
else:
self.SellMarket(self.Volume)
self.LogInfo(f"Contrarian short entry: ATR = {currentAtr}, Avg = {self._avgAtr}, StdDev = {self._stdDevAtr}, High volatility")
# Check for exit conditions
elif self.Position > 0: # Long position
if currentAtr < self._avgAtr and not priceDirectionIsBuy:
self.ClosePosition()
self.LogInfo(f"Long exit: ATR = {currentAtr}, Avg = {self._avgAtr}, Price down")
elif self.Position < 0: # Short position
if currentAtr < self._avgAtr and priceDirectionIsBuy:
self.ClosePosition()
self.LogInfo(f"Short exit: ATR = {currentAtr}, Avg = {self._avgAtr}, Price up")
# Save current ATR for next iteration
self._prevAtr = currentAtr
def UpdateAtrStatistics(self, currentAtr):
# Add current value to the queue
self._atrValues.Enqueue(currentAtr)
self._sumAtr += currentAtr
self._sumSquaresAtr += currentAtr * currentAtr
self._count += 1
# If queue is larger than period, remove oldest value
while self._atrValues.Count > self.AveragePeriod:
oldestAtr = self._atrValues.Dequeue()
self._sumAtr -= oldestAtr
self._sumSquaresAtr -= oldestAtr * oldestAtr
self._count -= 1
# Calculate average and standard deviation
if self._count > 0:
self._avgAtr = self._sumAtr / self._count
if self._count > 1:
variance = (self._sumSquaresAtr - (self._sumAtr * self._sumAtr) / self._count) / (self._count - 1)
self._stdDevAtr = 0 if variance <= 0 else Math.Sqrt(float(variance))
else:
self._stdDevAtr = 0
def CreateClone(self):
"""
!! REQUIRED!! Creates a new instance of the strategy.
"""
return volatility_mean_reversion_strategy()