Auf GitHub ansehen

Strategie Low Volatility Reversion

Diese Mean-Reversion-Strategie wird nur in ruhigen Märkten aktiviert. Sie misst den ATR über ein Rückblickfenster und tritt ein, wenn die Volatilität unter einen Prozentsatz dieses Durchschnitts fällt und der Preis von seinem gleitenden Durchschnitt abweicht.

Tests zeigen eine durchschnittliche jährliche Rendite von etwa 139%. Es funktioniert am besten auf dem Aktienmarkt.

Durch den Handel gegen kleine Bewegungen in ruhigen Bedingungen zielt es darauf ab, Rückpraller zu erfassen, ohne großen Trends nachzujagen.

Positionen schließen, sobald der Preis den gleitenden Durchschnitt berührt oder der ATR-basierte Stop-Loss erreicht wird.

Details

  • Einstiegskriterien: Preis entfernt vom gleitenden Durchschnitt, während ATR unter dem Schwellenwert liegt.
  • Long/Short: Beide Richtungen.
  • Ausstiegskriterien: Preis kehrt zur MA zurück oder Stop wird ausgelöst.
  • Stops: Ja.
  • Standardwerte:
    • MAPeriod = 20
    • AtrPeriod = 14
    • AtrLookbackPeriod = 20
    • AtrThresholdPercent = 50m
    • AtrMultiplier = 2.0m
    • CandleType = TimeSpan.FromMinutes(5)
  • Filter:
    • Kategorie: Mean Reversion
    • Richtung: Beide
    • Indikatoren: ATR, MA
    • Stops: Ja
    • Komplexität: Mittel
    • Zeitrahmen: Intraday
    • Saisonalität: Nein
    • Neuronale Netze: Nein
    • Divergenz: Nein
    • Risikolevel: Mittel
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>
/// Strategy that trades on mean reversion during periods of low volatility.
/// It identifies periods of low ATR and opens positions when price
/// deviates from its moving average, expecting a return to the mean.
/// </summary>
public class LowVolReversionStrategy : Strategy
{
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _atrLookbackPeriod;
	private readonly StrategyParam<decimal> _atrThresholdPercent;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;

	private decimal _avgAtr;
	private int _lookbackCounter;
	private int _cooldown;

	/// <summary>
	/// Period for Moving Average calculation.
	/// </summary>
	public int MAPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Period for ATR calculation.
	/// </summary>
	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	/// <summary>
	/// Lookback period for ATR average calculation.
	/// </summary>
	public int AtrLookbackPeriod
	{
		get => _atrLookbackPeriod.Value;
		set => _atrLookbackPeriod.Value = value;
	}

	/// <summary>
	/// ATR threshold as percentage of average ATR.
	/// </summary>
	public decimal AtrThresholdPercent
	{
		get => _atrThresholdPercent.Value;
		set => _atrThresholdPercent.Value = value;
	}

	/// <summary>
	/// Type of candles used for strategy calculation.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Cooldown bars between trades.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Initialize the Low Volatility Reversion strategy.
	/// </summary>
	public LowVolReversionStrategy()
	{
		_maPeriod = Param(nameof(MAPeriod), 20)
			.SetDisplay("MA Period", "Period for Moving Average calculation", "Indicators")
			.SetOptimize(10, 50, 5);

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
			.SetOptimize(7, 21, 7);

		_atrLookbackPeriod = Param(nameof(AtrLookbackPeriod), 20)
			.SetDisplay("ATR Lookback", "Lookback period for ATR average calculation", "Indicators")
			.SetOptimize(10, 50, 10);

		_atrThresholdPercent = Param(nameof(AtrThresholdPercent), 80m)
			.SetDisplay("ATR Threshold %", "ATR threshold as percentage of average ATR", "Entry")
			.SetOptimize(30m, 90m, 10m);

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");

		_cooldownBars = Param(nameof(CooldownBars), 500)
			.SetRange(1, 1000)
			.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_avgAtr = default;
		_lookbackCounter = default;
		_cooldown = default;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_avgAtr = 0;
		_lookbackCounter = 0;
		_cooldown = 0;

		var sma = new SimpleMovingAverage { Length = MAPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(sma, atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, sma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal smaValue, decimal atrValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		// Gather ATR values for average calculation
		if (_lookbackCounter < AtrLookbackPeriod)
		{
			if (_lookbackCounter == 0)
				_avgAtr = atrValue;
			else
				_avgAtr = (_avgAtr * _lookbackCounter + atrValue) / (_lookbackCounter + 1);

			_lookbackCounter++;
			return;
		}
		else
		{
			_avgAtr = (_avgAtr * (AtrLookbackPeriod - 1) + atrValue) / AtrLookbackPeriod;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

		// Check if we're in a low volatility period
		decimal atrThreshold = _avgAtr * (AtrThresholdPercent / 100);
		bool isLowVolatility = atrValue < atrThreshold;

		if (Position == 0 && isLowVolatility)
		{
			if (candle.ClosePrice < smaValue)
			{
				BuyMarket();
				_cooldown = CooldownBars;
			}
			else if (candle.ClosePrice > smaValue)
			{
				SellMarket();
				_cooldown = CooldownBars;
			}
		}
		else if (Position > 0)
		{
			if (candle.ClosePrice > smaValue)
			{
				SellMarket();
				_cooldown = CooldownBars;
			}
		}
		else if (Position < 0)
		{
			if (candle.ClosePrice < smaValue)
			{
				BuyMarket();
				_cooldown = CooldownBars;
			}
		}
	}
}