Открыть на GitHub

Стратегия OsMA Four Colors Arrow

Обзор

Стратегия переносит советник MetaTrader «OsMA Four Colors Arrow» в инфраструктуру StockSharp. В оригинале точки входа генерируются стрелками одноимённого индикатора, который отслеживает смену фаз гистограммы OsMA (MACD). В версии для StockSharp то же поведение моделируется через отслеживание переходов гистограммы через ноль: переход снизу вверх формирует длинный сигнал, сверху вниз — короткий. Дополнительный режим инверсии позволяет мгновенно переключиться на контртрендовую логику.

Алгоритм работает только по закрытым свечам и может ограничивать торговлю дневным временным окном, как это делалось в MQL-варианте. Встроенное управление позицией включает настраиваемый объём сделки, ограничение общего размера позиции и автоматическое сопровождение стоп-лосс/тейк-профит/трейлинг-стопа в пипсах.

Логика торговли

  1. Подписка на выбранный таймфрейм и расчёт гистограммы MACD (OsMA) с настраиваемыми периодами EMA.
  2. После закрытия свечи проверяется знак гистограммы:
    • Переход гистограммы в положительную область ⇒ «бычья» стрелка ⇒ сигнал на покупку.
    • Переход в отрицательную область ⇒ «медвежья» стрелка ⇒ сигнал на продажу.
  3. Перед отправкой заявки применяются дополнительные фильтры:
    • Ограничение направления (только покупки, только продажи либо обе стороны).
    • Инверсия сигналов.
    • Закрытие встречной позиции перед открытием новой.
    • Запрет усреднения при включённом параметре «OnlyOnePosition» и контроль общего лимита позиций.
  4. Рыночные заявки отправляются с заданным объёмом. Метод StartProtection преобразует параметры в пипсах в абсолютные цены и управляет стопами и трейлингом автоматически.
  5. При активированном фильтре времени сигналы, попадающие вне торговой сессии, игнорируются.

Параметры

Имя Описание
CandleType Таймфрейм расчётов и поиска сигналов.
FastPeriod / SlowPeriod / SignalPeriod Периоды EMA для гистограммы MACD (OsMA).
StopLossPips / TakeProfitPips Размеры стоп-лосса и тейк-профита в пипсах (0 — выключено).
TrailingActivatePips Прибыль в пипсах, после которой включается трейлинг.
TrailingStopPips Дистанция трейлинг-стопа в пипсах (0 — отключено).
TrailingStepPips Дополнительный профит для очередного подтягивания трейлинга.
MaxPositions Максимальное количество агрегированных позиций (в объёмах TradeVolume). 0 — без ограничений.
ReverseSignals Инверсия сигналов.
DirectionMode Разрешённое направление сделок.
CloseOppositePositions Закрывать встречную позицию перед новым входом.
OnlyOnePosition Запрет на добавление к уже открытой позиции в том же направлении.
UseTimeControl Включение фильтра торговых часов.
StartHour, StartMinute, EndHour, EndMinute Начало и конец сессии (поддерживается переход через полуночь).
TradeVolume Объём рыночной заявки в лотах.

Особенности

  • Параметры трейлинг-стопа повторяют логику советника: сначала требуется пройти TrailingActivatePips, затем стоп подтягивается с шагом TrailingStepPips.
  • Для корректного пересчёта пипсов инструмент должен предоставлять PriceStep и Decimals. При отсутствии данных используется единица цены.
  • Если MaxPositions больше нуля, стратегия может наращивать позицию пакетами по TradeVolume, не превышая заданный предел.
  • При включённом фильтре времени совпадающие значения начала и конца сессии блокируют торговлю, чтобы избежать двусмысленности.
  • Работа ведётся только по закрытым свечам, что соответствует исходному MQL-советнику.
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;

/// <summary>
/// Simplified from "OsMA Four Colors Arrow" MetaTrader expert.
/// Uses MACD histogram (OsMA) zero-crossing as entry signal.
/// Buys when histogram crosses above zero, sells when it crosses below.
/// </summary>
public class OsMaFourColorsArrowStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _signalPeriod;

	private MovingAverageConvergenceDivergence _macd;
	private readonly Queue<decimal> _macdHistory = new();
	private decimal? _prevHistogram;

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

	public int FastPeriod
	{
		get => _fastPeriod.Value;
		set => _fastPeriod.Value = value;
	}

	public int SlowPeriod
	{
		get => _slowPeriod.Value;
		set => _slowPeriod.Value = value;
	}

	public int SignalPeriod
	{
		get => _signalPeriod.Value;
		set => _signalPeriod.Value = value;
	}

	public OsMaFourColorsArrowStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for signal generation", "General");

		_fastPeriod = Param(nameof(FastPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA length for MACD", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA length for MACD", "Indicators");

		_signalPeriod = Param(nameof(SignalPeriod), 12)
			.SetGreaterThanZero()
			.SetDisplay("Signal Period", "Signal line smoothing period", "Indicators");
	}

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

		_prevHistogram = null;
		_macdHistory.Clear();

		_macd = new MovingAverageConvergenceDivergence
		{
			ShortMa = { Length = FastPeriod },
			LongMa = { Length = SlowPeriod },
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_macd, ProcessCandle)
			.Start();

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

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

		if (!_macd.IsFormed)
			return;

		// Compute signal line as SMA of MACD values
		_macdHistory.Enqueue(macdValue);
		if (_macdHistory.Count > SignalPeriod)
			_macdHistory.Dequeue();

		if (_macdHistory.Count < SignalPeriod)
		{
			_prevHistogram = null;
			return;
		}

		decimal sum = 0;
		var history = _macdHistory.ToArray();
		foreach (var v in history)
			sum += v;
		var signal = sum / history.Length;

		// OsMA = MACD - Signal (histogram)
		var histogram = macdValue - signal;

		if (_prevHistogram is null)
		{
			_prevHistogram = histogram;
			return;
		}

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		var crossUp = _prevHistogram.Value <= 0 && histogram > 0;
		var crossDown = _prevHistogram.Value >= 0 && histogram < 0;

		if (crossUp)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (crossDown)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		_prevHistogram = histogram;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_macd = null;
		_prevHistogram = null;
		_macdHistory.Clear();

		base.OnReseted();
	}
}