Bollinger Volatility Breakout
確定した5分足を使用し、指標の形成と取引許可を待ちます。移動統計にはシグナル足を含めます。Cは終値、M = SMA(C, 20)、D = 母標準偏差(C, 20)、U = M + 2 × D、L = M − 2 × D、A = ATR(14)で、真の値幅をWilder方式で平滑化します。
高ボラティリティ条件は A >= SMA(A, 14) + 2 × 母標準偏差(A, 14) です。価格水準はボリンジャーバンドの上限、中央、下限です。
建玉がないとき、高ボラティリティが確認され C >= U なら買い、C <= L なら売ります。新規数量は Volume です。待機期間が終了し、未完了注文がない場合のみ新規エントリーできます。 エントリーには A > 0 が必要です。
k = StopLossMultiplier. エントリー時のシグナル足の終値とATRを固定します。買い建玉のストップは C_entry − k × A_entry、売り建玉は C_entry + k × A_entry です。各足の確定時に終値が固定ストップに達した場合、買いで C <= M、売りで C >= M の場合、または高ボラティリティ条件が失効した場合、実際の残存建玉を全量成行で決済します。
注文後の次の84本の確定足では新規エントリーを停止しますが、決済判定は継続します。ストップは足の終値で判定し、成行注文は利用可能な価格で約定します。保有中のストップ距離は固定です。利食い注文とトレーリングストップはありません。
既定パラメーター
BollingerPeriod = 20
BollingerDeviation = 2
AtrPeriod = 14
AtrDeviationMultiplier = 2
StopLossMultiplier = 2
CooldownBars = 84
CandleType = TimeSpan.FromMinutes(5).TimeFrame()
namespace StockSharp.Samples.Strategies;
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;
/// <summary>
/// Breakout strategy that trades Bollinger band breaks only when ATR expands beyond its recent regime.
/// </summary>
public class BollingerVolatilityBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerDeviation;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrDeviationMultiplier;
private readonly StrategyParam<decimal> _stopLossMultiplier;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private BollingerBands _bollingerBands;
private AverageTrueRange _atr;
private SimpleMovingAverage _atrSma;
private StandardDeviation _atrStdDev;
private decimal _stopPrice;
private Order _pendingOrder;
private int _cooldown;
/// <summary>
/// Period for Bollinger bands calculation.
/// </summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>
/// Standard deviation multiplier for Bollinger bands.
/// </summary>
public decimal BollingerDeviation
{
get => _bollingerDeviation.Value;
set => _bollingerDeviation.Value = value;
}
/// <summary>
/// Period for ATR calculation.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR standard deviation multiplier used for volatility confirmation.
/// </summary>
public decimal AtrDeviationMultiplier
{
get => _atrDeviationMultiplier.Value;
set => _atrDeviationMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier used for stop distance.
/// </summary>
public decimal StopLossMultiplier
{
get => _stopLossMultiplier.Value;
set => _stopLossMultiplier.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 BollingerVolatilityBreakoutStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetRange(5, 100)
.SetDisplay("Bollinger Period", "Period for Bollinger band calculation", "Indicators");
_bollingerDeviation = Param(nameof(BollingerDeviation), 2m)
.SetRange(0.5m, 5m)
.SetDisplay("Bollinger Deviation", "Standard deviation multiplier for Bollinger bands", "Indicators");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetRange(5, 50)
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators");
_atrDeviationMultiplier = Param(nameof(AtrDeviationMultiplier), 2m)
.SetRange(0.1m, 5m)
.SetDisplay("ATR Deviation Multiplier", "ATR regime threshold multiplier", "Signals");
_stopLossMultiplier = Param(nameof(StopLossMultiplier), 2m)
.SetRange(0.5m, 10m)
.SetDisplay("Stop Loss Multiplier", "ATR multiplier used for stop distance", "Risk");
_cooldownBars = Param(nameof(CooldownBars), 84)
.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();
_bollingerBands = null;
_atr = null;
_atrSma = null;
_atrStdDev = null;
_stopPrice = 0m;
_pendingOrder = null;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (Security == null)
throw new InvalidOperationException("Security is not specified.");
_bollingerBands = new BollingerBands
{
Length = BollingerPeriod,
Width = BollingerDeviation,
};
_atr = new AverageTrueRange { Length = AtrPeriod };
_atrSma = new SimpleMovingAverage { Length = AtrPeriod };
_atrStdDev = new StandardDeviation { Length = AtrPeriod };
_cooldown = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_bollingerBands, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bollingerBands);
DrawIndicator(area, _atr);
DrawOwnTrades(area);
}
}
private bool IsOrderPending => _pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed);
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue)
{
if (candle.State != CandleStates.Finished)
return;
var typedBands = (BollingerBandsValue)bollingerValue;
if (typedBands.UpBand is not decimal upperBand ||
typedBands.LowBand is not decimal lowerBand ||
typedBands.MovingAverage is not decimal middleBand)
return;
var atrValue = _atr.Process(candle).ToDecimal();
var atrAverageValue = _atrSma.Process(atrValue, candle.OpenTime, true).ToDecimal();
var atrStdDevValue = _atrStdDev.Process(atrValue, candle.OpenTime, true).ToDecimal();
if (!_bollingerBands.IsFormed || !_atr.IsFormed || !_atrSma.IsFormed || !_atrStdDev.IsFormed)
return;
var isCoolingDown = _cooldown > 0;
if (isCoolingDown)
_cooldown--;
if (!IsFormedAndOnlineAndAllowTrading() || IsOrderPending)
return;
var volatilityThreshold = atrAverageValue + AtrDeviationMultiplier * atrStdDevValue;
var isHighVolatility = atrValue >= volatilityThreshold;
var price = candle.ClosePrice;
if (Position == 0)
{
if (isCoolingDown || !isHighVolatility || atrValue <= 0m)
return;
if (price >= upperBand)
{
_stopPrice = price - atrValue * StopLossMultiplier;
_pendingOrder = BuyMarket();
_cooldown = CooldownBars;
}
else if (price <= lowerBand)
{
_stopPrice = price + atrValue * StopLossMultiplier;
_pendingOrder = SellMarket();
_cooldown = CooldownBars;
}
return;
}
if (Position > 0)
{
if (price <= middleBand || !isHighVolatility || price <= _stopPrice)
{
_pendingOrder = SellMarket(Math.Abs(Position));
_cooldown = CooldownBars;
}
}
else if (Position < 0)
{
if (price >= middleBand || !isHighVolatility || price >= _stopPrice)
{
_pendingOrder = 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")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, OrderStates
from StockSharp.Algo.Indicators import BollingerBands, AverageTrueRange, SimpleMovingAverage, StandardDeviation, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class bollinger_volatility_breakout_strategy(Strategy):
"""
Breakout strategy that trades Bollinger band breaks only when ATR expands
beyond its recent regime.
"""
def __init__(self):
super(bollinger_volatility_breakout_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 20) \
.SetDisplay("Bollinger Period", "Period for Bollinger band calculation", "Indicators")
self._bollinger_deviation = self.Param("BollingerDeviation", 2.0) \
.SetDisplay("Bollinger Deviation", "Standard deviation multiplier for Bollinger bands", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
self._atr_deviation_multiplier = self.Param("AtrDeviationMultiplier", 2.0) \
.SetDisplay("ATR Deviation Multiplier", "ATR regime threshold multiplier", "Signals")
self._stop_loss_multiplier = self.Param("StopLossMultiplier", 2.0) \
.SetDisplay("Stop Loss Multiplier", "ATR multiplier used for stop distance", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 84) \
.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._bollinger_bands = None
self._atr = None
self._atr_sma = None
self._atr_std_dev = None
self._stop_price = 0.0
self._pending_order = None
self._cooldown = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(bollinger_volatility_breakout_strategy, self).OnReseted()
self._bollinger_bands = None
self._atr = None
self._atr_sma = None
self._atr_std_dev = None
self._stop_price = 0.0
self._pending_order = None
self._cooldown = 0
def OnStarted2(self, time):
super(bollinger_volatility_breakout_strategy, self).OnStarted2(time)
atr_period = int(self._atr_period.Value)
self._bollinger_bands = BollingerBands()
self._bollinger_bands.Length = int(self._bollinger_period.Value)
self._bollinger_bands.Width = Decimal(self._bollinger_deviation.Value)
self._atr = AverageTrueRange()
self._atr.Length = atr_period
self._atr_sma = SimpleMovingAverage()
self._atr_sma.Length = atr_period
self._atr_std_dev = StandardDeviation()
self._atr_std_dev.Length = atr_period
self._cooldown = 0
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._bollinger_bands, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._bollinger_bands)
self.DrawIndicator(area, self._atr)
self.DrawOwnTrades(area)
@property
def _is_order_pending(self):
return self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed)
def _process_candle(self, candle, bollinger_value):
if candle.State != CandleStates.Finished:
return
up_band = bollinger_value.UpBand
low_band = bollinger_value.LowBand
moving_avg = bollinger_value.MovingAverage
if up_band is None or low_band is None or moving_avg is None:
return
upper_band = float(up_band)
lower_band = float(low_band)
middle_band = float(moving_avg)
atr_result = self._atr.Process(CandleIndicatorValue(self._atr, candle))
atr_value = float(atr_result)
atr_average_value = float(process_float(self._atr_sma, Decimal(atr_value), candle.OpenTime, True))
atr_std_dev_value = float(process_float(self._atr_std_dev, Decimal(atr_value), candle.OpenTime, True))
if not self._bollinger_bands.IsFormed or not self._atr.IsFormed or not self._atr_sma.IsFormed or not self._atr_std_dev.IsFormed:
return
is_cooling_down = self._cooldown > 0
if is_cooling_down:
self._cooldown -= 1
if not self.IsFormedAndOnlineAndAllowTrading() or self._is_order_pending:
return
adm = float(self._atr_deviation_multiplier.Value)
volatility_threshold = atr_average_value + adm * atr_std_dev_value
is_high_volatility = atr_value >= volatility_threshold
price = float(candle.ClosePrice)
cd = int(self._cooldown_bars.Value)
slm = float(self._stop_loss_multiplier.Value)
if self.Position == 0:
if is_cooling_down or not is_high_volatility or atr_value <= 0:
return
if price >= upper_band:
self._stop_price = price - atr_value * slm
self._pending_order = self.BuyMarket()
self._cooldown = cd
elif price <= lower_band:
self._stop_price = price + atr_value * slm
self._pending_order = self.SellMarket()
self._cooldown = cd
return
if self.Position > 0:
if price <= middle_band or not is_high_volatility or price <= self._stop_price:
self._pending_order = self.SellMarket(Math.Abs(self.Position))
self._cooldown = cd
elif self.Position < 0:
if price >= middle_band or not is_high_volatility or price >= self._stop_price:
self._pending_order = self.BuyMarket(Math.Abs(self.Position))
self._cooldown = cd
def CreateClone(self):
return bollinger_volatility_breakout_strategy()