Ver en GitHub

Estrategia de Agotamiento ATR

Un repentino aumento en el Average True Range indica una expansión de la volatilidad que puede desvanecerse rápidamente. Esta estrategia busca lecturas de ATR que superen una media móvil por un multiplicador configurable. Cuando se combina con una vela de reversión, tiene como objetivo capturar la posterior contracción.

Las pruebas indican una rentabilidad anual media de aproximadamente el 139%. Funciona mejor en el mercado de acciones.

Cada barra actualiza el ATR y su propio promedio. Si el ATR supera el promedio por el multiplicador y la vela cierra en dirección opuesta al movimiento anterior, se abre una operación. El stop-loss también utiliza un múltiplo del ATR, anclando el riesgo a los niveles actuales de volatilidad.

Las posiciones típicamente dependen del stop para la salida, buscando una retracción rápida después de que el pico de volatilidad se disipe.

Detalles

  • Criterios de entrada: Pico de ATR por encima del promedio con vela de reversión.
  • Largo/Corto: Ambos.
  • Criterios de salida: Stop-loss.
  • Stops: Sí, basado en ATR.
  • Valores predeterminados:
    • AtrPeriod = 14
    • AtrAvgPeriod = 20
    • AtrMultiplier = 1.5
    • MaPeriod = 20
    • StopLoss = 2%
    • CandleType = 5 minute
  • Filtros:
    • Categoría: Reversión
    • Dirección: Ambos
    • Indicadores: ATR, MA
    • Stops: Sí
    • Complejidad: Intermedio
    • Marco temporal: Intradía
    • Estacionalidad: No
    • Redes neuronales: No
    • Divergencia: No
    • Nivel de riesgo: Medio
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;
	}
}