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
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.
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.
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.
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
Attach the strategy to a liquid instrument and ensure the three candle streams are available.
Set the Trade Volume, stop/target distances, and thresholds according to the traded market's
volatility.
Optimise thresholds separately for bullish and bearish impulses if the instrument exhibits
asymmetric behaviour.
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;
}
}
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 eliot_wave_strategy(Strategy):
def __init__(self):
super(eliot_wave_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(eliot_wave_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(eliot_wave_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return eliot_wave_strategy()