GitHub で見る

EMA Cross Trailing Strategy

Overview

This strategy is the StockSharp conversion of the MetaTrader 4 expert advisor located at MQL/8606/EMA_CROSS_2.mq4. It preserves the original idea of tracking the relationship between a slow and a fast exponential moving average and opening a single market position when a crossover occurs. Protective exits (take profit, stop loss and trailing stop) are handled through the high-level StartProtection helper so the behaviour mirrors the MetaTrader implementation while using StockSharp best practices.

Trading logic

  • Build candles with the configurable CandleType (15-minute bars by default) and feed two EMA indicators: the slow EMA uses SlowEmaLength and the fast EMA uses FastEmaLength.
  • Maintain the latest direction of the slow EMA relative to the fast EMA. The first completed candle after both indicators are formed is used only to initialise this direction, just like the first_time guard in the original advisor.
  • When the slow EMA moves above the fast EMA (new direction becomes 1) and the strategy is flat, send a market buy order. When the slow EMA moves below the fast EMA (new direction becomes 2) and the strategy is flat, send a market sell order. This reproduces the exact up/down mapping of the MQL function Crossed(LEma, SEma).
  • Only one position can be active at a time. While a trade is open (or the entry order is still pending), additional crossovers are ignored.

Trade and risk management

  • StartProtection configures take profit, stop loss and trailing stop distances in price units computed from the instrument PriceStep. Trailing stops are optional: set TrailingStopPips to zero to disable them.
  • Orders are placed with BuyMarket/SellMarket and closed by market when any protective level is triggered, exactly like the OrderSend and trailing logic from the original advisor.
  • The base lot size is controlled by OrderVolume. Before each entry it is aligned to the instrument volume step, minimum and maximum to avoid rejection.

Parameters

Parameter Description
TakeProfitPips Distance in pips (price steps) used for the protective take profit. Default: 20.
StopLossPips Distance in pips used for the protective stop loss. Default: 30.
TrailingStopPips Trailing distance in pips. Set to 0 to disable trailing. Default: 50.
OrderVolume Lot size of the market entries before alignment. Default: 2.
FastEmaLength Period of the fast EMA applied to closing prices. Default: 5.
SlowEmaLength Period of the slow EMA applied to closing prices. Default: 60.
CandleType Time-frame for candle building. Default: 15 minutes.

Notes

  • The strategy waits until both EMAs are fully formed before reacting to a crossover, removing the Bars < 100 check from the MQL script while achieving the same stability.
  • Because only market orders are used, there are no individual OrderModify calls. The built-in protection module automatically repositions the trailing stop in the same way the MetaTrader loop updated OrderStopLoss.
  • No Python port is provided (per request); only the C# implementation is included.
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
		}
	}
}