Volatility Cluster Breakout
Volatility Cluster Breakout 策略基于相关指标构建。
测试表明年均收益约为 169%,该策略在加密市场表现最佳。
当指标在指定周期的数据上确认条件时触发信号,适合积极交易者。
止损依赖ATR倍数及其他参数,可根据需要调整默认值以平衡风险和收益。
详细信息
- 入场条件: see implementation for indicator conditions.
- 多空: Both directions.
- 出场条件: opposite signal or stop logic.
- 止损: Yes, using indicator-based calculations.
- 默认值:
PriceAvgPeriod = 20AtrPeriod = 14StdDevMultiplier = 2.0mStopMultiplier = 2.0mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- 过滤器:
- 分类: Trend following
- 方向: Both
- 指标: multiple indicators
- 止损: Yes
- 复杂度: Intermediate
- 时间框架: Intraday (5m)
- 季节性: No
- 神经网络: No
- 背离: No
- 风险级别: Medium
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()