The BandOsMa Strategy converts the MetaTrader 5 "BandOsMA" expert advisor into a StockSharp strategy. It evaluates the MACD histogram (OsMA) using Bollinger Bands built directly on the histogram values. Breakouts above or below the bands create entry signals, while an additional moving average of the histogram manages signal exits.
The strategy operates on a single symbol and timeframe selected by the user. Indicator values are calculated on finished candles using StockSharp's high-level candle subscriptions.
Trading Logic
Indicators
MovingAverageConvergenceDivergenceSignal provides the MACD histogram (OsMA).
BollingerBands is applied to the OsMA sequence to detect extreme deviations.
A configurable moving average smooths the histogram and acts as an exit filter.
Entry
A long signal appears when the current OsMA closes below the lower band while the previous bar stayed above it.
A short signal appears when the current OsMA closes above the upper band while the previous bar stayed below it.
Exit
Signals are cleared when the histogram crosses the moving average in the opposite direction.
When an open position no longer matches the active signal, the position is closed immediately.
A pip-based stop-loss is attached to each position. The stop also acts as a trailing stop with the same distance and a trailing step equal to StopLossPoints / 50 (mirroring the MetaTrader helper class).
Position Management
Stop Loss & Trailing: The stop distance is expressed in MetaTrader points and converted into price units using the instrument's PriceStep. The same distance is used for the trailing stop, which moves forward once the close price improves by at least the trailing step.
One Position at a Time: Only one net position is maintained. Opposite signals close the current position before considering a new entry.
Parameters
Group
Name
Description
Default
General
CandleType
Timeframe for candle subscription and indicator calculation.
H1
Risk
LotSize
Trade volume in lots.
0.01
Risk
StopLossPoints
Stop-loss distance expressed in MetaTrader points (also used for trailing).
Shift applied to Bollinger buffers (non-negative).
0
Indicators
BollingerDeviation
Standard deviation multiplier for Bollinger Bands.
2
Indicators
MovingAveragePeriod
Length of the moving average applied to OsMA.
10
Indicators
MovingAverageShift
Shift applied to the moving average buffer (non-negative).
0
Indicators
MovingAverageMethod
Moving average type (Simple, Exponential, Smoothed, LinearWeighted).
Simple
Implementation Notes
Candle processing uses WhenCandlesFinished to ensure only final bars drive the logic.
Indicator values are stored in history buffers to emulate MetaTrader-style buffer shifts. Negative shifts are not supported; use zero or positive values as in the original expert defaults.
Trailing stops rely on candle closes rather than tick-by-tick updates. Adjust the pip distance if precise tick-level trailing is required.
Usage
Select the desired symbol and timeframe in StockSharp.
Configure the parameters, especially CandleType, LotSize, and indicator periods.
Start the strategy; it will subscribe to candles, compute the indicators, and execute trades according to the described logic.
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>
/// Strategy combining the MACD histogram (OsMA) with Bollinger Bands to trade reversals.
/// When OsMA crosses below lower Bollinger band, buy signal is generated.
/// When OsMA crosses above upper Bollinger band, sell signal is generated.
/// </summary>
public class BandOsMaStrategy : 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 decimal _prevOsma;
private decimal _prevUpper;
private decimal _prevLower;
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 BandOsMaStrategy()
{
_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");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFastPeriod },
LongMa = { Length = MacdSlowPeriod }
},
SignalMa = { Length = MacdSignalPeriod }
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, macd);
DrawOwnTrades(area);
}
}
private BollingerBands _bollinger;
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;
_bollinger ??= new BollingerBands
{
Length = BollingerPeriod,
Width = BollingerDeviation
};
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;
}
if (_hasPrev)
{
// Buy: OsMA crosses below lower band (reversal up expected)
if (_prevOsma > _prevLower && osma <= lower && Position <= 0)
{
BuyMarket(Position < 0 ? Math.Abs(Position) + 1 : 1);
}
// Sell: OsMA crosses above upper band (reversal down expected)
else if (_prevOsma < _prevUpper && osma >= upper && Position >= 0)
{
SellMarket(Position > 0 ? Math.Abs(Position) + 1 : 1);
}
}
_prevOsma = osma;
_prevUpper = upper;
_prevLower = lower;
_hasPrev = true;
}
/// <inheritdoc />
protected override void OnReseted()
{
_prevOsma = 0;
_prevUpper = 0;
_prevLower = 0;
_hasPrev = false;
_bollinger = null;
base.OnReseted();
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergence
from StockSharp.Algo.Strategies import Strategy
class band_os_ma_strategy(Strategy):
def __init__(self):
super(band_os_ma_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._macd_fast_period = self.Param("MacdFastPeriod", 20)
self._macd_slow_period = self.Param("MacdSlowPeriod", 50)
self._macd_signal_period = self.Param("MacdSignalPeriod", 12)
self._bollinger_period = self.Param("BollingerPeriod", 14)
self._bollinger_deviation = self.Param("BollingerDeviation", 2.0)
self._macd_history = []
self._osma_history = []
self._prev_osma = None
self._prev_upper = None
self._prev_lower = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def MacdFastPeriod(self):
return self._macd_fast_period.Value
@MacdFastPeriod.setter
def MacdFastPeriod(self, value):
self._macd_fast_period.Value = value
@property
def MacdSlowPeriod(self):
return self._macd_slow_period.Value
@MacdSlowPeriod.setter
def MacdSlowPeriod(self, value):
self._macd_slow_period.Value = value
@property
def MacdSignalPeriod(self):
return self._macd_signal_period.Value
@MacdSignalPeriod.setter
def MacdSignalPeriod(self, value):
self._macd_signal_period.Value = value
@property
def BollingerPeriod(self):
return self._bollinger_period.Value
@BollingerPeriod.setter
def BollingerPeriod(self, value):
self._bollinger_period.Value = value
@property
def BollingerDeviation(self):
return self._bollinger_deviation.Value
@BollingerDeviation.setter
def BollingerDeviation(self, value):
self._bollinger_deviation.Value = value
def OnReseted(self):
super(band_os_ma_strategy, self).OnReseted()
self._macd_history = []
self._osma_history = []
self._prev_osma = None
self._prev_upper = None
self._prev_lower = None
def OnStarted2(self, time):
super(band_os_ma_strategy, self).OnStarted2(time)
self._macd_history = []
self._osma_history = []
self._prev_osma = None
self._prev_upper = None
self._prev_lower = None
macd = MovingAverageConvergenceDivergence()
macd.ShortMa.Length = self.MacdFastPeriod
macd.LongMa.Length = self.MacdSlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(macd, self._process_candle).Start()
def _process_candle(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
macd_val = float(macd_value)
signal_period = self.MacdSignalPeriod
bb_period = self.BollingerPeriod
bb_dev = float(self.BollingerDeviation)
# Compute signal line manually
self._macd_history.append(macd_val)
while len(self._macd_history) > signal_period:
self._macd_history.pop(0)
if len(self._macd_history) < signal_period:
return
signal = sum(self._macd_history) / signal_period
osma = macd_val - signal
# Compute Bollinger Bands on OsMA
self._osma_history.append(osma)
while len(self._osma_history) > bb_period:
self._osma_history.pop(0)
if len(self._osma_history) < bb_period:
return
mean = sum(self._osma_history) / len(self._osma_history)
variance = sum((x - mean) ** 2 for x in self._osma_history) / len(self._osma_history)
std_dev = math.sqrt(variance)
upper = mean + bb_dev * std_dev
lower = mean - bb_dev * std_dev
if self._prev_osma is not None and self._prev_upper is not None and self._prev_lower is not None:
# Buy: OsMA crosses below lower band
if self._prev_osma > self._prev_lower and osma <= lower and self.Position <= 0:
self.BuyMarket()
# Sell: OsMA crosses above upper band
elif self._prev_osma < self._prev_upper and osma >= upper and self.Position >= 0:
self.SellMarket()
self._prev_osma = osma
self._prev_upper = upper
self._prev_lower = lower
def CreateClone(self):
return band_os_ma_strategy()