GitHub で見る

BandOsMaCustom Strategy

Overview

This strategy is a direct port of the MetaTrader 5 expert advisor located at MQL/45596/mql5/Experts/MQL5Book/p7/BandOsMACustom.mq5. The original robot combines the MACD histogram (also known as OsMA) with Bollinger Bands and a moving average that are applied to the histogram values instead of raw prices. Whenever the histogram pierces the lower band, the expert opens a long trade, while touches of the upper band trigger short entries. The histogram crossing a separate moving average closes the position. A protective stop and a trailing-stop step (equal to one-fiftieth of the stop) keep risk under control.

The StockSharp implementation preserves this behaviour using the high-level API, so the trading logic stays readable and debuggable inside the framework.

Conversion highlights

  • The MACD histogram is implemented through MovingAverageConvergenceDivergenceHistogram, fed with the candle price that corresponds to the MetaTrader PRICE_* mode selected by the AppliedPrice parameter.
  • Bollinger Bands and the exit moving average process the OsMA output rather than price data. A compact history buffer reproduces the MetaTrader shift arguments for both indicators.
  • The strategy keeps the original long/short signalling: crossings below the lower band start longs, crossings above the upper band start shorts, and the OsMA crossing its moving average closes the trade.
  • StartProtection mirrors the MetaTrader stop-loss plus trailing-stop block. The trailing step is calculated as StopLossPoints / 50, just like the MQL class TrailingStop did.

Indicators

Indicator Purpose
MovingAverageConvergenceDivergenceHistogram Recreates MetaTrader's iOsMA output.
BollingerBands Calculates upper and lower thresholds over the histogram.
Moving average (SMA/EMA/SMMA/LWMA) Filters exits when the histogram crosses it.

Parameters

Name Default Description
CandleType 1-hour time-frame Primary timeframe used for all indicator calculations.
FastOsmaPeriod 12 Fast EMA length from the OsMA calculation.
SlowOsmaPeriod 26 Slow EMA length from the OsMA calculation.
SignalPeriod 9 Signal SMA length from the OsMA calculation.
AppliedPrice Typical MetaTrader-style applied price that feeds the histogram.
BandsPeriod 26 Length of the Bollinger Bands drawn on the histogram values.
BandsShift 0 Right shift (in bars) applied to the Bollinger values.
BandsDeviation 2.0 Standard deviation multiplier for the bands.
MaPeriod 10 Length of the exit moving average calculated on the histogram.
MaShift 0 Right shift (in bars) applied to the exit moving average.
MaMethod Simple Moving-average method (SMA, EMA, SMMA, LWMA).
StopLossPoints 1000 Protective stop distance expressed in price steps.
OrderVolume 0.01 Trading volume, identical to the MetaTrader “Lots” input.

Trading rules

  1. Subscribe to the selected candle series and feed the chosen applied price into the MACD histogram.
  2. Pass each histogram value to the Bollinger Bands and the exit moving average.
  3. Detect signals using the shifted buffers:
    • If the histogram drops through the lower band, set a bullish signal.
    • If the histogram rises through the upper band, set a bearish signal.
    • When the histogram crosses the exit moving average, clear the active signal, which allows the position to be closed.
  4. Manage positions:
    • Close existing longs whenever the bullish signal disappears; close shorts when the bearish signal disappears.
    • Open a long when the bullish signal is active and there is no open position; open a short when the bearish signal is active and the position is flat.
  5. Apply StartProtection with the configured stop-loss distance and a trailing step equal to StopLossPoints / 50 price steps.

Notes

  • All comments in the source code are in English to comply with the repository guidelines.
  • The history buffers guarantee that the StockSharp version respects MetaTrader BandsShift and MaShift parameters without requesting indicator values by index.
  • The strategy aligns with the high-level API conventions: SubscribeCandles drives indicator updates, and direct calls to BuyMarket/SellMarket mimic the original expert's order placement.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy combining the MACD histogram (OsMA) with Bollinger Bands to trade reversals.
