Estrategia ATR Range Breakout
ATR Range Breakout mide el movimiento del precio durante un número fijo de barras y lo compara con el rango verdadero promedio. Cuando el movimiento supera el ATR, se abre una posición en la dirección del movimiento.
Las pruebas indican un retorno anual promedio de aproximadamente 169%. Funciona mejor en el mercado de criptomonedas.
La estrategia verifica el precio cada N barras y utiliza la media móvil para señales de salida. Busca capturar el momentum cuando la volatilidad se expande más allá de los niveles normales.
Las operaciones se cierran cuando el precio cruza de nuevo la media móvil o cuando se activa el stop basado en ATR.
Detalles
- Criterios de entrada: El precio se mueve más que el ATR durante el período de observación.
- Largo/Corto: Ambas direcciones.
- Criterios de salida: El precio cruza la MA o stop.
- Stops: Sí.
- Valores predeterminados:
MAPeriod= 20ATRPeriod= 14LookbackPeriod= 5CandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoría: Ruptura
- Dirección: Ambos
- Indicadores: ATR, MA
- Stops: Sí
- Complejidad: Básico
- 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 Range strategy.
/// Enters long when price moves up by at least ATR over N candles,
/// enters short when price moves down by at least ATR over N candles.
/// </summary>
public class AtrRangeStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private decimal _nBarsAgoPrice;
private int _barCounter;
private int _cooldown;
/// <summary>
/// MA Period.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// ATR Period.
/// </summary>
public int ATRPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Lookback Period (N candles for price movement).
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Candle type 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 ATR Range strategy.
/// </summary>
public AtrRangeStrategy()
{
_maPeriod = Param(nameof(MAPeriod), 20)
.SetDisplay("MA Period", "Period for Moving Average calculation", "Indicators")
.SetOptimize(10, 50, 10);
_atrPeriod = Param(nameof(ATRPeriod), 14)
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
.SetOptimize(7, 28, 7);
_lookbackPeriod = Param(nameof(LookbackPeriod), 5)
.SetDisplay("Lookback Period", "Number of candles to measure price movement", "Entry")
.SetOptimize(3, 10, 1);
_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();
_nBarsAgoPrice = default;
_barCounter = default;
_cooldown = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_nBarsAgoPrice = 0;
_barCounter = 0;
_cooldown = 0;
var ma = new SimpleMovingAverage { Length = MAPeriod };
var atr = new AverageTrueRange { Length = ATRPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
_barCounter++;
if (_barCounter == 1 || _barCounter % LookbackPeriod == 1)
{
_nBarsAgoPrice = candle.ClosePrice;
return;
}
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Check at end of each lookback period
if (_barCounter % LookbackPeriod == 0)
{
var priceMovement = candle.ClosePrice - _nBarsAgoPrice;
var absMovement = Math.Abs(priceMovement);
if (absMovement >= atrValue)
{
if (Position == 0 && priceMovement > 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
else if (Position == 0 && priceMovement < 0)
{
SellMarket();
_cooldown = CooldownBars;
}
}
}
// Exit logic: price crosses MA
if (Position > 0 && candle.ClosePrice < maValue)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0 && candle.ClosePrice > maValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class atr_range_strategy(Strategy):
"""
ATR Range strategy.
Enters long when price moves up by at least ATR over N candles,
enters short when price moves down by at least ATR over N candles.
"""
def __init__(self):
super(atr_range_strategy, self).__init__()
self._ma_period = self.Param("MAPeriod", 20).SetDisplay("MA Period", "Period for Moving Average calculation", "Indicators")
self._atr_period = self.Param("ATRPeriod", 14).SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
self._lookback_period = self.Param("LookbackPeriod", 5).SetDisplay("Lookback Period", "Number of candles to measure price movement", "Entry")
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._n_bars_ago_price = 0.0
self._bar_counter = 0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(atr_range_strategy, self).OnReseted()
self._n_bars_ago_price = 0.0
self._bar_counter = 0
self._cooldown = 0
def OnStarted2(self, time):
super(atr_range_strategy, self).OnStarted2(time)
self._n_bars_ago_price = 0.0
self._bar_counter = 0
self._cooldown = 0
ma = SimpleMovingAverage()
ma.Length = self._ma_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_val, atr_val):
if candle.State != CandleStates.Finished:
return
self._bar_counter += 1
close = float(candle.ClosePrice)
lb = self._lookback_period.Value
if self._bar_counter == 1 or self._bar_counter % lb == 1:
self._n_bars_ago_price = close
return
if self._cooldown > 0:
self._cooldown -= 1
return
mv = float(ma_val)
av = float(atr_val)
cd = self._cooldown_bars.Value
# Check at end of each lookback period
if self._bar_counter % lb == 0:
price_movement = close - self._n_bars_ago_price
abs_movement = abs(price_movement)
if abs_movement >= av:
if self.Position == 0 and price_movement > 0:
self.BuyMarket()
self._cooldown = cd
elif self.Position == 0 and price_movement < 0:
self.SellMarket()
self._cooldown = cd
# Exit logic: price crosses MA
if self.Position > 0 and close < mv:
self.SellMarket()
self._cooldown = cd
elif self.Position < 0 and close > mv:
self.BuyMarket()
self._cooldown = cd
def CreateClone(self):
return atr_range_strategy()