GitHub で見る

ボラティリティ平均回帰戦略

このアプローチは市場ボラティリティの変動を中心に取引します。ATRが移動平均から大きく乖離した場合、ボラティリティが異常に高くなったか低くなったことを示し、回帰する可能性があります。

テストでは平均年間リターンが約73%であることが示されています。暗号資産市場で最も良いパフォーマンスを発揮します。

ATRが平均値からDeviationMultiplier倍の標準偏差を引いた値を下回り、価格が移動平均を下回るときにロングエントリーします。ATRが上部バンドを超え、価格が平均を上回るときにショートエントリーします。ATRが平均水準に戻ったらポジションを決済します。

このようなセットアップは、価格方向ではなくボラティリティの極端な状態に逆張りするトレーダーに適しています。ボラティリティが拡大し続けた場合に備えて保護的なストップロスを使用します。

詳細

  • エントリー条件:
    • ロング: ATR < Avg - DeviationMultiplier * StdDev && Close < MA
    • ショート: ATR > Avg + DeviationMultiplier * StdDev && Close > MA
  • ロング/ショート: 両方。
  • エグジット条件:
    • ロング: ATR > Avg のときに決済
    • ショート: ATR < Avg のときに決済
  • ストップ: あり、パーセントストップロス。
  • デフォルト値:
    • AtrPeriod = 14
    • AveragePeriod = 20
    • DeviationMultiplier = 2m
    • CandleType = 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;
				}
			}
		}
	}
}