Bollinger Supertrend Strategy
This strategy blends Bollinger Bands with the Supertrend indicator to pinpoint entries during strong directional moves. Bollinger Bands gauge volatility expansion while the Supertrend line tracks the overall trend and acts as a trailing stop.
Testing indicates an average annual return of about 79%. It performs best in the stocks market.
A long trade triggers when price closes above the upper Bollinger Band and remains above the Supertrend line, confirming momentum and trend alignment. A short trade occurs when price closes below the lower band while staying under the Supertrend level. Trades are exited once price crosses back through the Supertrend, indicating momentum has faded.
Because the system waits for breakouts beyond normal volatility, it suits traders looking to capture sustained runs rather than quick reversals. The Supertrend stop dynamically adjusts to market swings, helping manage risk without manual intervention.
Details
- Entry Criteria:
- Long: Close > upper Bollinger Band && Close > Supertrend
- Short: Close < lower Bollinger Band && Close < Supertrend
- Long/Short: Both sides.
- Exit Criteria:
- Long: Exit when price crosses below Supertrend
- Short: Exit when price crosses above Supertrend
- Stops: Yes, via Supertrend trailing stop.
- Default Values:
BollingerPeriod= 20BollingerDeviation= 2.0mSupertrendPeriod= 10SupertrendMultiplier= 3.0mCandleType= TimeSpan.FromMinutes(15)
- Filters:
- Category: Trend
- Direction: Both
- Indicators: Bollinger Bands, Supertrend
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk Level: Medium
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>
/// Strategy based on Bollinger Bands and Supertrend indicators.
/// Enters long when price breaks above upper Bollinger Band and is above Supertrend.
/// Enters short when price breaks below lower Bollinger Band and is below Supertrend.
/// Uses Supertrend for dynamic exit.
/// </summary>
public class BollingerSupertrendStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerDeviation;
private readonly StrategyParam<int> _supertrendPeriod;
private readonly StrategyParam<decimal> _supertrendMultiplier;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private BollingerBands _bollinger;
private AverageTrueRange _atr;
private bool _isLongTrend;
private decimal _supertrendValue;
private decimal _lastClose;
private int _cooldown;
/// <summary>
/// Bollinger Bands period.
/// </summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>
/// Bollinger Bands standard deviation multiplier.
/// </summary>
public decimal BollingerDeviation
{
get => _bollingerDeviation.Value;
set => _bollingerDeviation.Value = value;
}
/// <summary>
/// Supertrend ATR period.
/// </summary>
public int SupertrendPeriod
{
get => _supertrendPeriod.Value;
set => _supertrendPeriod.Value = value;
}
/// <summary>
/// Supertrend ATR multiplier.
/// </summary>
public decimal SupertrendMultiplier
{
get => _supertrendMultiplier.Value;
set => _supertrendMultiplier.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type parameter.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BollingerSupertrendStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Period for Bollinger Bands calculation", "Indicators")
.SetOptimize(10, 30, 5);
_bollingerDeviation = Param(nameof(BollingerDeviation), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Deviation", "Standard deviation multiplier for Bollinger Bands", "Indicators")
.SetOptimize(1.5m, 3.0m, 0.5m);
_supertrendPeriod = Param(nameof(SupertrendPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Supertrend Period", "ATR period for Supertrend calculation", "Indicators")
.SetOptimize(7, 14, 1);
_supertrendMultiplier = Param(nameof(SupertrendMultiplier), 3.0m)
.SetGreaterThanZero()
.SetDisplay("Supertrend Multiplier", "ATR multiplier for Supertrend calculation", "Indicators")
.SetOptimize(2.0m, 4.0m, 0.5m);
_cooldownBars = Param(nameof(CooldownBars), 60)
.SetRange(1, 200)
.SetDisplay("Cooldown Bars", "Bars between trades", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
_bollinger = null;
_atr = null;
_isLongTrend = default;
_supertrendValue = default;
_lastClose = default;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Initialize indicators
_bollinger = new BollingerBands
{
Length = BollingerPeriod,
Width = BollingerDeviation
};
_atr = new AverageTrueRange
{
Length = SupertrendPeriod
};
// Create candles subscription
var subscription = SubscribeCandles(CandleType);
// Bind Bollinger indicator to candle subscription
subscription
.BindEx(_bollinger, _atr, ProcessCandle)
.Start();
// Setup chart if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue, IIndicatorValue atrValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Extract Bollinger Band values
var bb = (BollingerBandsValue)bollingerValue;
if (bb.MovingAverage is not decimal middleBand)
return;
if (bb.UpBand is not decimal upperBand)
return;
if (bb.LowBand is not decimal lowerBand)
return;
// Calculate Supertrend
// Note: This is a simplified Supertrend implementation
decimal atrValue2 = atrValue.ToDecimal() * SupertrendMultiplier;
decimal upperBand2 = (candle.HighPrice + candle.LowPrice) / 2 + atrValue2;
decimal lowerBand2 = (candle.HighPrice + candle.LowPrice) / 2 - atrValue2;
// Determine Supertrend value and direction
if (_lastClose == 0)
{
// First candle initialization
_supertrendValue = candle.ClosePrice > (candle.HighPrice + candle.LowPrice) / 2 ?
lowerBand2 : upperBand2;
_isLongTrend = candle.ClosePrice > _supertrendValue;
}
else
{
// Calculate Supertrend
if (_isLongTrend)
{
// Previous trend was up
if (candle.ClosePrice < _supertrendValue)
{
// Trend changes to down
_isLongTrend = false;
_supertrendValue = upperBand2;
}
else
{
// Trend remains up, adjust supertrend value
_supertrendValue = Math.Max(lowerBand2, _supertrendValue);
}
}
else
{
// Previous trend was down
if (candle.ClosePrice > _supertrendValue)
{
// Trend changes to up
_isLongTrend = true;
_supertrendValue = lowerBand2;
}
else
{
// Trend remains down, adjust supertrend value
_supertrendValue = Math.Min(upperBand2, _supertrendValue);
}
}
}
_lastClose = candle.ClosePrice;
// Trading logic
bool isPriceAboveSupertrend = candle.ClosePrice > _supertrendValue;
bool isPriceAboveUpperBand = candle.ClosePrice > upperBand;
bool isPriceBelowLowerBand = candle.ClosePrice < lowerBand;
if (_cooldown > 0)
_cooldown--;
// Long signal: Price breaks above upper Bollinger Band and is above Supertrend
if (_cooldown == 0 && isPriceAboveUpperBand && isPriceAboveSupertrend)
{
if (Position <= 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
// Short signal: Price breaks below lower Bollinger Band and is below Supertrend
else if (_cooldown == 0 && isPriceBelowLowerBand && !isPriceAboveSupertrend)
{
if (Position >= 0)
{
SellMarket();
_cooldown = CooldownBars;
}
}
// Exit signals based on Supertrend
else if ((Position > 0 && !isPriceAboveSupertrend) ||
(Position < 0 && isPriceAboveSupertrend))
{
if (Position > 0)
{
SellMarket();
_cooldown = CooldownBars;
}
else if (Position < 0)
{
BuyMarket();
_cooldown = 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, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class bollinger_supertrend_strategy(Strategy):
def __init__(self):
super(bollinger_supertrend_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 20) \
.SetDisplay("Bollinger Period", "Period for Bollinger Bands calculation", "Indicators")
self._bollinger_deviation = self.Param("BollingerDeviation", 2.0) \
.SetDisplay("Bollinger Deviation", "Standard deviation multiplier for Bollinger Bands", "Indicators")
self._supertrend_period = self.Param("SupertrendPeriod", 10) \
.SetDisplay("Supertrend Period", "ATR period for Supertrend calculation", "Indicators")
self._supertrend_multiplier = self.Param("SupertrendMultiplier", 3.0) \
.SetDisplay("Supertrend Multiplier", "ATR multiplier for Supertrend calculation", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 60) \
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._is_long_trend = False
self._supertrend_value = 0.0
self._last_close = 0.0
self._cooldown = 0
@property
def bollinger_period(self):
return self._bollinger_period.Value
@property
def bollinger_deviation(self):
return self._bollinger_deviation.Value
@property
def supertrend_period(self):
return self._supertrend_period.Value
@property
def supertrend_multiplier(self):
return self._supertrend_multiplier.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(bollinger_supertrend_strategy, self).OnReseted()
self._is_long_trend = False
self._supertrend_value = 0.0
self._last_close = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(bollinger_supertrend_strategy, self).OnStarted2(time)
bollinger = BollingerBands()
bollinger.Length = self.bollinger_period
bollinger.Width = self.bollinger_deviation
atr = AverageTrueRange()
atr.Length = self.supertrend_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(bollinger, atr, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
def OnProcess(self, candle, bollinger_value, atr_value):
if candle.State != CandleStates.Finished:
return
bb = bollinger_value
if bb.MovingAverage is None or bb.UpBand is None or bb.LowBand is None:
return
middle_band = float(bb.MovingAverage)
upper_band = float(bb.UpBand)
lower_band = float(bb.LowBand)
atr_val = float(atr_value) * float(self.supertrend_multiplier)
hl2 = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
upper_band2 = hl2 + atr_val
lower_band2 = hl2 - atr_val
if self._last_close == 0:
self._supertrend_value = lower_band2 if float(candle.ClosePrice) > hl2 else upper_band2
self._is_long_trend = float(candle.ClosePrice) > self._supertrend_value
else:
if self._is_long_trend:
if float(candle.ClosePrice) < self._supertrend_value:
self._is_long_trend = False
self._supertrend_value = upper_band2
else:
self._supertrend_value = max(lower_band2, self._supertrend_value)
else:
if float(candle.ClosePrice) > self._supertrend_value:
self._is_long_trend = True
self._supertrend_value = lower_band2
else:
self._supertrend_value = min(upper_band2, self._supertrend_value)
self._last_close = float(candle.ClosePrice)
is_above_supertrend = float(candle.ClosePrice) > self._supertrend_value
is_above_upper = float(candle.ClosePrice) > upper_band
is_below_lower = float(candle.ClosePrice) < lower_band
if self._cooldown > 0:
self._cooldown -= 1
if self._cooldown == 0 and is_above_upper and is_above_supertrend:
if self.Position <= 0:
self.BuyMarket()
self._cooldown = self.cooldown_bars
elif self._cooldown == 0 and is_below_lower and not is_above_supertrend:
if self.Position >= 0:
self.SellMarket()
self._cooldown = self.cooldown_bars
elif (self.Position > 0 and not is_above_supertrend) or (self.Position < 0 and is_above_supertrend):
if self.Position > 0:
self.SellMarket()
self._cooldown = self.cooldown_bars
elif self.Position < 0:
self.BuyMarket()
self._cooldown = self.cooldown_bars
def CreateClone(self):
return bollinger_supertrend_strategy()