Открыть на GitHub

Стратегия EMA Cross Trailing

Обзор

Стратегия представляет собой портирование советника MetaTrader 4 MQL/8606/EMA_CROSS_2.mq4 в инфраструктуру StockSharp. Она сохраняет исходную идею — отслеживать взаимное положение медленной и быстрой экспоненциальных скользящих средних и открывать единственную позицию при смене направления. Управление тейк-профитом, стоп-лоссом и трейлинг-стопом реализовано через высокоуровневый метод StartProtection, что позволяет повторить логику оригинала и одновременно следовать рекомендациям StockSharp.

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

  • Формируются свечи заданного таймфрейма CandleType (по умолчанию 15 минут) и на их основе рассчитываются две EMA: медленная с периодом SlowEmaLength и быстрая с периодом FastEmaLength.
  • Ведётся учёт последнего направления (медленная EMA выше или ниже быстрой). Первая завершённая свеча после формирования индикаторов используется только для инициализации направления — аналог переменной first_time в MQL.
  • Когда медленная EMA поднимается выше быстрой (новое направление равно 1) и стратегия находится вне позиции, отправляется рыночная заявка на покупку. Когда медленная EMA опускается ниже быстрой (новое направление 2), отправляется рыночная заявка на продажу. Тем самым полностью воспроизводится поведение функции Crossed(LEma, SEma) из исходного кода.
  • Одновременно может существовать только одна позиция. Пока ордер на вход исполняется либо позиция открыта, все последующие сигналы игнорируются.

Управление сделкой и рисками

  • StartProtection задаёт параметры тейк-профита, стоп-лосса и трейлинг-стопа в абсолютных ценовых шагах, вычисленных из PriceStep инструмента. Значение TrailingStopPips = 0 отключает трейлинг.
  • Для входов используются BuyMarket/SellMarket, а закрытие происходит рыночными ордерами при достижении защитных уровней, что отражает логику циклов OrderSend и OrderModify в советнике.
  • Параметр OrderVolume задаёт базовый объём позиции. Перед отправкой заявки значение корректируется в соответствии с шагом объёма, минимальными и максимальными ограничениями инструмента.

Параметры

Параметр Описание
TakeProfitPips Расстояние в пунктах до тейк-профита. Значение по умолчанию: 20.
StopLossPips Расстояние в пунктах до стоп-лосса. Значение по умолчанию: 30.
TrailingStopPips Длина трейлинг-стопа в пунктах. Значение 0 выключает трейлинг. По умолчанию: 50.
OrderVolume Объём входа (в лотах) до выравнивания по шагу. Значение по умолчанию: 2.
FastEmaLength Период быстрой EMA по ценам закрытия. Значение по умолчанию: 5.
SlowEmaLength Период медленной EMA по ценам закрытия. Значение по умолчанию: 60.
CandleType Таймфрейм свечей. Значение по умолчанию: 15 минут.

Примечания

  • Проверка Bars < 100 из MQL заменена ожиданием формирования обеих EMA, что обеспечивает такую же устойчивость сигналов.
  • Трейлинг-стоп обслуживается встроенным модулем защиты. Благодаря этому нет необходимости вручную вызывать OrderModify, как это делалось в оригинальной версии.
  • Python-реализация не создавалась по запросу — в директории присутствует только C# версия.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// EMA crossover strategy with trailing stop.
/// Buys when fast EMA crosses above slow EMA, sells on the opposite crossover.
/// </summary>
public class EmaCrossTrailingStrategy : Strategy
{
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<DataType> _candleType;

	private int _currentDirection;
	private bool _hasInitialDirection;

	public EmaCrossTrailingStrategy()
	{
		_fastEmaLength = Param(nameof(FastEmaLength), 5)
			.SetDisplay("Fast EMA", "Length of the fast exponential moving average.", "Indicator");

		_slowEmaLength = Param(nameof(SlowEmaLength), 60)
			.SetDisplay("Slow EMA", "Length of the slow exponential moving average.", "Indicator");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Time frame used to build candles and EMAs.", "General");
	}

	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

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

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

		_currentDirection = 0;
		_hasInitialDirection = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fastEma, slowEma, 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 fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Determine direction: 1 = fast above slow (bullish), -1 = fast below slow (bearish)
		var newDirection = fastValue > slowValue ? 1 : fastValue < slowValue ? -1 : 0;

		if (newDirection == 0)
			return;

		if (!_hasInitialDirection)
		{
			_currentDirection = newDirection;
			_hasInitialDirection = true;
			return;
		}

		if (newDirection == _currentDirection)
			return;

		var prevDirection = _currentDirection;
		_currentDirection = newDirection;

		// Crossover detected
		if (newDirection == 1 && prevDirection == -1)
		{
			// Bullish crossover
			if (Position < 0)
				BuyMarket(); // Close short
			if (Position <= 0)
				BuyMarket(); // Open long
		}
		else if (newDirection == -1 && prevDirection == 1)
		{
			// Bearish crossover
			if (Position > 0)
				SellMarket(); // Close long
			if (Position >= 0)
				SellMarket(); // Open short
		}
	}
}