Ver en GitHub

Three EMA Strategy

Overview

The strategy reproduces the MetaTrader "ThreeEMA" expert advisor by stacking three exponential moving averages (EMAs). It looks for directional alignment between a fast, medium, and slow EMA on the same timeframe. When the averages are strictly ordered in ascending fashion (fast above medium above slow) the strategy opens or maintains a long position. When the order flips (fast below medium below slow) it opens or maintains a short position. Protective stop-loss and take-profit offsets mirror the original MQL parameters and are expressed in price points relative to the instrument's tick size.

Original MQL behaviour

The MQL version instantiated three EMA indicators (FastPeriod, MediumPeriod, SlowPeriod) and generated trading signals based on their relative ordering on the most recently closed bar:

  • Open long / close short when FastEMA > MediumEMA > SlowEMA.
  • Open short / close long when FastEMA < MediumEMA < SlowEMA.
  • Stop-loss and take-profit were applied as fixed distances in points from the entry price.

Orders were submitted with market execution and the money management block used a fixed lot size. The trailing module was disabled.

StockSharp implementation details

  • Uses the high-level candle subscription API. Three ExponentialMovingAverage indicators are bound to the main timeframe subscription so every finished candle delivers all EMA values simultaneously.
  • Trading decisions are evaluated only on fully formed candles to avoid intrabar noise.
  • Whenever a directional stack appears, the strategy cancels any working orders, closes the opposite exposure if necessary, and opens a new market position in the required direction.
  • StartProtection converts the configured point-based stop-loss and take-profit distances into actual price offsets using the instrument's PriceStep. This mirrors the protective behaviour from the original EA.
  • Chart integration draws candles and all three EMAs when a chart area is available, making it easy to validate signals visually.

Parameters

Name Default Description
CandleType 1-minute time frame Time frame of the candle subscription used for EMAs.
FastPeriod 5 Length of the fast EMA. Must be lower than MediumPeriod.
MediumPeriod 12 Length of the medium EMA. Must be between the fast and slow periods.
SlowPeriod 24 Length of the slow EMA. Must be the highest period value.
StopLossPoints 400 Protective stop-loss distance expressed in instrument points (converted to price using PriceStep). Set to zero to disable.
TakeProfitPoints 900 Take-profit distance in instrument points (converted to price using PriceStep). Set to zero to disable.

Usage notes

  1. Configure Volume before starting the strategy to reflect the desired order size (the original EA used fixed lots).
  2. Ensure the EMA periods remain strictly increasing; otherwise an exception is thrown during OnStarted to match the validation found in the MQL source.
  3. Because the logic always flips positions when the EMA stack reverses, the strategy is continuously market-exposed whenever conditions alternate between bullish and bearish alignments.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class ThreeEmaStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public ThreeEmaStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}