4H Bollinger Breakout Strategy
The 4H Bollinger Breakout strategy trades Bollinger Band breakouts on the four-hour chart. Long positions are opened when price crosses above the lower band with volume and trend confirmation. Short positions are taken when price crosses below the upper band and RSI is below a threshold.
Details
- Entry Criteria:
- Long: Close crosses above lower band, volume above its SMA and price above trend SMA.
- Short: Close crosses below upper band, volume above its SMA, price below trend SMA, RSI < 85.
- Long/Short: Both sides.
- Exit Criteria:
- Long: Close crosses above upper band.
- Short: Close crosses below lower band.
- Stops: None.
- Default Values:
BollingerLength= 20BollingerMultiplier= 1.8VolumeLength= 20TrendLength= 80RsiLength= 14UseLongSignals= TrueUseShortSignals= True
- Filters:
- Category: Trend breakout
- Direction: Both
- Indicators: Bollinger Bands, Volume SMA, Trend SMA, RSI
- Stops: None
- Complexity: Medium
- Timeframe: 4H
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
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 - trades Bollinger Band breakouts with trend and RSI filters.
/// Buys when close crosses above lower band with uptrend.
/// Sells when close crosses below upper band with downtrend.
/// </summary>
public class BollingerBreakout2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _bollingerLength;
private readonly StrategyParam<decimal> _bollingerMultiplier;
private readonly StrategyParam<int> _trendLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _cooldownBars;
private decimal _prevClose;
private decimal _prevUpper;
private decimal _prevLower;
private bool _isInitial;
private int _cooldownRemaining;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int BollingerLength { get => _bollingerLength.Value; set => _bollingerLength.Value = value; }
public decimal BollingerMultiplier { get => _bollingerMultiplier.Value; set => _bollingerMultiplier.Value = value; }
public int TrendLength { get => _trendLength.Value; set => _trendLength.Value = value; }
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public BollingerBreakout2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_bollingerLength = Param(nameof(BollingerLength), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Length", "Bollinger Bands period", "Bollinger Bands");
_bollingerMultiplier = Param(nameof(BollingerMultiplier), 1.8m)
.SetGreaterThanZero()
.SetDisplay("StdDev Multiplier", "Standard deviation multiplier", "Bollinger Bands");
_trendLength = Param(nameof(TrendLength), 50)
.SetGreaterThanZero()
.SetDisplay("Trend MA Length", "Length for trend moving average", "Filters");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI calculation length", "Filters");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = 0;
_prevUpper = 0;
_prevLower = 0;
_isInitial = true;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var trendSma = new SimpleMovingAverage { Length = TrendLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var bollinger = new BollingerBands { Length = BollingerLength, Width = BollingerMultiplier };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, trendSma, rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue trendValue, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (bollingerValue is not BollingerBandsValue bb)
return;
if (bb.UpBand is not decimal upperBand ||
bb.LowBand is not decimal lowerBand ||
bb.MovingAverage is not decimal middleBand)
return;
var trend = trendValue.ToDecimal();
var rsi = rsiValue.ToDecimal();
var close = candle.ClosePrice;
if (_isInitial)
{
_prevClose = close;
_prevUpper = upperBand;
_prevLower = lowerBand;
_isInitial = false;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevClose = close;
_prevUpper = upperBand;
_prevLower = lowerBand;
return;
}
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevClose = close;
_prevUpper = upperBand;
_prevLower = lowerBand;
return;
}
var trendConditionLong = close > trend;
var trendConditionShort = close < trend;
// Long entry: close breaks above upper band + uptrend
if (close > upperBand && _prevClose <= _prevUpper && Position <= 0 && trendConditionLong)
{
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Short entry: close breaks below lower band + downtrend
else if (close < lowerBand && _prevClose >= _prevLower && Position >= 0 && trendConditionShort)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
SellMarket(Volume);
_cooldownRemaining = CooldownBars;
}
// Exit long: close drops below middle band
else if (Position > 0 && close < middleBand)
{
SellMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
// Exit short: close rises above middle band
else if (Position < 0 && close > middleBand)
{
BuyMarket(Math.Abs(Position));
_cooldownRemaining = CooldownBars;
}
_prevClose = close;
_prevUpper = upperBand;
_prevLower = lowerBand;
}
}
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, SimpleMovingAverage, RelativeStrengthIndex, IndicatorHelper
from StockSharp.Algo.Strategies import Strategy
class bollinger_breakout2_strategy(Strategy):
"""Bollinger Breakout Strategy."""
def __init__(self):
super(bollinger_breakout2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._bollinger_length = self.Param("BollingerLength", 20) \
.SetDisplay("Bollinger Length", "Bollinger Bands period", "Bollinger Bands")
self._bollinger_multiplier = self.Param("BollingerMultiplier", 1.8) \
.SetDisplay("StdDev Multiplier", "Standard deviation multiplier", "Bollinger Bands")
self._trend_length = self.Param("TrendLength", 50) \
.SetDisplay("Trend MA Length", "Length for trend moving average", "Filters")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI calculation length", "Filters")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars between trades", "Risk")
self._prev_close = 0.0
self._prev_upper = 0.0
self._prev_lower = 0.0
self._is_initial = True
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(bollinger_breakout2_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_upper = 0.0
self._prev_lower = 0.0
self._is_initial = True
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(bollinger_breakout2_strategy, self).OnStarted2(time)
trend_sma = SimpleMovingAverage()
trend_sma.Length = int(self._trend_length.Value)
rsi = RelativeStrengthIndex()
rsi.Length = int(self._rsi_length.Value)
bb = BollingerBands()
bb.Length = int(self._bollinger_length.Value)
bb.Width = self._bollinger_multiplier.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bb, trend_sma, rsi, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bb)
self.DrawOwnTrades(area)
def _on_process(self, candle, bb_value, trend_value, rsi_value):
if candle.State != CandleStates.Finished:
return
if bb_value.IsEmpty or trend_value.IsEmpty or rsi_value.IsEmpty:
return
if bb_value.UpBand is None or bb_value.LowBand is None or bb_value.MovingAverage is None:
return
upper_band = float(bb_value.UpBand)
lower_band = float(bb_value.LowBand)
middle_band = float(bb_value.MovingAverage)
trend = float(IndicatorHelper.ToDecimal(trend_value))
close = float(candle.ClosePrice)
if self._is_initial:
self._prev_close = close
self._prev_upper = upper_band
self._prev_lower = lower_band
self._is_initial = False
return
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_close = close
self._prev_upper = upper_band
self._prev_lower = lower_band
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_close = close
self._prev_upper = upper_band
self._prev_lower = lower_band
return
cooldown = int(self._cooldown_bars.Value)
trend_long = close > trend
trend_short = close < trend
if close > upper_band and self._prev_close <= self._prev_upper and self.Position <= 0 and trend_long:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self.BuyMarket(self.Volume)
self._cooldown_remaining = cooldown
elif close < lower_band and self._prev_close >= self._prev_lower and self.Position >= 0 and trend_short:
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self.SellMarket(self.Volume)
self._cooldown_remaining = cooldown
elif self.Position > 0 and close < middle_band:
self.SellMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
elif self.Position < 0 and close > middle_band:
self.BuyMarket(Math.Abs(self.Position))
self._cooldown_remaining = cooldown
self._prev_close = close
self._prev_upper = upper_band
self._prev_lower = lower_band
def CreateClone(self):
return bollinger_breakout2_strategy()