Открыть на GitHub

Стратегия MACD Sample Trend Filter

Стратегия представляет собой перенос классического советника MACD Sample из MetaTrader 5. Используются пересечения MACD, дополненные фильтрацией по EMA. Размер позиции задаётся свойством Volume, а управление риском основано на настраиваемых порогах MACD, дистанции тейк-профита и трейлинг-стопа (в пунктах).

Основная логика

  • Индикаторы
    • MovingAverageConvergenceDivergenceSignal с периодами (12, 26, 9) для расчёта линий MACD и сигнальной линии.
    • ExponentialMovingAverage с периодом 26 для определения направления тренда.
  • Условия входа
    • Покупка: MACD находится ниже нуля, пересекает сигнальную линию снизу вверх, его абсолютное значение превышает MACD Open Level, а EMA растёт.
    • Продажа: MACD находится выше нуля, пересекает сигнальную линию сверху вниз, его значение превышает MACD Open Level, а EMA снижается.
  • Условия выхода
    • MACD пересекает сигнальную линию в противоположном направлении с амплитудой выше MACD Close Level.
    • Цена достигает тейк-профита, отстоящего от цены входа на заданное число пунктов.
    • Активированный трейлинг-стоп срабатывает.
  • Работа трейлинг-стопа
    • Для длинной позиции трейлинг активируется, когда максимум свечи превышает цену входа на расстояние трейлинга. Стоп переносится на high − trailing distance и далее подтягивается только вверх.
    • Для короткой позиции трейлинг активируется при снижении минимума на требуемое расстояние и фиксируется на low + trailing distance, подтягиваясь только вниз.

Параметры

Параметр Значение по умолчанию Описание
FastPeriod 12 Период быстрой EMA в MACD.
SlowPeriod 26 Период медленной EMA в MACD.
SignalPeriod 9 Период сигнальной EMA в MACD.
TrendPeriod 26 Период EMA, используемой как трендовый фильтр.
MacdOpenLevelPips 3 Минимальная величина MACD (в пунктах) для открытия позиции.
MacdCloseLevelPips 2 Минимальная величина MACD (в пунктах) для закрытия позиции по пересечению.
TakeProfitPips 50 Дистанция тейк-профита в пунктах.
TrailingStopPips 30 Дистанция трейлинг-стопа в пунктах. Значение 0 отключает трейлинг.
CandleType Свечи 15 минут Тип свечей для расчётов.

Пересчёт пунктов

В оригинальном советнике значения умножались на 10 для инструментов с 3 или 5 знаками после запятой. Перенос использует такое же правило на основе Security.PriceStep:

  • Если шаг цены содержит 3 или 5 десятичных знаков, пункт = PriceStep * 10.
  • Иначе пункт равен PriceStep.
  • При отсутствии данных о шаге цены параметры рассматриваются как абсолютные ценовые расстояния и требуют ручной настройки.

Особенности реализации

  • Закрытие позиций выполняется до проверки новых сигналов, что повторяет алгоритм MT5.
  • Вызовы LogInfo фиксируют входы, выходы и обновления трейлинг-стопа для удобства диагностики.
  • Стратегия не выставляет защитные ордера автоматически; управление позициями реализовано в методе ProcessCandle.
  • Свойство Volume задаёт базовый объём. При смене направления стратегия добавляет Math.Abs(Position) для закрытия противоположной позиции.

Отличия от версии MQL5

  • Обработка выполняется на закрытых свечах, а не на каждом тике, что уменьшает количество повторных сигналов и повышает детерминированность.
  • Для имитации срабатывания тейк-профита и трейлинга используются максимумы и минимумы свечей, приближая их к котировкам Bid/Ask в MetaTrader.
  • При отсутствии Security.PriceStep пороговые значения трактуются как абсолютные цены и должны быть откорректированы под конкретный инструмент.

Перед запуском на другом рынке убедитесь, что выбранный таймфрейм и пороги пунктов соответствуют шагу цены торгуемого инструмента.

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>
/// MACD strategy with EMA trend filter.
/// Buys when MACD histogram is positive and price is above trend EMA.
/// Sells when MACD histogram is negative and price is below trend EMA.
/// </summary>
public class MacdSampleTrendFilterStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _trendPeriod;

	private int _prevSignal;

	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 TrendPeriod
	{
		get => _trendPeriod.Value;
		set => _trendPeriod.Value = value;
	}

	public MacdSampleTrendFilterStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

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

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

		_trendPeriod = Param(nameof(TrendPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("Trend Period", "Trend EMA period", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevSignal = 0;
	}

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

		_prevSignal = 0;

		var fastEma = new ExponentialMovingAverage { Length = FastPeriod };
		var slowEma = new ExponentialMovingAverage { Length = SlowPeriod };
		var trendEma = new ExponentialMovingAverage { Length = TrendPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, trendEma, ProcessCandle)
			.Start();

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

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var close = candle.ClosePrice;
		var macdLine = fast - slow;

		// Signal: MACD positive + price above trend = bullish; MACD negative + price below trend = bearish
		var signal = 0;
		if (macdLine > 0 && close > trend)
			signal = 1;
		else if (macdLine < 0 && close < trend)
			signal = -1;

		if (signal == _prevSignal)
			return;

		var oldSignal = _prevSignal;
		_prevSignal = signal;

		if (signal == 1 && oldSignal <= 0)
		{
			if (Position < 0)
				BuyMarket();
			if (Position <= 0)
				BuyMarket();
		}
		else if (signal == -1 && oldSignal >= 0)
		{
			if (Position > 0)
				SellMarket();
			if (Position >= 0)
				SellMarket();
		}
	}
}