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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class atr_exhaustion_strategy(Strategy):
"""
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.
"""
def __init__(self):
super(atr_exhaustion_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14).SetDisplay("ATR Period", "Period for ATR", "Indicators")
self._ma_period = self.Param("MAPeriod", 20).SetDisplay("MA Period", "Period for SMA", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._cooldown_bars = self.Param("CooldownBars", 500).SetDisplay("Cooldown Bars", "Bars to wait between trades", "General")
self._prev_atr = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(atr_exhaustion_strategy, self).OnReseted()
self._prev_atr = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(atr_exhaustion_strategy, self).OnStarted2(time)
self._prev_atr = 0.0
self._cooldown = 0
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sma_val, atr_val):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_atr = float(atr_val)
return
av = float(atr_val)
if self._prev_atr == 0:
self._prev_atr = av
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_atr = av
return
# ATR spike: current ATR significantly higher than previous
atr_spike = self._prev_atr > 0 and av > self._prev_atr * 1.3
is_bullish = candle.ClosePrice > candle.OpenPrice
is_bearish = candle.ClosePrice < candle.OpenPrice
sv = float(sma_val)
close = float(candle.ClosePrice)
cd = self._cooldown_bars.Value
if self.Position == 0 and atr_spike and is_bullish:
self.BuyMarket()
self._cooldown = cd
elif self.Position == 0 and atr_spike and is_bearish:
self.SellMarket()
self._cooldown = cd
elif self.Position > 0 and close < sv:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > sv:
self.BuyMarket()
self._cooldown = cd
self._prev_atr = av
def CreateClone(self):
return atr_exhaustion_strategy()