Auf GitHub ansehen

Graal EMA Momentum Strategy

This strategy is a conversion of the MetaTrader 4 expert advisor 0Graal-CROSSmuvingi. It trades trend reversals that occur when a fast exponential moving average (EMA) on closing prices crosses a slower EMA calculated on opening prices. A momentum oscillator confirms the breakout direction, and a fixed-distance take profit replicates the original MT4 execution model.

Trading Idea

  1. Fast EMA on close tracks the most recent price action.
  2. Slow EMA on open lags behind and forms the crossover baseline.
  3. Momentum oscillator (period 14) measures how strongly price accelerates away from the neutral value (100). The strategy only trades when momentum deviates from 100 by more than a configurable filter and continues to strengthen in the same direction.
  4. Take profit closes trades after a predefined distance measured in instrument points, mirroring the MT4 TakeProfit parameter.

Entry Rules

  • Long setup
    • The fast EMA crosses above the slow EMA on the current finished candle while the previous bar had the fast EMA below or equal to the slow EMA.
    • Momentum (value minus 100) is greater than the MomentumFilter threshold and also higher than the previous bar's momentum reading.
    • Existing short positions are closed before opening a new long. The new long size equals the configured Volume plus any amount required to flip an open short.
  • Short setup
    • The fast EMA crosses below the slow EMA while the previous bar had the fast EMA above or equal to the slow EMA.
    • Momentum (value minus 100) is below the negative MomentumFilter threshold and less than the previous bar's momentum reading.
    • Existing long positions are closed before opening a new short. The new short size equals the configured Volume plus the quantity needed to cover an open long.

Exit Rules

  • Positions are closed automatically when price reaches the calculated take-profit target (TakeProfitPoints * PriceStep).
  • A new opposite signal also reverses the position immediately because the order size always includes the quantity of the current position.

Parameters

Name Description Default
FastPeriod Length of the EMA on closing prices. 13
SlowPeriod Length of the EMA on opening prices. 34
MomentumPeriod Momentum oscillator lookback. 14
MomentumFilter Minimum absolute momentum deviation from 100 required to trade. 0.1
TakeProfitPoints Distance to the profit target in price points (multiplied by PriceStep). 200
CandleType Candle data type used for calculations (15-minute timeframe by default). 15-minute time frame
Volume Order size used for new entries. The engine inherits it from the base class. 1

Implementation Notes

  • Signals are processed on closed candles only (CandleStates.Finished).
  • The strategy subscribes to the chosen candle type with SubscribeCandles and binds both EMA and momentum indicators via the high-level API.
  • The slow EMA is manually updated with opening prices inside the bind callback to replicate the MT4 behavior where PRICE_OPEN was used.
  • Take-profit management watches intrabar highs and lows to emulate MT4's point-based exit logic.
  • StartProtection() is enabled on start to guard against unexpected open positions before the strategy begins trading.
using System;

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

namespace StockSharp.Samples.Strategies;

public class GraalEmaMomentumStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _momentumPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int MomentumPeriod { get => _momentumPeriod.Value; set => _momentumPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public GraalEmaMomentumStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 20).SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 80).SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
		_momentumPeriod = Param(nameof(MomentumPeriod), 14).SetDisplay("Momentum", "Momentum period", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 100).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = default;
		_prevSlow = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = 0;
		_prevSlow = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var mom = new Momentum { Length = MomentumPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, mom, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal mom)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_hasPrev) { _prevFast = fast; _prevSlow = slow; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_prevFast <= _prevSlow && fast > slow && mom > 0 && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevFast >= _prevSlow && fast < slow && mom < 0 && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevFast = fast;
		_prevSlow = slow;
	}
}