GitHub で見る

1H Bollinger Bands Strategy

Overview

The 1H Bollinger Bands Strategy adapts the MetaTrader expert "1H Bolinger Bands" to the StockSharp high-level API. The idea is to trade bounces from the daily Bollinger Bands while the hourly trend is aligned and the long-term monthly MACD confirms the direction. The strategy works on the H1 timeframe (default) and relies on additional higher-timeframe data streams for confirmation.

Trading Logic

  • Trend Filter: Two linear weighted moving averages (LWMA 250 and 500) on the base timeframe ensure that only trades aligned with the dominant direction are allowed.
  • Trigger Pattern: On the higher timeframe (daily by default), the strategy watches for a candle whose low pierces below the lower Bollinger Band and the next candle opens back above it (reverse for shorts with the upper band). This replicates the original bounce condition.
  • Momentum Confirmation: Momentum (period 14) is calculated on the higher timeframe. At least one of the three most recent momentum deviations from 100 must exceed the configured threshold (default 0.3).
  • MACD Filter: A monthly MACD (12/26/9) must agree with the signal. For long trades the MACD line must be above the signal line, for shorts it must be below.
  • Entry: When all filters align, the strategy opens a market order. If there is an opposite position open, the requested volume neutralizes the existing exposure and flips the direction.

Position Management

Risk management is implemented directly in the strategy using pip-based distances converted through Security.PriceStep:

  • Stop Loss: Closes the position once price moves against the entry by the configured number of pips.
  • Take Profit: Locks in profits when price reaches the configured pip target.
  • Trailing Stop (optional): When enabled and the move exceeds the trailing distance, an internal trailing level follows price. A bar penetrating that level closes the trade.
  • Break-Even (optional): After price advances by the trigger distance, the stop level is moved to the entry price plus the configured offset (minus for shorts). A pullback to that level exits the position.

Money-based profit management from the original expert is not recreated; the StockSharp version focuses on price-based controls to remain exchange-agnostic.

Parameters

Parameter Description Default
CandleType Base timeframe for signal evaluation. 1 hour candles
HigherTimeFrame Timeframe used for Bollinger Bands and momentum. 1 day candles
MacdTimeFrame Timeframe for the confirming MACD. 30-day candles
FastMaPeriod / SlowMaPeriod Fast/slow LWMA lengths on the base timeframe. 6 / 85
TrendFastPeriod / TrendSlowPeriod Long-term LWMA trend filters. 250 / 500
MomentumPeriod Momentum lookback on the higher timeframe. 14
MomentumThreshold Minimum absolute deviation from 100 for momentum. 0.3
BollingerPeriod / BollingerWidth Daily Bollinger Band settings. 20 / 2.0
TradeVolume Base volume for each new position. 1
StopLossPips / TakeProfitPips Protective stop and target in pips. 20 / 50
EnableTrailing / TrailingStopPips Trailing stop toggle and distance. true / 40
EnableBreakEven / BreakEvenTriggerPips / BreakEvenOffsetPips Break-even toggle, trigger distance, and offset. true / 30 / 30

All numeric parameters are exposed through StrategyParam<T> and can be optimized in Designer/Runner.

Implementation Notes

  • The strategy subscribes to three candle streams simultaneously: base timeframe, higher timeframe for Bollinger/Momentum, and MACD timeframe.
  • Momentum uses the standard StockSharp Momentum indicator and stores the last three deviations to mimic the MQL logic.
  • Trade volume and pip distances assume that Security.PriceStep is correctly populated; otherwise, protective logic will not trigger.
  • StockSharp maintains a single net position. The "Max_Trades" scaling behaviour from the original script is simplified to a single aggregated position in this port.
  • Equity-based stop outs and money trailing features from the MQL version are intentionally omitted to keep the implementation exchange-neutral.

Usage

  1. Attach the strategy to a security that provides hourly, daily, and monthly candles (or adjust the parameters accordingly).
  2. Ensure the security exposes PriceStep so pip distances translate into price offsets.
  3. Configure the desired volume and risk parameters in the UI or in code before starting the strategy.
  4. Start the strategy; it will automatically subscribe to the necessary data, evaluate signals on closed candles, and manage the position with the configured protective rules.

Known Differences from the MQL Expert

  • Money-based trailing and total equity stop are not implemented; only price-based controls are retained.
  • Alerts, e-mail, and push notifications from the MQL code are omitted.
  • Order stacking is replaced by StockSharp's single net position model.

These adjustments keep the strategy idiomatic for StockSharp while preserving the core trading idea of the original expert.

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;

public class OneHBollingerBandsStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public OneHBollingerBandsStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}