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
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ema_cross_trailing_strategy(Strategy):
"""
EMA crossover strategy with trailing stop.
Buys when fast EMA crosses above slow EMA, sells on opposite crossover.
"""
def __init__(self):
super(ema_cross_trailing_strategy, self).__init__()
self._fast_ema_length = self.Param("FastEmaLength", 5) \
.SetDisplay("Fast EMA", "Length of the fast EMA", "Indicator")
self._slow_ema_length = self.Param("SlowEmaLength", 60) \
.SetDisplay("Slow EMA", "Length of the slow EMA", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Time frame for candles and EMAs", "General")
self._current_direction = 0
self._has_initial_direction = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ema_cross_trailing_strategy, self).OnReseted()
self._current_direction = 0
self._has_initial_direction = False
def OnStarted2(self, time):
super(ema_cross_trailing_strategy, self).OnStarted2(time)
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_ema_length.Value
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self._slow_ema_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_ema, slow_ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_val)
slow_val = float(slow_val)
if fast_val > slow_val:
new_direction = 1
elif fast_val < slow_val:
new_direction = -1
else:
return
if not self._has_initial_direction:
self._current_direction = new_direction
self._has_initial_direction = True
return
if new_direction == self._current_direction:
return
prev_direction = self._current_direction
self._current_direction = new_direction
if new_direction == 1 and prev_direction == -1:
if self.Position < 0:
self.BuyMarket()
if self.Position <= 0:
self.BuyMarket()
elif new_direction == -1 and prev_direction == 1:
if self.Position > 0:
self.SellMarket()
if self.Position >= 0:
self.SellMarket()
def CreateClone(self):
return ema_cross_trailing_strategy()