Auf GitHub ansehen

Bollinger Band Pending Stops Strategy

Overview

This sample converts the original MQL "Bb_0_1" expert advisor into the StockSharp high level API. The strategy listens to one candle subscription and uses Bollinger Bands to bracket the current price. When the market sits between the upper and lower bands, the algorithm places three layered buy stop orders above price and three layered sell stop orders below price. Each layer is configured with individual take-profit distances while sharing the same stop reference taken from the opposite band.

Trading logic

  • Subscribe to the configured timeframe and calculate Bollinger Bands with the requested period and deviation.
  • Inside the trading window (StartHour < hour < EndHour) and while the price remains between the bands, place pending orders:
    • Three buy stops at the current upper band level with take-profits displaced by FirstTakeProfit, SecondTakeProfit, and ThirdTakeProfit price steps above the entry.
    • Three sell stops at the current lower band level with mirrored take-profits below the entry.
    • All entries inherit the opposite band as their initial protective stop.
  • Pending orders are automatically re-registered whenever the bands move closer to price so that the orders follow the indicator envelopes.
  • Once a stop order executes, the strategy registers explicit stop-loss and take-profit orders for the filled volume.
  • Trailing protection is optional: UseBandTrailingStop selects the opposite band for trailing, otherwise the middle band (EMA) is used. Stops only trail when the close moves beyond the entry price and the indicator value provides a better level.

Parameters

Name Description
CandleType Time frame used for the Bollinger Band calculations.
BandPeriod Number of candles used by the bands.
BandDeviation Standard deviation multiplier for the bands.
Volume Volume of each pending layer.
StartHour / EndHour Hourly trading window (exclusive bounds).
FirstTakeProfit, SecondTakeProfit, ThirdTakeProfit Take-profit distances expressed in price steps for every layer.
UseBandTrailingStop Select the trailing reference: opposite band (true) or Bollinger middle line (false).

Implementation notes

  • Order volume mirrors the original expert advisor by using a static size (Volume). Risk-based position sizing from the MQL code is not implemented because the StockSharp sample environment does not provide account history.
  • Indicator shift parameters from the MQL script are not exposed because the high level API already delivers aligned values for the current candle.
  • Protective orders are normal stop and limit orders that are refreshed whenever the band-based trailing conditions improve the stop level.
using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Bollinger Band breakout strategy.
/// Buys when price closes above upper band, sells when price closes below lower band.
/// Exits at middle band.
/// </summary>
public class BollingerBandPendingStopsStrategy : Strategy
{
	private readonly StrategyParam<int> _bandPeriod;
	private readonly StrategyParam<decimal> _bandWidth;
	private readonly StrategyParam<DataType> _candleType;

	public int BandPeriod { get => _bandPeriod.Value; set => _bandPeriod.Value = value; }
	public decimal BandWidth { get => _bandWidth.Value; set => _bandWidth.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public BollingerBandPendingStopsStrategy()
	{
		_bandPeriod = Param(nameof(BandPeriod), 20)
			.SetDisplay("Band Period", "Bollinger bands period", "Indicators");

		_bandWidth = Param(nameof(BandWidth), 1m)
			.SetDisplay("Band Width", "Bollinger bands deviation", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); }

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var bb = new BollingerBands { Length = BandPeriod, Width = BandWidth };

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

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

		if (!value.IsFinal || value.IsEmpty)
			return;

		var bbVal = value.IsEmpty ? null : value as BollingerBandsValue;
		if (bbVal == null)
			return;

		var upper = bbVal.UpBand;
		var lower = bbVal.LowBand;
		var middle = bbVal.MovingAverage;

		if (upper == null || lower == null || middle == null)
			return;

		var close = candle.ClosePrice;

		// Breakout above upper band - buy
		if (close > upper.Value && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Breakout below lower band - sell
		else if (close < lower.Value && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}
		// Exit at middle band
		else if (Position > 0 && close < middle.Value)
		{
			SellMarket();
		}
		else if (Position < 0 && close > middle.Value)
		{
			BuyMarket();
		}
	}
}