Estratégia ATR Expansion Breakout
Esta estratégia segue explosões de volatilidade usando o Average True Range. Quando o ATR está aumentando em comparação com a barra anterior e o preço opera em relação a uma média móvel, ela busca surfar o rompimento.
Os testes indicam um retorno anual médio de aproximadamente 145%. Funciona melhor no mercado de criptomoedas.
A expansão do ATR implica que um movimento forte está em andamento. As entradas se alinham com a direção do preço em relação à média móvil, enquanto as contrações de volatilidade acionam as saídas.
Os stops são definidos usando um múltiplo de ATR para dar espaço às operações durante alta volatilidade.
Detalhes
- Critérios de entrada: ATR aumentando e preço acima/abaixo da MA.
- Comprado/Vendido: Ambos os lados.
- Critérios de saída: ATR contrai ou stop é atingido.
- Stops: Sim.
- Valores padrão:
AtrPeriod= 14MAPeriod= 20AtrMultiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoria: Rompimento
- Direção: Ambos
- Indicadores: ATR, MA
- Stops: Sim
- Complexidade: Básico
- Período: Intradiário
- Sazonalidade: Não
- Redes neurais: Não
- Divergência: Não
- Nível de risco: Médio
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 volatility expansion as measured by ATR.
/// Enters on any one-bar increase in formed ATR in the price/SMA direction,
/// exits when volatility contracts.
/// </summary>
public class AtrExpansionStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevAtr;
private bool _hasPrev;
private Order _pendingOrder;
private Unit _stopDistance;
private bool _protectionStarted;
/// <summary>
/// Period for ATR calculation.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Period for Moving Average calculation.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Type of candles used for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize the ATR Expansion strategy.
/// </summary>
public AtrExpansionStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 14).SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
.SetOptimize(7, 21, 7);
_maPeriod = Param(nameof(MAPeriod), 20).SetGreaterThanZero()
.SetDisplay("MA Period", "Period for MA calculation", "Indicators")
.SetOptimize(10, 50, 5);
_atrMultiplier = Param(nameof(AtrMultiplier), 2m).SetNotNegative()
.SetDisplay("ATR Stop Multiplier", "Frozen entry ATR stop distance; zero disables it", "Protection");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
OrderRegistering += order => _pendingOrder = order;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, DataType.Level1)];
}
public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevAtr = default;
_hasPrev = default;
_pendingOrder = null;
_stopDistance = null;
_protectionStarted = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevAtr = 0;
_hasPrev = false;
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var atr = new AverageTrueRange { Length = AtrPeriod };
var sma = new SimpleMovingAverage { Length = MAPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(atr, sma, ProcessCandle, false)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native protection runs before this callback, including between finished candles.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished || !atrValue.Indicator.IsFormed || !smaValue.Indicator.IsFormed
|| !IsFormedAndOnlineAndAllowTrading())
return;
var atr = atrValue.GetValue<decimal>();
var mean = smaValue.GetValue<decimal>();
if (!_hasPrev)
{
_hasPrev = true;
_prevAtr = atr;
return;
}
var previous = _prevAtr;
_prevAtr = atr;
if (_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
return;
// Any contraction closes, including a zero ATR; the MA direction is entry-only.
if (Position != 0m && atr < previous)
{
if (Position > 0m) SellMarket(Position);
else BuyMarket(Math.Abs(Position));
}
else if (Position == 0m && atr > previous)
{
if (candle.ClosePrice > mean) Enter(Sides.Buy, atr);
else if (candle.ClosePrice < mean) Enter(Sides.Sell, atr);
}
}
private void Enter(Sides side, decimal atr)
{
var distance = atr * AtrMultiplier;
_stopDistance ??= new Unit(distance);
// Keep the Unit reference held by native cached protection controllers.
_stopDistance.Value = distance;
if (!_protectionStarted && distance > 0m)
{
StartProtection(new Unit(), _stopDistance, useMarketOrders: true, isLocalStop: true);
_protectionStarted = true;
}
RegisterOrder(new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = side,
Volume = Volume,
Comment = "ATR expansion entry",
});
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal, Math
from StockSharp.Messages import DataType, CandleStates, Unit, OrderStates, OrderTypes, Sides, Level1Fields
from StockSharp.BusinessEntities import Order, Subscription
from StockSharp.Algo.Indicators import AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class atr_expansion_strategy(Strategy):
"""
Strategy that trades on volatility expansion as measured by ATR.
Enters on any one-bar increase in formed ATR in the price/SMA direction,
exits when volatility contracts.
"""
def __init__(self):
super(atr_expansion_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
self._ma_period = self.Param("MAPeriod", 20).SetGreaterThanZero().SetDisplay("MA Period", "Period for MA calculation", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetNotNegative().SetDisplay("ATR Stop Multiplier", "Frozen entry ATR stop distance; zero disables it", "Protection")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_atr = Decimal.Zero
self._has_prev = False
self._pending_order = None
self._stop_distance = None
self._protection_started = False
self.OrderRegistering += self._track_pending
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _track_pending(self, order):
self._pending_order = order
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(atr_expansion_strategy, self).OnReseted()
self._prev_atr = Decimal.Zero
self._has_prev = False
self._pending_order = None
self._stop_distance = None
self._protection_started = False
def OnStarted2(self, time):
super(atr_expansion_strategy, self).OnStarted2(time)
self._prev_atr = Decimal.Zero
self._has_prev = False
self._pending_order = None
self._stop_distance = None
self._protection_started = False
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(atr, sma, self._process_candle, False).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# Native protection runs before this callback, including between finished candles.
pass
def _process_candle(self, candle, atr_value, sma_value):
if candle.State != CandleStates.Finished or not atr_value.Indicator.IsFormed or not sma_value.Indicator.IsFormed or not self.IsFormedAndOnlineAndAllowTrading():
return
atr = atr_value.GetValue[Decimal](None)
mean = sma_value.GetValue[Decimal](None)
if not self._has_prev:
self._has_prev = True
self._prev_atr = atr
return
previous = self._prev_atr
self._prev_atr = atr
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
# Any contraction closes, including zero ATR; price/MA is only an entry filter.
if self.Position != 0 and atr < previous:
if self.Position > 0:
self.SellMarket(self.Position)
else:
self.BuyMarket(Math.Abs(self.Position))
elif self.Position == 0 and atr > previous:
if candle.ClosePrice > mean:
self._enter(Sides.Buy, atr)
elif candle.ClosePrice < mean:
self._enter(Sides.Sell, atr)
def _enter(self, side, atr):
distance = atr * Decimal(self._atr_multiplier.Value)
if self._stop_distance is None:
self._stop_distance = Unit(distance)
# Keep the same Unit held by native cached protection controllers.
self._stop_distance.Value = distance
if not self._protection_started and distance > 0:
self.StartProtection(Unit(), self._stop_distance, useMarketOrders=True, isLocalStop=True)
self._protection_started = True
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = side
order.Volume = self.Volume
order.Comment = "ATR expansion entry"
self.RegisterOrder(order)
def CreateClone(self):
return atr_expansion_strategy()