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
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// 4H Bollinger Breakout strategy.
/// A long opens when the close crosses above the lower band with volume above its SMA and price above the trend SMA. A short opens
/// when the close crosses below the upper band with volume above its SMA, price below the trend SMA and RSI below 85. A long closes
/// when the close crosses above the upper band and a short when it crosses below the lower band.
/// </summary>
public class BollingerBreakout2Strategy : Strategy
{
private const decimal _rsiShortLimit = 85m;
private readonly StrategyParam<int> _bollingerLength;
private readonly StrategyParam<decimal> _bollingerMultiplier;
private readonly StrategyParam<int> _volumeLength;
private readonly StrategyParam<int> _trendLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<bool> _useLongSignals;
private readonly StrategyParam<bool> _useShortSignals;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeSma;
private decimal? _prevClose;
private decimal? _prevUpper;
private decimal? _prevLower;
/// <summary>
/// Bollinger Bands period.
/// </summary>
public int BollingerLength
{
get => _bollingerLength.Value;
set => _bollingerLength.Value = value;
}
/// <summary>
/// Bollinger Bands standard deviation multiplier.
/// </summary>
public decimal BollingerMultiplier
{
get => _bollingerMultiplier.Value;
set => _bollingerMultiplier.Value = value;
}
/// <summary>
/// Period of the volume SMA.
/// </summary>
public int VolumeLength
{
get => _volumeLength.Value;
set => _volumeLength.Value = value;
}
/// <summary>
/// Period of the trend SMA.
/// </summary>
public int TrendLength
{
get => _trendLength.Value;
set => _trendLength.Value = value;
}
/// <summary>
/// Period of RSI.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Allow long trades.
/// </summary>
public bool UseLongSignals
{
get => _useLongSignals.Value;
set => _useLongSignals.Value = value;
}
/// <summary>
/// Allow short trades.
/// </summary>
public bool UseShortSignals
{
get => _useShortSignals.Value;
set => _useShortSignals.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BollingerBreakout2Strategy()
{
_bollingerLength = Param(nameof(BollingerLength), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Length", "Bollinger Bands period", "Bollinger Bands");
_bollingerMultiplier = Param(nameof(BollingerMultiplier), 1.8m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Multiplier", "Standard deviation multiplier", "Bollinger Bands");
_volumeLength = Param(nameof(VolumeLength), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Length", "Period of the volume SMA", "Filters");
_trendLength = Param(nameof(TrendLength), 80)
.SetGreaterThanZero()
.SetDisplay("Trend Length", "Period of the trend SMA", "Filters");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "Period of RSI", "Filters");
_useLongSignals = Param(nameof(UseLongSignals), true)
.SetDisplay("Use Long Signals", "Allow long trades", "General");
_useShortSignals = Param(nameof(UseShortSignals), true)
.SetDisplay("Use Short Signals", "Allow short trades", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_volumeSma = null;
_prevClose = null;
_prevUpper = null;
_prevLower = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = null;
_prevUpper = null;
_prevLower = null;
var bollinger = new BollingerBands { Length = BollingerLength, Width = BollingerMultiplier };
var trendSma = new SimpleMovingAverage { Length = TrendLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
_volumeSma = new SimpleMovingAverage { Length = VolumeLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bollinger, trendSma, rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawIndicator(area, trendSma);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, rsi);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue trendValue, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
var volumeAverage = _volumeSma.Process(candle.TotalVolume, candle.ServerTime, true).ToDecimal();
if (!bollingerValue.IsFormed || bollingerValue is not BollingerBandsValue { UpBand: decimal upper, LowBand: decimal lower })
return;
var close = candle.ClosePrice;
var prevClose = _prevClose;
var prevUpper = _prevUpper;
var prevLower = _prevLower;
_prevClose = close;
_prevUpper = upper;
_prevLower = lower;
if (!_volumeSma.IsFormed || !trendValue.IsFormed || !rsiValue.IsFormed)
return;
if (prevClose is not decimal pc || prevUpper is not decimal pu || prevLower is not decimal pl)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var trend = trendValue.ToDecimal();
var rsi = rsiValue.ToDecimal();
var volumeHigh = candle.TotalVolume > volumeAverage;
var crossAboveLower = pc <= pl && close > lower;
var crossBelowUpper = pc >= pu && close < upper;
var crossAboveUpper = pc <= pu && close > upper;
var crossBelowLower = pc >= pl && close < lower;
var longSignal = UseLongSignals && crossAboveLower && volumeHigh && close > trend;
var shortSignal = UseShortSignals && crossBelowUpper && volumeHigh && close < trend && rsi < _rsiShortLimit;
if (longSignal && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortSignal && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && crossAboveUpper)
SellMarket(Position);
else if (Position < 0 && crossBelowLower)
BuyMarket(-Position);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BollingerBands, SimpleMovingAverage, RelativeStrengthIndex
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
RSI_SHORT_LIMIT = 85
class bollinger_breakout2_strategy(Strategy):
"""
4H Bollinger Breakout strategy.
A long opens when the close crosses above the lower band with volume above its SMA and price above the trend SMA. A short opens
when the close crosses below the upper band with volume above its SMA, price below the trend SMA and RSI below 85. A long closes
when the close crosses above the upper band and a short when it crosses below the lower band.
"""
def __init__(self):
super(bollinger_breakout2_strategy, self).__init__()
self._bollinger_length = self.Param("BollingerLength", 20).SetGreaterThanZero().SetDisplay("Bollinger Length", "Bollinger Bands period", "Bollinger Bands")
self._bollinger_multiplier = self.Param("BollingerMultiplier", 1.8).SetGreaterThanZero().SetDisplay("Bollinger Multiplier", "Standard deviation multiplier", "Bollinger Bands")
self._volume_length = self.Param("VolumeLength", 20).SetGreaterThanZero().SetDisplay("Volume Length", "Period of the volume SMA", "Filters")
self._trend_length = self.Param("TrendLength", 80).SetGreaterThanZero().SetDisplay("Trend Length", "Period of the trend SMA", "Filters")
self._rsi_length = self.Param("RsiLength", 14).SetGreaterThanZero().SetDisplay("RSI Length", "Period of RSI", "Filters")
self._use_long_signals = self.Param("UseLongSignals", True).SetDisplay("Use Long Signals", "Allow long trades", "General")
self._use_short_signals = self.Param("UseShortSignals", True).SetDisplay("Use Short Signals", "Allow short trades", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_sma = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_close = None
self._prev_upper = None
self._prev_lower = None
def OnReseted(self):
super(bollinger_breakout2_strategy, self).OnReseted()
self._volume_sma = None
self._reset_state()
def OnStarted2(self, time):
super(bollinger_breakout2_strategy, self).OnStarted2(time)
self._reset_state()
bollinger = BollingerBands()
bollinger.Length = self._bollinger_length.Value
bollinger.Width = Decimal(self._bollinger_multiplier.Value)
trend_sma = SimpleMovingAverage()
trend_sma.Length = self._trend_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
self._volume_sma = SimpleMovingAverage()
self._volume_sma.Length = self._volume_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bollinger, trend_sma, rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawIndicator(area, trend_sma)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
def _process_candle(self, candle, bollinger_value, trend_value, rsi_value):
if candle.State != CandleStates.Finished:
return
volume_average = process_float(self._volume_sma, candle.TotalVolume, candle.ServerTime, True).GetValue[Decimal](None)
if not bollinger_value.IsFormed or bollinger_value.UpBand is None or bollinger_value.LowBand is None:
return
upper = bollinger_value.UpBand
lower = bollinger_value.LowBand
close = candle.ClosePrice
prev_close = self._prev_close
prev_upper = self._prev_upper
prev_lower = self._prev_lower
self._prev_close = close
self._prev_upper = upper
self._prev_lower = lower
if not self._volume_sma.IsFormed or not trend_value.IsFormed or not rsi_value.IsFormed:
return
if prev_close is None or prev_upper is None or prev_lower is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
trend = trend_value.GetValue[Decimal](None)
rsi = rsi_value.GetValue[Decimal](None)
volume_high = candle.TotalVolume > volume_average
cross_above_lower = prev_close <= prev_lower and close > lower
cross_below_upper = prev_close >= prev_upper and close < upper
cross_above_upper = prev_close <= prev_upper and close > upper
cross_below_lower = prev_close >= prev_lower and close < lower
long_signal = self._use_long_signals.Value and cross_above_lower and volume_high and close > trend
short_signal = self._use_short_signals.Value and cross_below_upper and volume_high and close < trend and rsi < Decimal(RSI_SHORT_LIMIT)
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and cross_above_upper:
self.SellMarket(self.Position)
elif self.Position < 0 and cross_below_lower:
self.BuyMarket(-self.Position)
def CreateClone(self):
return bollinger_breakout2_strategy()