Открыть на GitHub

Стратегия BandOsMaCustom

Общее описание

Стратегия портирована из MetaTrader 5 эксперт-советника MQL/45596/mql5/Experts/MQL5Book/p7/BandOsMACustom.mq5. Оригинальный робот использует гистограмму MACD (OsMA), строит поверх неё полосы Боллинджера и дополнительную скользящую среднюю. Входы выполняются при пробое полос Боллинджера самой гистограммой, а выходы — при пересечении гистограммой скользящей средней. Все расстояния измеряются в пунктах инструмента, стоп и трейлинг-стоп совпадают по величине, а шаг подтягивания равен StopLoss / 50.

Версия на StockSharp сохраняет ту же логику и реализована полностью на высокоуровневом API, чтобы облегчить сопровождение и визуализацию.

Особенности конверсии

  • Гистограмма MACD реализована через MovingAverageConvergenceDivergenceHistogram и питается ценой, выбранной параметром AppliedPrice (аналог MetaTrader PRICE_*).
  • Полосы Боллинджера и выходная скользящая средняя получают на вход значения OsMA, поэтому хранится компактная история, имитирующая параметры смещения BandsShift и MaShift без прямого обращения к прошлым барам индикатора.
  • Торговая логика повторяет исходную: пробой нижней полосы создаёт бычий сигнал, пробой верхней — медвежий, пересечение с выходной средней сбрасывает сигнал и позволяет закрыть позицию.
  • Метод StartProtection задаёт стоп-лосс и трейлинг-стоп с шагом StopLossPoints / 50, полностью повторяя класс TrailingStop из MQL.

Используемые индикаторы

Индикатор Назначение
MovingAverageConvergenceDivergenceHistogram Полный аналог iOsMA из MetaTrader.
BollingerBands Полосы Боллинджера для гистограммы OsMA.
Скользящая средняя (SMA/EMA/SMMA/LWMA) Фильтр для закрытия позиций.

Параметры

Имя Значение по умолчанию Описание
CandleType Часовые свечи Базовый таймфрейм для всех расчётов.
FastOsmaPeriod 12 Быстрая EMA в расчёте OsMA.
SlowOsmaPeriod 26 Медленная EMA в расчёте OsMA.
SignalPeriod 9 Период сигнальной SMA OsMA.
AppliedPrice Typical Тип цены (MetaTrader PRICE_*), подаваемой в OsMA.
BandsPeriod 26 Период полос Боллинджера по гистограмме.
BandsShift 0 Смещение полос Боллинджера в барах.
BandsDeviation 2.0 Множитель стандартного отклонения.
MaPeriod 10 Период выходной скользящей средней.
MaShift 0 Смещение скользящей средней в барах.
MaMethod Simple Тип скользящей (SMA/EMA/SMMA/LWMA).
StopLossPoints 1000 Стоп-лосс в шагах цены.
OrderVolume 0.01 Объём позиции, эквивалент вводу Lots в MetaTrader.

Правила торговли

  1. Подписаться на выбранный поток свечей и передавать в OsMA цену согласно AppliedPrice.
  2. Каждое новое значение OsMA направлять в полосы Боллинджера и выходную скользящую среднюю.
  3. Формировать сигналы с учётом смещённых буферов:
    • OsMA пересекает нижнюю полосу сверху вниз — фиксируется бычий сигнал.
    • OsMA пересекает верхнюю полосу снизу вверх — фиксируется медвежий сигнал.
    • OsMA пересекает выходную среднюю — сигнал сбрасывается.
  4. Управление позициями:
    • При отсутствии соответствующего сигнала закрывать текущую позицию.
    • При отсутствии позиции открывать сделку по активному сигналу.
  5. Включить защиту через StartProtection, передав рассчитанный стоп-лосс и трейлинг-стоп с шагом StopLossPoints / 50.

Дополнительно

  • Все комментарии в коде написаны на английском языке согласно политике репозитория.
  • Исторические буферы позволяют воспроизвести поведение MetaTrader по параметрам смещения индикаторов, не обращаясь к значениям по индексам.
  • Стратегия использует только высокоуровневые вызовы StockSharp: расчёты выполняет подписка SubscribeCandles, а рыночные заявки оформляются методами BuyMarket и SellMarket.
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();
	}
}