Открыть на GitHub

Стратегия MACD Not So Sample

Обзор

Стратегия MACD Not So Sample — это порт MetaTrader-советника MACD_Not_So_Sample. Исходный робот работал на графике EURUSD с таймфреймом H4, используя пересечения MACD и сигнальной линии, подтверждённые EMA-трендом, и сопровождал позицию крупным тейк-профитом и трейлинг-стопом. Версия для StockSharp сохраняет эту идею: отрицательный MACD, который пересекает сигнал снизу вверх при растущей EMA, открывает длинную позицию; положительный MACD, который пересекает сигнал сверху вниз при падающей EMA, открывает короткую позицию.

Перенос реализует все элементы управления позицией: установка тейк-профита, пересчёт трейлинг-стопа после достаточного движения цены и выход по обратному пересечению MACD, когда модуль значения превышает заданные уровни. Все вычисления выполняются на закрытых свечах H4, что полностью повторяет поведение MetaTrader.

Торговая логика

  1. Подписаться на таймфрейм CandleType (по умолчанию четырёхчасовые свечи) и обрабатывать только свечи в состоянии Finished.
  2. Рассчитать индикатор MovingAverageConvergenceDivergenceSignal с параметрами FastPeriod, SlowPeriod, SignalPeriod, чтобы получить значения MACD и сигнальной линии.
  3. Построить EMA-тренд с периодом TrendPeriod для фильтрации направлений сделок.
  4. Перевести точечные параметры (MacdOpenLevelPips, MacdCloseLevelPips, TakeProfitPips, TrailingStopPips) в ценовые расстояния с учётом минимального шага цены инструмента.
  5. При отсутствии позиции:
    • Открыть лонг, если MACD ниже нуля и пересёк сигнал снизу вверх, предыдущее значение было ниже сигнала, EMA растёт, а модуль MACD превышает MacdOpenLevelPips.
    • Открыть шорт, если MACD выше нуля и пересёк сигнал сверху вниз, предыдущее значение было выше сигнала, EMA снижается, а модуль MACD превышает MacdOpenLevelPips.
  6. При длинной позиции:
    • Закрыть сделку, когда MACD становится положительным, пересекает сигнал сверху вниз и его величина больше MacdCloseLevelPips.
    • Выйти досрочно при достижении тейк-профита или при срабатывании трейлинг-стопа.
  7. При короткой позиции:
    • Закрыть сделку, когда MACD становится отрицательным, пересекает сигнал снизу вверх и его модуль больше MacdCloseLevelPips.
    • Выйти досрочно при достижении тейк-профита или при срабатывании трейлинг-стопа.
  8. Трейлинг-стоп активируется только после того, как цена пройдёт порог TrailingStopPips, затем уровень сопровождает новые экстремумы свечей, фиксируя прибыль.

Параметры

Название Тип Значение по умолчанию Описание
FastPeriod int 47 Период быстрой EMA в расчёте MACD.
SlowPeriod int 166 Период медленной EMA в расчёте MACD.
SignalPeriod int 11 Период EMA сигнальной линии MACD.
TrendPeriod int 8 Период EMA, используемой как трендовый фильтр.
MacdOpenLevelPips decimal 1 Минимальный модуль MACD (в пунктах) для открытия позиции.
MacdCloseLevelPips decimal 3 Минимальный модуль MACD (в пунктах) для закрытия позиции.
TakeProfitPips decimal 550 Расстояние тейк-профита в пунктах.
TrailingStopPips decimal 19 Шаг трейлинг-стопа в пунктах. Значение 0 отключает трейлинг.
TradeVolume decimal 1 Объём, используемый при рыночных заявках.
CandleType DataType H4 Свечной ряд, который обрабатывает стратегия.
RequiredSecurityCode string EURUSD Ожидаемый тикер инструмента, проверка повторяет поведение MetaTrader.

Отличия от оригинального MetaTrader-советника

  • В MetaTrader каждая сделка — отдельный ордер с MagicNumber. В StockSharp используется чистая позиция, поэтому стратегия закрывает текущую позицию и открывает новую без управления множеством ордеров.
  • В MQL-версии объём зависел от свободной маржи (AccountFreeMargin). В порте используется параметр TradeVolume; управление рисками следует настраивать во внешних компонентах.
  • Трейлинг-стоп реализован через анализ экстремумов свечи, а не модификацию ордеров, но момент срабатывания совпадает с оригинальной логикой.
  • Индикаторы рассчитываются через высокоуровневые классы StockSharp и подписку на свечи, без прямых вызовов iMACD и iMA.

Рекомендации по использованию

  • Перед запуском убедитесь, что код инструмента совпадает с RequiredSecurityCode. При несоответствии стратегия немедленно остановится, чтобы избежать торгов по неверному активу.
  • Параметр TradeVolume копируется в Strategy.Volume в методе OnStarted, поэтому вспомогательные методы (BuyMarket, SellMarket) используют заданный объём.
  • Трейлинг-стоп начинает действовать только после прохождения ценой указанного расстояния; до этого момента закрытие происходит по MACD или тейк-профиту.
  • При добавлении стратегии на график выводятся свечи, оба индикатора и сделки, что упрощает визуальную проверку сигналов.

Индикаторы

  • MovingAverageConvergenceDivergenceSignal (линия MACD и сигнальная линия).
  • ExponentialMovingAverage (трендовый фильтр).
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// MACD Not So Sample: Dual EMA crossover with RSI confirmation and ATR stops.
/// </summary>
public class MacdNotSoSampleStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;

	public MacdNotSoSampleStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");
		_fastEmaLength = Param(nameof(FastEmaLength), 12)
			.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators");
		_slowEmaLength = Param(nameof(SlowEmaLength), 26)
			.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators");
		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");
		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
	public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
	public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
	public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fastEma, slowEma, rsi, atr, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal rsiVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished) return;
		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
		}
		else if (Position < 0)
		{
			if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
		}

		if (Position == 0)
		{
			if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 50) { _entryPrice = close; BuyMarket(); }
			else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal < 50) { _entryPrice = close; SellMarket(); }
		}
		_prevFast = fastVal; _prevSlow = slowVal;
	}
}