The strategy replicates the original MetaTrader 4 expert advisor "BOLINGER BAND SQUEEZE" using the StockSharp high level API. It looks for periods where Bollinger Bands contract and then enter trades once the bands expand, provided that momentum and trend filters confirm the move. The conversion keeps the multi-timeframe confirmation logic and transforms the money-management blocks into StockSharp idioms.
Trading Logic
Band squeeze and expansion
Bollinger Bands (length 20, deviation 2 by default) are calculated on the working timeframe.
The width of the most recent completed candle is compared against the width RetraceCandles bars ago.
A valid breakout requires the width ratio to exceed SqueezeRatio, signalling that price is expanding out of the squeeze.
Trend filter
Two weighted moving averages (WMA 6 and WMA 85 on typical price) define the immediate trend. Long trades require the fast WMA to be above the slow WMA, shorts the opposite.
Momentum confirmation
A higher timeframe Momentum indicator (length 14) checks whether price deviates sufficiently from the 100 level. The maximum deviation of the last three higher timeframe values must exceed the direction-specific threshold.
The higher timeframe is automatically selected to match the mapping used in the MT4 script (e.g., M15 → H1, H1 → D1, D1 → monthly). Weekly data also falls back to monthly confirmation. If no higher timeframe is available the momentum filter is skipped.
Macro filter
A monthly Moving Average Convergence Divergence (MACD 12/26/9) ensures that longer-term momentum matches the trade direction (MACD line above signal for longs, below for shorts).
Entry rules
Longs: band expansion, fast WMA above slow WMA, monthly MACD bullish, higher timeframe momentum deviation above MomentumBuyThreshold, and structural candle overlap (candle[-2].Low < candle[-1].High).
Shorts: band expansion, fast WMA below slow WMA, monthly MACD bearish, higher timeframe momentum deviation above MomentumSellThreshold, and the mirrored candle condition (candle[-1].Low < candle[-2].High).
Exit rules
Positions are closed when price closes on or beyond the outer Bollinger Band in the trade direction (i.e., long exits at the upper band, short exits at the lower band), mirroring the MT4 implementation.
StartProtection() enables StockSharp's protective order infrastructure so stop-loss / take-profit extensions can be added if required.
Indicators and Data Subscriptions
Primary timeframe candles defined by CandleType.
Higher timeframe candles for momentum confirmation (auto-mapped from the base timeframe).
Monthly candles for MACD filtering (30-day approximation).
Indicators: Bollinger Bands, two Weighted Moving Averages (typical price), Momentum, and MovingAverageConvergenceDivergenceSignal.
Parameters
Name
Default
Description
CandleType
15-minute candles
Primary working timeframe.
BollingerPeriod
20
Bollinger Band length.
BollingerWidth
2.0
Bollinger Band standard deviation multiplier.
SqueezeRatio
1.1
Minimum width expansion ratio between current and historical bands.
RetraceCandles
10
Lookback used for squeeze comparison.
FastMaLength
6
Length of the fast WMA (typical price).
SlowMaLength
85
Length of the slow WMA (typical price).
MomentumLength
14
Momentum period on the higher timeframe.
MomentumBuyThreshold
0.3
Minimum deviation from 100 required to validate long entries.
MomentumSellThreshold
0.3
Minimum deviation from 100 required to validate short entries.
All parameters are exposed as StrategyParam<T> values and can be optimised inside StockSharp Designer or at runtime.
Implementation Notes
The strategy uses SubscribeCandles().BindEx(...) to keep indicator wiring declarative and avoids manual indicator collections, as required by the high-level API guidelines.
Weighted moving averages are driven by typical price inside the candle-processing callback to preserve the behaviour of the LWMA calculations in the MT4 script.
Higher timeframe momentum values are stored in a three-element queue to mimic iMomentum lookbacks 1–3 from the original code.
Monthly MACD values persist in class fields so that every primary-timeframe candle has access to the latest long-term bias.
Exits triggered by the outer bands replace the MT4 trailing stop/breakeven blocks while retaining the visual intent of closing when price tags the opposite envelope.
The strategy leaves order sizing to the base Strategy.Volume. Position flips automatically offset any existing exposure by adding Math.Abs(Position) to the order volume.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class BollingerBandSqueezeBreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal? _prevFast, _prevSlow;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public BollingerBandSqueezeBreakoutStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 10).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 30).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = null;
_prevSlow = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = null; _prevSlow = null;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fast); DrawIndicator(area, slow); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished) return;
if (!IsFormedAndOnlineAndAllowTrading()) { _prevFast = fast; _prevSlow = slow; return; }
if (_prevFast == null || _prevSlow == null) { _prevFast = fast; _prevSlow = slow; return; }
var prevAbove = _prevFast.Value > _prevSlow.Value;
var currAbove = fast > slow;
_prevFast = fast; _prevSlow = slow;
if (!prevAbove && currAbove && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
else if (prevAbove && !currAbove && Position >= 0) { if (Position > 0) SellMarket(); 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
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class bollinger_band_squeeze_breakout_strategy(Strategy):
def __init__(self):
super(bollinger_band_squeeze_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._fast_period = self.Param("FastPeriod", 10) \
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
self._slow_period = self.Param("SlowPeriod", 30) \
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
self._prev_fast = None
self._prev_slow = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastPeriod(self):
return self._fast_period.Value
@property
def SlowPeriod(self):
return self._slow_period.Value
def OnReseted(self):
super(bollinger_band_squeeze_breakout_strategy, self).OnReseted()
self._prev_fast = None
self._prev_slow = None
def OnStarted2(self, time):
super(bollinger_band_squeeze_breakout_strategy, self).OnStarted2(time)
self._prev_fast = None
self._prev_slow = None
fast = ExponentialMovingAverage()
fast.Length = self.FastPeriod
slow = ExponentialMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, slow, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _on_process(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
if self._prev_fast is None or self._prev_slow is None:
self._prev_fast = fv
self._prev_slow = sv
return
prev_above = self._prev_fast > self._prev_slow
curr_above = fv > sv
self._prev_fast = fv
self._prev_slow = sv
if not prev_above and curr_above and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif prev_above and not curr_above and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
def CreateClone(self):
return bollinger_band_squeeze_breakout_strategy()