Estrategia de Momentum Squeeze de Ruptura BB
La estrategia BB Breakout Momentum Squeeze combina un oscilador de ruptura de Bollinger Bands con un filtro de squeeze de volatilidad. El squeeze se detecta cuando las Bollinger Bands se mueven fuera de los Keltner Channels, señalando una posible expansión. Una operación larga ocurre cuando el oscilador alcista cruza por encima del umbral durante esta expansión, mientras que una operación corta usa el cruce bajista. Los stops se basan en una banda ATR y un objetivo de riesgo-recompensa completa la lógica de salida.
Detalles
- Criterios de entrada:
- Squeeze apagado (Bollinger Bands fuera de los Keltner Channels).
- Largo: el oscilador alcista cruza por encima del umbral.
- Corto: el oscilador bajista cruza por encima del umbral.
- Largo/Corto: Ambos lados.
- Criterios de salida:
- El precio alcanza el stop ATR o el objetivo de riesgo-recompensa.
- Stops: Banda ATR con objetivo de riesgo-recompensa.
- Valores predeterminados:
BbLength= 14BbMultiplier= 1.0Threshold= 50SqueezeLength= 20SqueezeBbMultiplier= 2.0KcMultiplier= 2.0AtrLength= 30AtrMultiplier= 1.4RrRatio= 1.5
- Filtros:
- Categoría: Ruptura de volatilidad
- Dirección: Ambos
- Indicadores: Bollinger Bands, Keltner Channels, ATR
- Stops: Sí
- Complejidad: Moderado
- Marco temporal: Cualquiera
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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()