Bollinger Band Width Breakout 戦略
Bollinger Bandの幅は上部バンドと下部バンドの間隔を測定します。幅の拡大はボラティリティとトレンド形成の可能性を示します。この戦略は幅が増加しているときにブレイクアウトを取引します。
テストでは年間平均リターン約151%が示されています。株式市場で最も優れたパフォーマンスを発揮します。
中央バンドに対する価格の位置が方向を決定します。中央バンドより上で価格が拡大するチャンネルはロングを引き起こし、下では拡大するチャンネルがショートを引き起こします。
バンド幅が収縮するかボラティリティストップに達するとエグジットが発生します。
詳細
- エントリー条件: バンド幅が拡大し、価格が中央バンドに対して位置する。
- ロング/ショート: 両方向。
- エグジット条件: バンド幅が収縮するかストップ。
- ストップ: はい。
- デフォルト値:
BollingerPeriod= 20BollingerDeviation= 2.0mAtrMultiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- フィルター:
- カテゴリ: ブレイクアウト
- 方向: 両方
- インジケーター: Bollinger Bands, ATR
- ストップ: はい
- 複雑さ: 基本
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// Trades a strict increase in absolute Bollinger width in the price/middle direction,
/// closes on any strict contraction, and protects actual fills with frozen entry ATR.
/// </summary>
public class BollingerBandWidthStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerDeviation;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal? _previousWidth;
private Order _pendingOrder;
private Unit _stopDistance;
private bool _protectionStarted;
public int BollingerPeriod { get => _bollingerPeriod.Value; set => _bollingerPeriod.Value = value; }
public decimal BollingerDeviation { get => _bollingerDeviation.Value; set => _bollingerDeviation.Value = value; }
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public BollingerBandWidthStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20).SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Period for Bollinger Bands calculation", "Indicators")
.SetOptimize(10, 30, 5);
_bollingerDeviation = Param(nameof(BollingerDeviation), 2m).SetGreaterThanZero()
.SetDisplay("Bollinger Deviation", "Deviation for Bollinger Bands calculation", "Indicators")
.SetOptimize(1.5m, 2.5m, 0.25m);
_atrPeriod = Param(nameof(AtrPeriod), 14).SetGreaterThanZero()
.SetDisplay("ATR Period", "Wilder ATR lookback for protection", "Protection");
_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;
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
_previousWidth = null;
_pendingOrder = null;
_stopDistance = null;
_protectionStarted = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_previousWidth = null;
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 bollinger = new BollingerBands { Length = BollingerPeriod, Width = BollingerDeviation };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(bollinger, atr, ProcessCandle, false).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native protection runs before this callback, including between finished candles.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished || !bollingerValue.Indicator.IsFormed || !atrValue.Indicator.IsFormed
|| !IsFormedAndOnlineAndAllowTrading())
return;
var bb = (BollingerBandsValue)bollingerValue;
if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower || bb.MovingAverage is not decimal middle)
return;
var width = upper - lower;
var previous = _previousWidth;
_previousWidth = width;
// A fully formed zero-width bar is a valid baseline, not an uninitialized sentinel.
if (previous is not decimal prev)
return;
if (_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
return;
// Equal width is neutral, not contraction; the middle is an entry-only filter.
if (Position != 0m && width < prev)
{
if (Position > 0m) SellMarket(Position);
else BuyMarket(Math.Abs(Position));
}
else if (Position == 0m && width > prev)
{
var atr = atrValue.GetValue<decimal>();
if (candle.ClosePrice > middle) Enter(Sides.Buy, atr);
else if (candle.ClosePrice < middle) 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 = "Bollinger width 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 BollingerBands, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class bollinger_band_width_strategy(Strategy):
"""
Trades strict absolute Bollinger width expansion in the price/middle direction,
closes on strict contraction, and protects actual fills with frozen entry ATR.
"""
def __init__(self):
super(bollinger_band_width_strategy, self).__init__()
self._bb_period = self.Param("BollingerPeriod", 20).SetGreaterThanZero().SetDisplay("Bollinger Period", "Period for Bollinger Bands calculation", "Indicators")
self._bb_deviation = self.Param("BollingerDeviation", 2.0).SetGreaterThanZero().SetDisplay("Bollinger Deviation", "Deviation for Bollinger Bands calculation", "Indicators")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Wilder ATR lookback for protection", "Protection")
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._previous_width = None
self._pending_order = None
self._stop_distance = None
self._protection_started = False
self.OrderRegistering += self._track_pending
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _track_pending(self, order):
self._pending_order = order
def OnReseted(self):
super(bollinger_band_width_strategy, self).OnReseted()
self._previous_width = None
self._pending_order = None
self._stop_distance = None
self._protection_started = False
def OnStarted2(self, time):
super(bollinger_band_width_strategy, self).OnStarted2(time)
self._previous_width = None
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()
bb = BollingerBands()
bb.Length = self._bb_period.Value
bb.Width = Decimal(self._bb_deviation.Value)
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bb, atr, self._process_candle, False).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bb)
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, bb_value, atr_value):
if candle.State != CandleStates.Finished or not bb_value.Indicator.IsFormed or not atr_value.Indicator.IsFormed or not self.IsFormedAndOnlineAndAllowTrading():
return
if bb_value.UpBand is None or bb_value.LowBand is None or bb_value.MovingAverage is None:
return
width = bb_value.UpBand - bb_value.LowBand
middle = bb_value.MovingAverage
previous = self._previous_width
self._previous_width = width
# A formed zero-width bar is a valid baseline, not an uninitialized sentinel.
if previous is None:
return
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
# Equal width is neutral; the middle is an entry-only filter.
if self.Position != 0 and width < previous:
if self.Position > 0:
self.SellMarket(self.Position)
else:
self.BuyMarket(Math.Abs(self.Position))
elif self.Position == 0 and width > previous:
atr = atr_value.GetValue[Decimal](None)
if candle.ClosePrice > middle:
self._enter(Sides.Buy, atr)
elif candle.ClosePrice < middle:
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 = "Bollinger width entry"
self.RegisterOrder(order)
def CreateClone(self):
return bollinger_band_width_strategy()