View on GitHub

Bollinger Band Reversal Strategy

Price extremes outside the Bollinger Bands often snap back toward the middle band. This approach fades those extensions, buying dips below the lower band when the candle finishes green and selling rallies above the upper band after a red candle.

Testing indicates an average annual return of about 94%. It performs best in the stocks market.

The algorithm calculates Bollinger Bands on each bar and checks whether the close breaches the outer band. If a bullish candle closes below the lower band a long is opened; if a bearish candle closes above the upper band a short is taken. The stop relies on an ATR multiple while exits occur when price returns to the middle band.

Mean reversion trades typically last only a few bars, making this setup suitable for short-term volatility contractions.

Details

  • Entry Criteria: Close below lower band with bullish candle or close above upper band with bearish candle.
  • Long/Short: Both.
  • Exit Criteria: Price crossing middle band or stop-loss.
  • Stops: Yes, ATR based.
  • Default Values:
    • BollingerPeriod = 20
    • BollingerDeviation = 2.0
    • AtrMultiplier = 2.0
    • CandleType = 5 minute
  • Filters:
    • Category: Mean Reversion
    • Direction: Both
    • Indicators: Bollinger Bands, ATR
    • Stops: Yes
    • Complexity: Basic
    • Timeframe: Intraday
    • 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>
/// Bollinger Band Reversal strategy.
/// Enters long when price is below the lower Bollinger Band and candle is bullish.
/// Enters short when price is above the upper Bollinger Band and candle is bearish.
/// Exits at middle band.
/// </summary>
public class BollingerBandReversalStrategy : Strategy
{
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerDeviation;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cooldownBars;

	private int _cooldown;

	/// <summary>
	/// Bollinger Bands period.
	/// </summary>
	public int BollingerPeriod
	{
		get => _bollingerPeriod.Value;
		set => _bollingerPeriod.Value = value;
	}

	/// <summary>
	/// Bollinger Bands deviation multiplier.
	/// </summary>
	public decimal BollingerDeviation
	{
		get => _bollingerDeviation.Value;
		set => _bollingerDeviation.Value = value;
	}

	/// <summary>
	/// Candle type.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Cooldown bars.
	/// </summary>
	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	/// <summary>
	/// Constructor.
	/// </summary>
	public BollingerBandReversalStrategy()
	{
		_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Period", "Period for Bollinger Bands", "Indicators");

		_bollingerDeviation = Param(nameof(BollingerDeviation), 2.0m)
			.SetNotNegative()
			.SetDisplay("Bollinger Deviation", "Standard deviations for Bollinger Bands", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles to use", "General");

		_cooldownBars = Param(nameof(CooldownBars), 500)
			.SetRange(1, 1000)
			.SetDisplay("Cooldown Bars", "Bars to wait between trades", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_cooldown = default;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_cooldown = 0;

		var bollingerBands = new BollingerBands
		{
			Length = BollingerPeriod,
			Width = BollingerDeviation
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(bollingerBands, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, bollingerBands);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, IIndicatorValue bollingerValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!bollingerValue.IsFormed)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

		var bb = (BollingerBandsValue)bollingerValue;
		var upperBand = bb.UpBand;
		var lowerBand = bb.LowBand;
		var middleBand = bb.MovingAverage;

		var isBullish = candle.ClosePrice > candle.OpenPrice;
		var isBearish = candle.ClosePrice < candle.OpenPrice;

		if (Position == 0 && candle.ClosePrice < lowerBand && isBullish)
		{
			BuyMarket();
			_cooldown = CooldownBars;
		}
		else if (Position == 0 && candle.ClosePrice > upperBand && isBearish)
		{
			SellMarket();
			_cooldown = CooldownBars;
		}
		else if (Position > 0 && candle.ClosePrice > middleBand)
		{
			SellMarket();
			_cooldown = CooldownBars;
		}
		else if (Position < 0 && candle.ClosePrice < middleBand)
		{
			BuyMarket();
			_cooldown = CooldownBars;
		}
	}
}