BB ブレイクアウト・モメンタム・スクイーズ戦略
BB Breakout Momentum Squeeze戦略は、Bollinger Bandsのブレイクアウトオシレーターとボラティリティスクイーズフィルターを組み合わせます。Bollinger BandsがKeltner Channelsの外に出たときにスクイーズが検出され、潜在的な拡大を示します。この拡大中に強気ブレイクアウトオシレーターが閾値を上回るとロングトレードが発生し、弱気クロスはショートトレードを使用します。ストップはATRバンドに基づき、リスク-リワード目標がエグジットロジックを完成させます。
詳細
- エントリー条件:
- スクイーズオフ(Bollinger BandsがKeltner Channelsの外側)。
- ロング: 強気オシレーターが閾値を上回る。
- ショート: 弱気オシレーターが閾値を上回る。
- ロング/ショート: 両方。
- エグジット条件:
- 価格がATRストップまたはリスク-リワード目標に達する。
- ストップ: ATRバンドとリスク-リワード目標。
- デフォルト値:
BbLength= 14BbMultiplier= 1.0Threshold= 50SqueezeLength= 20SqueezeBbMultiplier= 2.0KcMultiplier= 2.0AtrLength= 30AtrMultiplier= 1.4RrRatio= 1.5
- フィルター:
- カテゴリ: ボラティリティブレイクアウト
- 方向: 両方
- インジケーター: Bollinger Bands, Keltner Channels, ATR
- ストップ: あり
- 複雑さ: 中程度
- 時間軸: 任意
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Bollinger Breakout + Momentum Squeeze strategy.
/// </summary>
public class BbBreakoutMomentumSqueezeStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbMultiplier;
private readonly StrategyParam<decimal> _threshold;
private readonly StrategyParam<int> _squeezeLength;
private readonly StrategyParam<decimal> _squeezeBbMultiplier;
private readonly StrategyParam<decimal> _kcMultiplier;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<decimal> _rrRatio;
private BollingerBands _bbBreakout;
private BollingerBands _squeezeBb;
private KeltnerChannels _keltner;
private AverageTrueRange _atr;
private SimpleMovingAverage _bullNum;
private SimpleMovingAverage _bullDen;
private SimpleMovingAverage _bearNum;
private SimpleMovingAverage _bearDen;
private SimpleMovingAverage _upperBandMa;
private SimpleMovingAverage _lowerBandMa;
private decimal? _prevBull;
private decimal? _prevBear;
/// <summary>
/// Initializes a new instance of the <see cref="BbBreakoutMomentumSqueezeStrategy"/>.
/// </summary>
public BbBreakoutMomentumSqueezeStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_bbLength = Param(nameof(BbLength), 10)
.SetGreaterThanZero()
.SetDisplay("BB Breakout Length", "Length for Bollinger breakout calculation", "BB Breakout")
.SetOptimize(10, 30, 2);
_bbMultiplier = Param(nameof(BbMultiplier), 1.0m)
.SetDisplay("BB Breakout Mult", "Bollinger breakout multiplier", "BB Breakout");
_threshold = Param(nameof(Threshold), 0m)
.SetRange(0m, 100m)
.SetDisplay("Threshold", "Middle line threshold", "BB Breakout");
_squeezeLength = Param(nameof(SqueezeLength), 20)
.SetGreaterThanZero()
.SetDisplay("Squeeze Length", "Length for squeeze calculation", "Squeeze");
_squeezeBbMultiplier = Param(nameof(SqueezeBbMultiplier), 2.0m)
.SetDisplay("Bollinger Mult", "Bollinger Band std multiplier for squeeze", "Squeeze");
_kcMultiplier = Param(nameof(KcMultiplier), 2.0m)
.SetDisplay("Keltner Mult", "Keltner Channel multiplier", "Squeeze");
_atrLength = Param(nameof(AtrLength), 30)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR calculation length", "ATR");
_atrMultiplier = Param(nameof(AtrMultiplier), 1.4m)
.SetDisplay("ATR Multiplier", "ATR stop multiplier", "ATR");
_rrRatio = Param(nameof(RrRatio), 1.5m)
.SetDisplay("RR Ratio", "Risk reward ratio", "Risk");
}
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>
/// Bollinger breakout length.
/// </summary>
public int BbLength { get => _bbLength.Value; set => _bbLength.Value = value; }
/// <summary>
/// Bollinger breakout multiplier.
/// </summary>
public decimal BbMultiplier { get => _bbMultiplier.Value; set => _bbMultiplier.Value = value; }
/// <summary>
/// Midline threshold for bull/bear oscillator.
/// </summary>
public decimal Threshold { get => _threshold.Value; set => _threshold.Value = value; }
/// <summary>
/// Squeeze calculation length.
/// </summary>
public int SqueezeLength { get => _squeezeLength.Value; set => _squeezeLength.Value = value; }
/// <summary>
/// Bollinger multiplier for squeeze detection.
/// </summary>
public decimal SqueezeBbMultiplier { get => _squeezeBbMultiplier.Value; set => _squeezeBbMultiplier.Value = value; }
/// <summary>
/// Keltner multiplier for squeeze detection.
/// </summary>
public decimal KcMultiplier { get => _kcMultiplier.Value; set => _kcMultiplier.Value = value; }
/// <summary>
/// ATR calculation length.
/// </summary>
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <summary>
/// ATR stop multiplier.
/// </summary>
public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
/// <summary>
/// Risk reward ratio.
/// </summary>
public decimal RrRatio { get => _rrRatio.Value; set => _rrRatio.Value = value; }
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevBull = null;
_prevBear = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bbBreakout = new BollingerBands { Length = BbLength, Width = BbMultiplier };
_squeezeBb = new BollingerBands { Length = SqueezeLength, Width = SqueezeBbMultiplier };
_keltner = new KeltnerChannels { Length = SqueezeLength, Multiplier = KcMultiplier };
_atr = new AverageTrueRange { Length = AtrLength };
_bullNum = new SMA { Length = BbLength };
_bullDen = new SMA { Length = BbLength };
_bearNum = new SMA { Length = BbLength };
_bearDen = new SMA { Length = BbLength };
_upperBandMa = new SMA { Length = 3 };
_lowerBandMa = new SMA { Length = 3 };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_bbBreakout, _squeezeBb, _keltner, _atr, ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbBreakoutIv, IIndicatorValue squeezeBbIv, IIndicatorValue keltnerIv, IIndicatorValue atrIv)
{
if (candle.State != CandleStates.Finished)
return;
if (bbBreakoutIv is not BollingerBandsValue bbBreakoutValue ||
bbBreakoutValue.UpBand is not decimal breakoutUpper ||
bbBreakoutValue.LowBand is not decimal breakoutLower)
return;
if (squeezeBbIv is not BollingerBandsValue squeezeBbValue ||
squeezeBbValue.UpBand is not decimal squeezeUpper ||
squeezeBbValue.LowBand is not decimal squeezeLower)
return;
if (keltnerIv is not KeltnerChannelsValue keltnerValue ||
keltnerValue.Upper is not decimal kcUpper ||
keltnerValue.Lower is not decimal kcLower)
return;
if (atrIv.ToNullableDecimal() is not decimal atr)
return;
var close = candle.ClosePrice;
var time = candle.ServerTime;
var bullNumVal = _bullNum.Process(new DecimalIndicatorValue(_bullNum, Math.Max(close - breakoutUpper, 0m), time) { IsFinal = true }).ToNullableDecimal();
var bullDenVal = _bullDen.Process(new DecimalIndicatorValue(_bullDen, Math.Abs(close - breakoutUpper), time) { IsFinal = true }).ToNullableDecimal();
var bearNumVal = _bearNum.Process(new DecimalIndicatorValue(_bearNum, Math.Max(breakoutLower - close, 0m), time) { IsFinal = true }).ToNullableDecimal();
var bearDenVal = _bearDen.Process(new DecimalIndicatorValue(_bearDen, Math.Abs(breakoutLower - close), time) { IsFinal = true }).ToNullableDecimal();
if (bullNumVal is not decimal bullNum || bullDenVal is not decimal bullDen ||
bearNumVal is not decimal bearNum || bearDenVal is not decimal bearDen)
return;
if (!_bullNum.IsFormed || !_bullDen.IsFormed || !_bearNum.IsFormed || !_bearDen.IsFormed)
return;
var bull = bullDen == 0 ? 0m : bullNum / bullDen * 100m;
var bear = bearDen == 0 ? 0m : bearNum / bearDen * 100m;
var bullCross = _prevBull.HasValue && _prevBull <= Threshold && bull > Threshold;
var bearCross = _prevBear.HasValue && _prevBear <= Threshold && bear > Threshold;
_prevBull = bull;
_prevBear = bear;
var squeezeDotGreen = squeezeLower < kcLower || squeezeUpper > kcUpper;
var upperBandVal = _upperBandMa.Process(new DecimalIndicatorValue(_upperBandMa, close + atr * AtrMultiplier, time) { IsFinal = true }).ToNullableDecimal();
var lowerBandVal = _lowerBandMa.Process(new DecimalIndicatorValue(_lowerBandMa, close - atr * AtrMultiplier, time) { IsFinal = true }).ToNullableDecimal();
if (upperBandVal is not decimal upperBand || lowerBandVal is not decimal lowerBand)
return;
if (!_upperBandMa.IsFormed || !_lowerBandMa.IsFormed)
return;
if (bullCross && Position == 0)
{
BuyMarket();
}
else if (bearCross && Position == 0)
{
SellMarket();
}
}
}
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
from StockSharp.Messages import DataType, CandleStates, UnitTypes, Unit
from StockSharp.Algo.Indicators import BollingerBands, KeltnerChannels, AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class bb_breakout_momentum_squeeze_strategy(Strategy):
def __init__(self):
super(bb_breakout_momentum_squeeze_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._bb_length = self.Param("BbLength", 10) \
.SetGreaterThanZero() \
.SetDisplay("BB Breakout Length", "Length for Bollinger breakout calculation", "BB Breakout")
self._bb_mult = self.Param("BbMultiplier", 1.0) \
.SetDisplay("BB Breakout Mult", "Bollinger breakout multiplier", "BB Breakout")
self._threshold = self.Param("Threshold", 0.0) \
.SetDisplay("Threshold", "Middle line threshold", "BB Breakout")
self._squeeze_length = self.Param("SqueezeLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("Squeeze Length", "Length for squeeze calculation", "Squeeze")
self._squeeze_bb_mult = self.Param("SqueezeBbMultiplier", 2.0) \
.SetDisplay("Bollinger Mult", "Bollinger Band std multiplier for squeeze", "Squeeze")
self._kc_mult = self.Param("KcMultiplier", 2.0) \
.SetDisplay("Keltner Mult", "Keltner Channel multiplier", "Squeeze")
self._atr_length = self.Param("AtrLength", 30) \
.SetGreaterThanZero() \
.SetDisplay("ATR Length", "ATR calculation length", "ATR")
self._atr_mult = self.Param("AtrMultiplier", 1.4) \
.SetDisplay("ATR Multiplier", "ATR stop multiplier", "ATR")
self._rr_ratio = self.Param("RrRatio", 1.5) \
.SetDisplay("RR Ratio", "Risk reward ratio", "Risk")
self._prev_bull = None
self._prev_bear = None
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(bb_breakout_momentum_squeeze_strategy, self).OnReseted()
self._prev_bull = None
self._prev_bear = None
def OnStarted2(self, time):
super(bb_breakout_momentum_squeeze_strategy, self).OnStarted2(time)
self._bb_breakout = BollingerBands()
self._bb_breakout.Length = self._bb_length.Value
self._bb_breakout.Width = self._bb_mult.Value
self._squeeze_bb = BollingerBands()
self._squeeze_bb.Length = self._squeeze_length.Value
self._squeeze_bb.Width = self._squeeze_bb_mult.Value
self._keltner = KeltnerChannels()
self._keltner.Length = self._squeeze_length.Value
self._keltner.Multiplier = self._kc_mult.Value
self._atr = AverageTrueRange()
self._atr.Length = self._atr_length.Value
bb_len = self._bb_length.Value
self._bull_num = SimpleMovingAverage()
self._bull_num.Length = bb_len
self._bull_den = SimpleMovingAverage()
self._bull_den.Length = bb_len
self._bear_num = SimpleMovingAverage()
self._bear_num.Length = bb_len
self._bear_den = SimpleMovingAverage()
self._bear_den.Length = bb_len
self._upper_band_ma = SimpleMovingAverage()
self._upper_band_ma.Length = 3
self._lower_band_ma = SimpleMovingAverage()
self._lower_band_ma.Length = 3
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._bb_breakout, self._squeeze_bb, self._keltner, self._atr, self._process_candle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, bb_breakout_iv, squeeze_bb_iv, keltner_iv, atr_iv):
if candle.State != CandleStates.Finished:
return
bb_upper = bb_breakout_iv.UpBand
bb_lower = bb_breakout_iv.LowBand
if bb_upper is None or bb_lower is None:
return
breakout_upper = float(bb_upper)
breakout_lower = float(bb_lower)
sq_upper = squeeze_bb_iv.UpBand
sq_lower = squeeze_bb_iv.LowBand
if sq_upper is None or sq_lower is None:
return
squeeze_upper = float(sq_upper)
squeeze_lower = float(sq_lower)
kc_upper_val = keltner_iv.Upper
kc_lower_val = keltner_iv.Lower
if kc_upper_val is None or kc_lower_val is None:
return
kc_upper = float(kc_upper_val)
kc_lower = float(kc_lower_val)
if not atr_iv.IsFormed:
return
atr = float(atr_iv)
close = float(candle.ClosePrice)
t = candle.ServerTime
bull_num_val = process_float(self._bull_num, max(close - breakout_upper, 0.0), t, True)
bull_den_val = process_float(self._bull_den, abs(close - breakout_upper), t, True)
bear_num_val = process_float(self._bear_num, max(breakout_lower - close, 0.0), t, True)
bear_den_val = process_float(self._bear_den, abs(breakout_lower - close), t, True)
if not self._bull_num.IsFormed or not self._bull_den.IsFormed or \
not self._bear_num.IsFormed or not self._bear_den.IsFormed:
return
bull_num_f = float(bull_num_val)
bull_den_f = float(bull_den_val)
bear_num_f = float(bear_num_val)
bear_den_f = float(bear_den_val)
bull = 0.0 if bull_den_f == 0 else bull_num_f / bull_den_f * 100.0
bear = 0.0 if bear_den_f == 0 else bear_num_f / bear_den_f * 100.0
threshold = float(self._threshold.Value)
bull_cross = self._prev_bull is not None and self._prev_bull <= threshold and bull > threshold
bear_cross = self._prev_bear is not None and self._prev_bear <= threshold and bear > threshold
self._prev_bull = bull
self._prev_bear = bear
atr_mult = float(self._atr_mult.Value)
upper_band_val = process_float(self._upper_band_ma, close + atr * atr_mult, t, True)
lower_band_val = process_float(self._lower_band_ma, close - atr * atr_mult, t, True)
if not self._upper_band_ma.IsFormed or not self._lower_band_ma.IsFormed:
return
if bull_cross and self.Position == 0:
self.BuyMarket()
elif bear_cross and self.Position == 0:
self.SellMarket()
def CreateClone(self):
return bb_breakout_momentum_squeeze_strategy()