/// Similar to BandOsMa but with customizable moving average filter for exit signals.
/// </summary>
public class BandOsMaCustomStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _macdFastPeriod;
	private readonly StrategyParam<int> _macdSlowPeriod;
	private readonly StrategyParam<int> _macdSignalPeriod;
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerDeviation;
	private readonly StrategyParam<int> _maPeriod;

	private BollingerBands _bollinger;
	private SMA _osmaMA;

	private decimal _prevOsma;
	private decimal _prevUpper;
	private decimal _prevLower;
	private decimal _prevMa;
	private bool _hasPrev;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int MacdFastPeriod
	{
		get => _macdFastPeriod.Value;
		set => _macdFastPeriod.Value = value;
	}

	public int MacdSlowPeriod
	{
		get => _macdSlowPeriod.Value;
		set => _macdSlowPeriod.Value = value;
	}

	public int MacdSignalPeriod
	{
		get => _macdSignalPeriod.Value;
		set => _macdSignalPeriod.Value = value;
	}

	public int BollingerPeriod
	{
		get => _bollingerPeriod.Value;
		set => _bollingerPeriod.Value = value;
	}

	public decimal BollingerDeviation
	{
		get => _bollingerDeviation.Value;
		set => _bollingerDeviation.Value = value;
	}

	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	public BandOsMaCustomStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe", "General");

		_macdFastPeriod = Param(nameof(MacdFastPeriod), 20)
			.SetDisplay("MACD Fast", "Fast EMA length", "Indicators");

		_macdSlowPeriod = Param(nameof(MacdSlowPeriod), 50)
			.SetDisplay("MACD Slow", "Slow EMA length", "Indicators");

		_macdSignalPeriod = Param(nameof(MacdSignalPeriod), 12)
			.SetDisplay("MACD Signal", "Signal EMA length", "Indicators");

		_bollingerPeriod = Param(nameof(BollingerPeriod), 14)
			.SetDisplay("Bollinger Period", "OsMA Bollinger Bands period", "Indicators");

		_bollingerDeviation = Param(nameof(BollingerDeviation), 2m)
			.SetDisplay("Bollinger Deviation", "Bollinger Bands deviation", "Indicators");

		_maPeriod = Param(nameof(MaPeriod), 10)
			.SetDisplay("MA Period", "OsMA moving average period for exit filter", "Indicators");
	}

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

		var macd = new MovingAverageConvergenceDivergenceSignal
		{
			Macd =
			{
				ShortMa = { Length = MacdFastPeriod },
				LongMa = { Length = MacdSlowPeriod }
			},
			SignalMa = { Length = MacdSignalPeriod }
		};

		_bollinger = new BollingerBands
		{
			Length = BollingerPeriod,
			Width = BollingerDeviation
		};

		_osmaMA = new SMA { Length = MaPeriod };

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

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

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

		var val = (IMovingAverageConvergenceDivergenceSignalValue)macdValue;
		if (val.Macd is not decimal macdLine || val.Signal is not decimal signalLine)
			return;

		var osma = macdLine - signalLine;

		var bbResult = (BollingerBandsValue)_bollinger.Process(new DecimalIndicatorValue(_bollinger, osma, candle.CloseTime));
		if (bbResult.UpBand is not decimal upper || bbResult.LowBand is not decimal lower)
			return;

		var maResult = _osmaMA.Process(new DecimalIndicatorValue(_osmaMA, osma, candle.CloseTime));
		if (maResult.IsEmpty)
			return;

		var ma = maResult.GetValue<decimal>();

		if (_hasPrev)
		{
			var buySignal = _prevOsma > _prevLower && osma <= lower && osma < ma;
			var sellSignal = _prevOsma < _prevUpper && osma >= upper && osma > ma;

			if (buySignal && Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + 1 : 1);
			else if (sellSignal && Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + 1 : 1);
		}

		_prevOsma = osma;
		_prevUpper = upper;
		_prevLower = lower;
		_prevMa = ma;
		_hasPrev = true;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_bollinger = null;
		_osmaMA = null;
		_prevOsma = 0;
		_prevUpper = 0;
		_prevLower = 0;
		_prevMa = 0;
		_hasPrev = false;

		base.OnReseted();
	}
}