Estratégia de Rompimento de Cluster de Volatilidade
A estratégia Volatility Cluster Breakout é construída em torno de rompimentos durante clusters de alta volatilidade.
Os testes indicam um retorno anual médio de aproximadamente 169%. Funciona melhor no mercado de criptomoedas.
Os sinais são disparados quando os indicadores confirmam oportunidades de rompimento em dados intradiários (5m). Isso torna o método adequado para traders ativos.
Os stops se baseiam em múltiplos de ATR e fatores como PriceAvgPeriod, AtrPeriod. Ajuste esses valores padrão para equilibrar risco e recompensa.
Detalhes
- Critérios de entrada: ver implementação para as condições do indicador.
- Comprado/Vendido: Ambos.
- Critérios de saída: sinal oposto ou lógica de stop.
- Stops: Sim, usando cálculos baseados em indicadores.
- Valores padrão:
PriceAvgPeriod = 20AtrPeriod = 14StdDevMultiplier = 2.0mStopMultiplier = 2.0mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Filtros:
- Categoria: Seguidor de tendência
- Direção: Ambos
- Indicadores: múltiplos indicadores
- Stops: Sim
- Complexidade: Intermediário
- Período: Intradiário (5m)
- 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;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Breakout strategy that trades only when ATR expands into a high-volatility cluster.
/// </summary>
public class VolatilityClusterBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _priceAvgPeriod;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _stdDevMultiplier;
private readonly StrategyParam<decimal> _stopMultiplier;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _sma;
private StandardDeviation _stdDev;
private AverageTrueRange _atr;
private SimpleMovingAverage _atrAvg;
private decimal _entryPrice;
private decimal _entryAtr;
private int _cooldown;
/// <summary>
/// Period for the moving average and standard deviation.
/// </summary>
public int PriceAvgPeriod
{
get => _priceAvgPeriod.Value;
set => _priceAvgPeriod.Value = value;
}
/// <summary>
/// Period for ATR calculation.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier used for breakout levels.
/// </summary>
public decimal StdDevMultiplier
{
get => _stdDevMultiplier.Value;
set => _stdDevMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier used for stop distance.
/// </summary>
public decimal StopMultiplier
{
get => _stopMultiplier.Value;
set => _stopMultiplier.Value = value;
}
/// <summary>
/// Bars to wait after each order.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public VolatilityClusterBreakoutStrategy()
{
_priceAvgPeriod = Param(nameof(PriceAvgPeriod), 20)
.SetRange(5, 100)
.SetDisplay("Price Average Period", "Period for moving average and standard deviation", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetRange(5, 50)
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators");
_stdDevMultiplier = Param(nameof(StdDevMultiplier), 1.3m)
.SetRange(0.25m, 5m)
.SetDisplay("StdDev Multiplier", "Multiplier for breakout levels", "Signals");
_stopMultiplier = Param(nameof(StopMultiplier), 1.8m)
.SetRange(0.5m, 10m)
.SetDisplay("Stop ATR Multiplier", "ATR multiplier used for stop distance", "Risk");
_cooldownBars = Param(nameof(CooldownBars), 60)
.SetRange(1, 500)
.SetDisplay("Cooldown Bars", "Bars to wait after each order", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for the strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security != null)
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sma = null;
_stdDev = null;
_atr = null;
_atrAvg = null;
_entryPrice = 0m;
_entryAtr = 0m;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Security == null)
throw new InvalidOperationException("Security is not specified.");
_sma = new SimpleMovingAverage { Length = PriceAvgPeriod };
_stdDev = new StandardDeviation { Length = PriceAvgPeriod };
_atr = new AverageTrueRange { Length = AtrPeriod };
_atrAvg = new SimpleMovingAverage { Length = Math.Max(AtrPeriod * 2, 10) };
_cooldown = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_sma, _stdDev, _atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _sma);
DrawIndicator(area, _atr);
DrawOwnTrades(area);
}
StartProtection(new Unit(0, UnitTypes.Absolute), new Unit(StopMultiplier, UnitTypes.Percent), false);
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue, decimal stdDevValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
var atrAvgValue = _atrAvg.Process(atrValue, candle.OpenTime, true).ToDecimal();
if (!_sma.IsFormed || !_stdDev.IsFormed || !_atr.IsFormed || !_atrAvg.IsFormed)
return;
if (ProcessState != ProcessStates.Started)
return;
if (_cooldown > 0)
{
_cooldown--;
return;
}
var upperLevel = smaValue + StdDevMultiplier * stdDevValue;
var lowerLevel = smaValue - StdDevMultiplier * stdDevValue;
var isHighVolatility = atrValue >= atrAvgValue * 1.15m;
var price = candle.ClosePrice;
if (Position == 0)
{
if (!isHighVolatility)
return;
if (price >= upperLevel)
{
_entryPrice = price;
_entryAtr = atrValue;
BuyMarket();
_cooldown = CooldownBars;
}
else if (price <= lowerLevel)
{
_entryPrice = price;
_entryAtr = atrValue;
SellMarket();
_cooldown = CooldownBars;
}
return;
}
var stopDistance = _entryAtr * StopMultiplier;
if (Position > 0)
{
if (price <= smaValue || !isHighVolatility || price <= _entryPrice - stopDistance)
{
SellMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
else if (Position < 0)
{
if (price >= smaValue || !isHighVolatility || price >= _entryPrice + stopDistance)
{
BuyMarket(Math.Abs(Position));
_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, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import SimpleMovingAverage, StandardDeviation, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class volatility_cluster_breakout_strategy(Strategy):
"""
Breakout strategy that trades only when ATR expands into a high-volatility cluster.
"""
def __init__(self):
super(volatility_cluster_breakout_strategy, self).__init__()
self._price_avg_period = self.Param("PriceAvgPeriod", 20) \
.SetDisplay("Price Average Period", "Period for moving average and standard deviation", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
self._std_dev_multiplier = self.Param("StdDevMultiplier", 1.3) \
.SetDisplay("StdDev Multiplier", "Multiplier for breakout levels", "Signals")
self._stop_multiplier = self.Param("StopMultiplier", 1.8) \
.SetDisplay("Stop ATR Multiplier", "ATR multiplier used for stop distance", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 60) \
.SetDisplay("Cooldown Bars", "Bars to wait after each order", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles for the strategy", "General")
self._sma = None
self._std_dev = None
self._atr = None
self._atr_avg = None
self._entry_price = 0.0
self._entry_atr = 0.0
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(volatility_cluster_breakout_strategy, self).OnReseted()
self._sma = None
self._std_dev = None
self._atr = None
self._atr_avg = None
self._entry_price = 0.0
self._entry_atr = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(volatility_cluster_breakout_strategy, self).OnStarted2(time)
atr_period = int(self._atr_period.Value)
price_period = int(self._price_avg_period.Value)
self._sma = SimpleMovingAverage()
self._sma.Length = price_period
self._std_dev = StandardDeviation()
self._std_dev.Length = price_period
self._atr = AverageTrueRange()
self._atr.Length = atr_period
self._atr_avg = SimpleMovingAverage()
self._atr_avg.Length = max(atr_period * 2, 10)
self._cooldown = 0
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._sma, self._std_dev, self._atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._sma)
self.DrawIndicator(area, self._atr)
self.DrawOwnTrades(area)
self.StartProtection(Unit(0, UnitTypes.Absolute), Unit(self._stop_multiplier.Value, UnitTypes.Percent), False)
def _process_candle(self, candle, sma_value, std_dev_value, atr_value):
if candle.State != CandleStates.Finished:
return
av = float(atr_value)
atr_avg_value = float(process_float(self._atr_avg, Decimal(av), candle.OpenTime, True))
if not self._sma.IsFormed or not self._std_dev.IsFormed or not self._atr.IsFormed or not self._atr_avg.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown > 0:
self._cooldown -= 1
return
sv = float(sma_value)
sdv = float(std_dev_value)
sdm = float(self._std_dev_multiplier.Value)
upper_level = sv + sdm * sdv
lower_level = sv - sdm * sdv
is_high_volatility = av >= atr_avg_value * 1.15
price = float(candle.ClosePrice)
cd = int(self._cooldown_bars.Value)
sm = float(self._stop_multiplier.Value)
if self.Position == 0:
if not is_high_volatility:
return
if price >= upper_level:
self._entry_price = price
self._entry_atr = av
self.BuyMarket()
self._cooldown = cd
elif price <= lower_level:
self._entry_price = price
self._entry_atr = av
self.SellMarket()
self._cooldown = cd
return
stop_distance = self._entry_atr * sm
if self.Position > 0:
if price <= sv or not is_high_volatility or price <= self._entry_price - stop_distance:
self.SellMarket(Math.Abs(self.Position))
self._cooldown = cd
elif self.Position < 0:
if price >= sv or not is_high_volatility or price >= self._entry_price + stop_distance:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown = cd
def CreateClone(self):
return volatility_cluster_breakout_strategy()