EMA WMA RSI is a conversion of the MetaTrader 4 expert advisor "EMA WMA RSI" created by cmillion. The original robot compares an exponential moving average (EMA) and a linear weighted moving average (WMA) calculated from candle opens, and filters every crossover with a Relative Strength Index (RSI) threshold. The StockSharp port keeps the same indicator logic, operates on finished candles, and reproduces the money-management options: optional counter-position flattening, point-based stop-loss/take-profit levels, and a trailing stop that can follow fixed distances, the latest fractal, or recent candle extremes.
The strategy is designed for a single symbol and time frame selected via the Candle Type parameter. It assumes MetaTrader "points" (the minimal tick) when converting risk distances to absolute prices, so instrument metadata such as Security.Step and Security.StepPrice should be filled for best results.
Strategy logic
Indicators
EMA – period defined by EMA Period, applied to candle open prices.
WMA – period defined by WMA Period, also fed with candle opens.
RSI – RSI Period, calculated on the same open-price stream.
All indicators update once per finished candle. The port mirrors the original "bar open" execution by storing the EMA/WMA values from the previous bar and comparing them against the current bar immediately after it closes.
Entry rules
Long setup
Current EMA value is below the WMA, while the previous bar had EMA above WMA (a downward cross).
RSI value is above 50.
If a short position exists, it is optionally closed when Close Counter Trades is enabled; otherwise the signal is ignored until the strategy is flat.
When the conditions hold, a market buy order is sent using either the fixed volume or the risk-based sizing.
Short setup – symmetrical logic: EMA crosses above WMA, the previous bar showed EMA below WMA, RSI is below 50, and the strategy either flattens a long or skips the trade.
Exit rules
Initial protection – Stop Loss (points) and Take Profit (points) translate to absolute distances using the instrument tick size. Either value can be set to zero to disable it.
Trailing stop
If Trailing Stop (points) is greater than zero, the stop follows price at a fixed distance measured from the latest close (only tightening, never loosening).
If the trailing distance is zero, the algorithm searches for adaptive levels:
Trailing Source = CandleExtremes looks back through previous candle highs/lows. A long stop moves to the first low at least five points below the current price; a short stop uses highs five points above.
Trailing Source = Fractals scans previously confirmed Bill Williams fractals (two candles on each side). The same five-point buffer applies to avoid placing the stop too close to the current price.
Trailing adjustments only activate after price moves beyond the original entry price, reproducing the MetaTrader EA behaviour.
Position exit – When the trailing stop or take-profit is touched within a candle’s range, the position is closed with a market order and the internal state is reset.
Position sizing
Fixed Volume provides the exact market order size (lots/contracts). This is the default, matching the EA parameter Lot.
Setting Fixed Volume to zero activates risk-based sizing. The strategy estimates the monetary risk per unit using the available stop distance (either the configured stop loss or the effective trailing distance) and Security.StepPrice. Risk % determines how much portfolio equity is exposed per trade. If both fixed volume and risk percent are zero the signal is ignored.
Parameters
Parameter
Description
Default
EMA Period
Period of the exponential moving average applied to candle opens.
28
WMA Period
Period of the linear weighted moving average on opens.
8
RSI Period
RSI length used as a directional filter.
14
Stop Loss (points)
Stop-loss offset in MetaTrader points. 0 disables the protective stop.
0
Take Profit (points)
Take-profit offset in points. 0 disables the target.
500
Trailing Stop (points)
Fixed trailing distance in points. 0 switches to adaptive trailing (fractals or candle lows/highs).
70
Trailing Source
Adaptive trailing method: CandleExtremes for raw highs/lows, Fractals for Williams fractals.
CandleExtremes
Close Counter Trades
Close an opposite position before opening a new trade.
false
Fixed Volume
Market order volume. Set to 0 to enable risk-based sizing.
0.1
Risk %
Percent of portfolio equity committed when Fixed Volume is zero. Requires a valid stop distance.
10
Candle Type
Primary timeframe used for indicators and signal evaluation.
30-minute candles
Implementation notes
Price-step conversions rely on Security.Step (or Security.PriceStep) and Security.StepPrice. Provide realistic instrument metadata to keep point-to-price calculations accurate.
The strategy processes only finished candles and uses their open prices for indicator updates, matching the "new bar" logic in the MQL4 code.
Trailing levels keep at least a five-point buffer away from the current price just like the original helper function SlLastBar.
When counter-position closing is disabled, the strategy never hedges—only a single net position is managed at a time.
No Python implementation is included in this package.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// EMA WMA RSI: Dual EMA crossover with RSI filter and ATR stops.
/// </summary>
public class EmaWmaRsiStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public EmaWmaRsiStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 8)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 28)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, rsi, atr, 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 fastVal, decimal slowVal, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 50) { _entryPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal < 50) { _entryPrice = close; SellMarket(); }
}
_prevFast = fastVal; _prevSlow = slowVal;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
class ema_wma_rsi_strategy(Strategy):
def __init__(self):
super(ema_wma_rsi_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 8) \
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 28) \
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(ema_wma_rsi_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
rv = float(rsi_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if (fv < sv and self._prev_fast >= self._prev_slow) or close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if (fv > sv and self._prev_fast <= self._prev_slow) or close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow and rv > 50:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow and rv < 50:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(ema_wma_rsi_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return ema_wma_rsi_strategy()