Auf GitHub ansehen

ATR-Erschöpfungs-Strategie

Ein plötzlicher Anstieg der Average True Range weist auf eine sich ausdehnende Volatilität hin, die schnell nachlassen kann. Diese Strategie sucht nach ATR-Werten, die eine gleitende Durchschnittslinie um einen konfigurierbaren Multiplikator überschreiten. In Verbindung mit einer Umkehrkerze zielt sie darauf ab, die anschließende Kontraktion zu erfassen.

Tests zeigen eine durchschnittliche Jahresrendite von etwa 139%. Am besten funktioniert die Strategie am Aktienmarkt.

Jeder Balken aktualisiert ATR und seinen eigenen Durchschnitt. Wenn ATR den Durchschnitt um den Multiplikator überschreitet und die Kerze entgegen der vorherigen Bewegung schließt, wird ein Trade eröffnet. Der Stop-Loss verwendet ebenfalls ein ATR-Vielfaches und verankert das Risiko an den aktuellen Volatilitätsniveaus.

Positionen verlassen sich typischerweise auf den Stop für den Ausstieg und suchen eine rasche Gegenbewegung, nachdem der Volatilitätsausbruch abgeklungen ist.

Details

  • Einstiegskriterien: ATR-Spike über dem Durchschnitt mit Umkehrkerze.
  • Long/Short: Beide.
  • Ausstiegskriterien: Stop-Loss.
  • Stops: Ja, ATR-basiert.
  • Standardwerte:
    • AtrPeriod = 14
    • AtrAvgPeriod = 20
    • AtrMultiplier = 1.5
    • MaPeriod = 20
    • StopLoss = 2%
    • CandleType = 5 minute
  • Filter:
    • Kategorie: Umkehr
    • 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>
/// ATR Exhaustion strategy.
/// Enters when ATR spikes (current ATR significantly higher than previous ATR).
/// ATR spike + bullish candle = buy.
/// ATR spike + bearish candle = sell.
/// Exits on SMA cross.
/// </summary>
public class AtrExhaustionStrategy : Strategy
{
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;

	private decimal _prevAtr;
	private int _cooldown;

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

	/// <summary>
	/// MA Period.
	/// </summary>
	public int MAPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

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

	/// <summary>
	/// Constructor.
	/// </summary>
	public AtrExhaustionStrategy()
	{
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetRange(7, 21)
			.SetDisplay("ATR Period", "Period for ATR", "Indicators");

		_maPeriod = Param(nameof(MAPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("MA Period", "Period for SMA", "Indicators");

		_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();
		_prevAtr = default;
		_cooldown = default;
	}

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

		_prevAtr = 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);
			DrawIndicator(area, atr);
			DrawOwnTrades(area);
		}
	}

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

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevAtr = atrValue;
			return;
		}

		if (_prevAtr == 0)
		{
			_prevAtr = atrValue;
			return;
		}

		if (_cooldown > 0)
		{
			_cooldown--;
			_prevAtr = atrValue;
			return;
		}

		// ATR spike: current ATR significantly higher than previous
		var atrSpike = _prevAtr > 0 && atrValue > _prevAtr * 1.3m;

		var isBullish = candle.ClosePrice > candle.OpenPrice;
		var isBearish = candle.ClosePrice < candle.OpenPrice;

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

		_prevAtr = atrValue;
	}
}