View on GitHub

Eliot Wave Strategy (Ported from MQL4 "Eliot Wave I")

Overview

The Eliot Wave Strategy is a StockSharp API port of the original MetaTrader 4 expert advisor "Eliot Wave I". The system combines a fast/slow Linear Weighted Moving Average (LWMA) crossover with multi-timeframe momentum confirmation and a very slow MACD filter. The goal is to identify impulsive moves in the direction of the prevailing trend while keeping risk constrained through built-in protective rules.

Core Indicators

  • Fast LWMA (default 6) — tracks short-term direction using typical price (High + Low + Close) / 3.
  • Slow LWMA (default 85) — measures broader trend on the same timeframe.
  • Momentum (default period 14) — evaluated on a higher timeframe and converted to a deviation relative to the neutral level 100. A reading above the configured threshold indicates a sufficiently strong impulse.
  • MACD (12, 26, 9) — calculated on a very slow timeframe (monthly by default) and used as a long-term filter. The strategy only buys when the MACD main line is above the signal line and sells when it is below.

Parameters

Name Description Default
Base Candle Primary timeframe for LWMA processing. 15-minute candles
Momentum Candle Higher timeframe used for the momentum confirmation. 1-hour candles
MACD Candle Very slow timeframe for the MACD trend filter. 30-day candles
Fast LWMA Length of the fast linear weighted moving average. 6
Slow LWMA Length of the slow linear weighted moving average. 85
Momentum Period Lookback for the momentum indicator on the confirmation timeframe. 14
Momentum Buy Threshold Minimum deviation above 100 required to validate a long setup. 0.3
Momentum Sell Threshold Minimum deviation above 100 required to validate a short setup. 0.3
Stop Loss (pts) Protective stop distance expressed in instrument points. 20
Take Profit (pts) Target distance expressed in instrument points. 50
Trade Volume Order size for each entry. 1 lot
Max Position Absolute net exposure allowed; prevents the strategy from exceeding the MQL EA's Max_Trades limit. 10 lots

All parameters are implemented as StrategyParam<T> so they can be optimised directly in Designer or Runner.

Trading Rules

  1. Trend and structure filter
    • Fast LWMA must stay above the slow LWMA to consider long trades.
    • Fast LWMA must stay below the slow LWMA to consider shorts.
    • The last two completed candles must overlap (Low[2] < High[1] for buys, Low[1] < High[2] for sells), replicating the consolidation requirement from the EA.
  2. Momentum confirmation
    • The higher timeframe momentum is transformed into abs(momentum - 100) values.
    • If any of the last three values exceeds the configured threshold the impulse is considered valid.
  3. Macro trend filter
    • Buy trades require the MACD main line to be above the signal line on the slow timeframe.
    • Sell trades require the MACD main line to be below the signal line.
  4. Order execution
    • When all conditions align the strategy sends a market order sized to reverse the current position and add the configured trade volume.
    • Position flips are supported so the behaviour matches the averaging logic of the original EA.

Risk Management

  • StartProtection automatically applies stop-loss and take-profit distances in instrument points.
  • Additional exit logic closes long positions when the fast LWMA drops below the slow LWMA or when the MACD filter turns bearish (and vice versa for shorts). This mirrors the MQL exit blocks.
  • The Max Position parameter prevents the strategy from accumulating exposure beyond the configured limit, respecting the EA's Max_Trades restriction.

Differences from the Original EA

  • Graphical trend-line checks and manual trade notifications were removed because they are specific to MetaTrader and have no StockSharp equivalent.
  • Break-even and complex trailing-stop variants from the MQL script are replaced by the simpler StartProtection mechanism. Users can extend the strategy if those behaviours are required.
  • Money-based equity protection is not implemented; risk is controlled through fixed stops and the position cap.

Usage Notes

  1. Attach the strategy to a liquid instrument and ensure the three candle streams are available.
  2. Set the Trade Volume, stop/target distances, and thresholds according to the traded market's volatility.
  3. Optimise thresholds separately for bullish and bearish impulses if the instrument exhibits asymmetric behaviour.
  4. Consider enabling the built-in chart visuals (candles, LWMAs, trade markers) for easier debugging.

This port focuses on reproducing the signal logic of the original EA using the high-level StockSharp API while keeping the implementation idiomatic and easy to maintain.

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 EliotWaveStrategy : 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 EliotWaveStrategy()
	{
		_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;
	}
}