Прорыв кластера волатильности
Стратегия Volatility Cluster Breakout основана на пробоях, возникающих при высоких кластерах волатильности.
Тестирование показывает среднегодичную доходность около 169%. Стратегию лучше запускать на крипторынке.
Сигналы формируются, когда индикаторы подтверждают возможность пробоя на внутридневных данных (5м). Такой метод подходит активным трейдерам.
Стопы рассчитываются на основе кратных ATR и параметров PriceAvgPeriod, AtrPeriod. Настройте эти значения для баланса риска и прибыли.
Подробности
- Критерии входа: см. реализацию условий индикаторов.
- Длинные/короткие: обе стороны.
- Критерии выхода: противоположный сигнал или логика стопов.
- Стопы: да, расчёт на основе индикаторов.
- Значения по умолчанию:
PriceAvgPeriod = 20AtrPeriod = 14StdDevMultiplier = 2.0mStopMultiplier = 2.0mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Фильтры:
- Категория: Следование за трендом
- Направление: Оба
- Индикаторы: несколько индикаторов
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной (5м)
- Сезонность: Нет
- Нейронные сети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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()