Bollinger Breakout
Bollinger Breakout seeks to capture moves that push beyond the Bollinger Bands and keep going. When price closes above the upper band or below the lower band, the strategy enters in the direction of the breakout if optional confirmations support the trade.
RSI, Aroon and moving‑average filters can be enabled to validate momentum and trend. An optional stop‑loss helps control risk. Positions are closed when price reaches the opposite band or the stop is triggered.
This approach favors markets prone to strong trends where band breaks lead to follow‑through rather than mean reversion.
Details
- Data: Price candles.
- Entry Criteria:
- Long: Close above upper band and all enabled filters confirm.
- Short: Close below lower band and all enabled filters confirm.
- Exit Criteria: Touch of opposite band or stop‑loss if
UseSL. - Stops: Optional stop‑loss (
UseSL). - Default Values:
UseRSI= TrueUseAroon= FalseUseMA= TrueUseSL= True
- Filters:
- Category: Breakout
- Direction: Long & Short
- Indicators: Bollinger Bands, RSI, Aroon, Moving Average
- Complexity: Moderate
- Risk level: High
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Bollinger Breakout Strategy.
/// Buys when candle body extends below lower BB with RSI oversold filter.
/// Sells when candle body extends above upper BB with RSI overbought filter.
/// </summary>
public class BollingerBreakoutStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleTypeParam;
private readonly StrategyParam<int> _bbLength;
private readonly StrategyParam<decimal> _bbMultiplier;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _rsiOversold;
private readonly StrategyParam<int> _rsiOverbought;
private readonly StrategyParam<int> _maLength;
private readonly StrategyParam<decimal> _candlePercent;
private readonly StrategyParam<int> _cooldownBars;
private BollingerBands _bollinger;
private RelativeStrengthIndex _rsi;
private ExponentialMovingAverage _ma;
private decimal? _entryPrice;
private int _cooldownRemaining;
public BollingerBreakoutStrategy()
{
_candleTypeParam = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General");
_bbLength = Param(nameof(BBLength), 20)
.SetGreaterThanZero()
.SetDisplay("BB Length", "Bollinger Bands period", "Bollinger Bands");
_bbMultiplier = Param(nameof(BBMultiplier), 1.5m)
.SetDisplay("BB StdDev", "Standard deviation multiplier", "Bollinger Bands");
_rsiLength = Param(nameof(RSILength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "RSI Filter");
_rsiOversold = Param(nameof(RSIOversold), 45)
.SetDisplay("RSI Oversold", "RSI oversold level", "RSI Filter");
_rsiOverbought = Param(nameof(RSIOverbought), 55)
.SetDisplay("RSI Overbought", "RSI overbought level", "RSI Filter");
_maLength = Param(nameof(MALength), 50)
.SetGreaterThanZero()
.SetDisplay("MA Length", "Moving Average period", "Moving Average");
_candlePercent = Param(nameof(CandlePercent), 0.3m)
.SetDisplay("Candle %", "Candle body penetration percentage", "Strategy");
_cooldownBars = Param(nameof(CooldownBars), 15)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
}
public DataType CandleType
{
get => _candleTypeParam.Value;
set => _candleTypeParam.Value = value;
}
public int BBLength
{
get => _bbLength.Value;
set => _bbLength.Value = value;
}
public decimal BBMultiplier
{
get => _bbMultiplier.Value;
set => _bbMultiplier.Value = value;
}
public int RSILength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int RSIOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
public int RSIOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
public int MALength
{
get => _maLength.Value;
set => _maLength.Value = value;
}
public decimal CandlePercent
{
get => _candlePercent.Value;
set => _candlePercent.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_bollinger = null;
_rsi = null;
_ma = null;
_entryPrice = null;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bollinger = new BollingerBands
{
Length = BBLength,
Width = BBMultiplier
};
_rsi = new RelativeStrengthIndex { Length = RSILength };
_ma = new ExponentialMovingAverage { Length = MALength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_bollinger, _rsi, _ma, OnProcess)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bollinger);
DrawIndicator(area, _ma);
DrawOwnTrades(area);
}
}
private void OnProcess(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue rsiValue, IIndicatorValue maValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_bollinger.IsFormed || !_rsi.IsFormed || !_ma.IsFormed)
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;
if (rsiValue.IsEmpty || maValue.IsEmpty)
return;
var rsi = rsiValue.ToDecimal();
var maVal = maValue.ToDecimal();
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
return;
}
var close = candle.ClosePrice;
var open = candle.OpenPrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
var candleSize = high - low;
if (candleSize <= 0)
return;
var buyZone = candleSize * CandlePercent + low;
var sellZone = high - candleSize * CandlePercent;
// Buy: candle buy zone below lower BB, bearish candle, RSI oversold, price above MA
var buySignal = buyZone < lower && close < open && rsi < RSIOversold && close > maVal;
// Sell: candle sell zone above upper BB, bullish candle, RSI overbought, price below MA
var sellSignal = sellZone > upper && close > open && rsi > RSIOverbought && close < maVal;
// Early exit at middle band
if (Position > 0 && close >= middle)
{
SellMarket(Math.Abs(Position));
_entryPrice = null;
_cooldownRemaining = CooldownBars;
return;
}
else if (Position < 0 && close <= middle)
{
BuyMarket(Math.Abs(Position));
_entryPrice = null;
_cooldownRemaining = CooldownBars;
return;
}
// Entry signals
if (buySignal && Position <= 0)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_entryPrice = close;
_cooldownRemaining = CooldownBars;
}
else if (sellSignal && Position >= 0)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_entryPrice = close;
_cooldownRemaining = CooldownBars;
}
}
}
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
from StockSharp.Algo.Indicators import BollingerBands, RelativeStrengthIndex, ExponentialMovingAverage, IndicatorHelper
from StockSharp.Algo.Strategies import Strategy
class bollinger_breakout_strategy(Strategy):
"""Bollinger Breakout Strategy with RSI and MA filters."""
def __init__(self):
super(bollinger_breakout_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle type", "Candle type for strategy calculation.", "General")
self._bb_length = self.Param("BBLength", 20) \
.SetDisplay("BB Length", "Bollinger Bands period", "Bollinger Bands")
self._bb_multiplier = self.Param("BBMultiplier", 1.5) \
.SetDisplay("BB StdDev", "Standard deviation multiplier", "Bollinger Bands")
self._rsi_length = self.Param("RSILength", 14) \
.SetDisplay("RSI Length", "RSI period", "RSI Filter")
self._rsi_oversold = self.Param("RSIOversold", 45) \
.SetDisplay("RSI Oversold", "RSI oversold level", "RSI Filter")
self._rsi_overbought = self.Param("RSIOverbought", 55) \
.SetDisplay("RSI Overbought", "RSI overbought level", "RSI Filter")
self._ma_length = self.Param("MALength", 50) \
.SetDisplay("MA Length", "Moving Average period", "Moving Average")
self._candle_percent = self.Param("CandlePercent", 0.3) \
.SetDisplay("Candle %", "Candle body penetration percentage", "Strategy")
self._cooldown_bars = self.Param("CooldownBars", 15) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._bollinger = None
self._rsi = None
self._ma = None
self._entry_price = None
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(bollinger_breakout_strategy, self).OnReseted()
self._bollinger = None
self._rsi = None
self._ma = None
self._entry_price = None
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(bollinger_breakout_strategy, self).OnStarted2(time)
self._bollinger = BollingerBands()
self._bollinger.Length = int(self._bb_length.Value)
self._bollinger.Width = float(self._bb_multiplier.Value)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = int(self._rsi_length.Value)
self._ma = ExponentialMovingAverage()
self._ma.Length = int(self._ma_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(self._bollinger, self._rsi, self._ma, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._bollinger)
self.DrawIndicator(area, self._ma)
self.DrawOwnTrades(area)
def _on_process(self, candle, bb_value, rsi_value, ma_value):
if candle.State != CandleStates.Finished:
return
if not self._bollinger.IsFormed or not self._rsi.IsFormed or not self._ma.IsFormed:
return
if bb_value.UpBand is None or bb_value.LowBand is None or bb_value.MovingAverage is None:
return
if rsi_value.IsEmpty or ma_value.IsEmpty:
return
rsi = float(IndicatorHelper.ToDecimal(rsi_value))
ma_val = float(IndicatorHelper.ToDecimal(ma_value))
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
return
close = float(candle.ClosePrice)
opn = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
upper = float(bb_value.UpBand)
lower = float(bb_value.LowBand)
middle = float(bb_value.MovingAverage)
candle_size = high - low
if candle_size <= 0:
return
candle_pct = float(self._candle_percent.Value)
cooldown = int(self._cooldown_bars.Value)
buy_zone = candle_size * candle_pct + low
sell_zone = high - candle_size * candle_pct
buy_signal = buy_zone < lower and close < opn and rsi < float(self._rsi_oversold.Value) and close > ma_val
sell_signal = sell_zone > upper and close > opn and rsi > float(self._rsi_overbought.Value) and close < ma_val
if self.Position > 0 and close >= middle:
self.SellMarket(Math.Abs(self.Position))
self._entry_price = None
self._cooldown_remaining = cooldown
return
elif self.Position < 0 and close <= middle:
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = None
self._cooldown_remaining = cooldown
return
if buy_signal and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._entry_price = close
self._cooldown_remaining = cooldown
elif sell_signal and self.Position >= 0:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._entry_price = close
self._cooldown_remaining = cooldown
def CreateClone(self):
return bollinger_breakout_strategy()