This strategy is a direct port of the MetaTrader expert advisor EA_MARSI_1-02. It trades crossovers between two copies of
Integer's custom EMA_RSI_VA indicator, a volatility-adaptive moving average driven by the Relative Strength Index (RSI).
Whenever the slow line crosses the fast line the engine reverses the net position, reproducing the original "flip on crossover"
behaviour while respecting StockSharp's order handling best practices.
Indicator mechanics
The original MQL package ships with a custom indicator called EMA_RSI_VA. It calculates a price-smoothed EMA whose effective
length is modulated by the distance of RSI from its neutral value. The StockSharp port introduces the
EmaRsiVolatilityAdaptiveIndicator class that replicates the formula precisely:
Compute RSI on the selected AppliedPrice source with period RSIPeriod.
Measure the RSI distance from 50 (|RSI - 50| + 1), which acts as a volatility proxy.
Multiply the configured EMA period by this multiplier to obtain a dynamic length pdsx.
Apply the standard EMA recursion with smoothing factor 2 / (pdsx + 1) using the candle's applied price as input.
Large RSI excursions shorten the smoothing window and make the line react faster; a flat RSI lengthens the window and damps
noise. Both the slow and fast lines expose the full set of price modes supported by StockSharp.Messages.AppliedPrice.
Trading rules
Signal detection
Sell / short: previous slow < previous fast and current slow ≥ current fast.
Buy / long: previous slow > previous fast and current slow ≤ current fast.
Execution
The strategy only analyses finished candles from the configured candle series.
When a signal occurs it submits a market order sized to both close the existing exposure and open the new direction.
Exchange limits are respected through Security.MinVolume, Security.VolumeStep, and Security.MaxVolume.
Reversals
Orders are netted so that a single SellMarket or BuyMarket call takes the position across the zero line, matching the
MQL behaviour where an opposite signal immediately flips the trade.
Risk management
TakeProfitPoints and StopLossPoints replicate the expert advisor's TP/SL fields (expressed in price points). When either
value is non-zero the strategy starts StockSharp's protection manager with absolute price offsets and useMarketOrders = true
to mirror the original OrderSend stop/limit modification loop.
UseBalanceMultiplier implements the use_Multpl toggle. When active the effective order volume becomes
Volume * PortfolioEquity / MaxDrawdown with a defensive clamp to exchange constraints.
The base class StartProtection() call is still executed so that external risk modules can attach trailing or break-even
logic if required.
Parameters
Parameter
Default
Description
Volume
0.1
Base market order size before any balance multiplier is applied.
TakeProfitPoints
0
Take-profit distance in instrument points; 0 disables the take-profit leg.
StopLossPoints
0
Stop-loss distance in instrument points; 0 disables the protective stop.
UseBalanceMultiplier
false
Enables balance-proportional position sizing identical to use_Multpl in the EA.
MaxDrawdown
10000
Denominator for the balance multiplier; corresponds to the EA's Max_drawdown.
SlowRsiPeriod
310
RSI lookback for the slow EMA_RSI_VA line.
SlowEmaPeriod
40
Base EMA length for the slow line before RSI adaptation.
SlowAppliedPrice
Close
Price mode forwarded to the slow indicator.
FastRsiPeriod
200
RSI lookback for the fast EMA_RSI_VA line.
FastEmaPeriod
50
Base EMA length for the fast line before RSI adaptation.
FastAppliedPrice
Close
Price mode forwarded to the fast indicator.
CandleType
TimeFrame(1m)
Candle series used for calculations.
Implementation notes
The port is written with StockSharp's high-level API (SubscribeCandles().Bind(...)) to avoid manual indicator loops.
Only completed candles are processed, matching CopyBuffer(..., 1, 2, ...) calls in the MQL source.
Volume normalisation uses Security.MinVolume, Security.VolumeStep, and Security.MaxVolume, preventing invalid orders on
real exchanges.
A Python version is intentionally omitted as requested; the directory only contains the C# implementation and documentation.
The resulting behaviour mirrors the source EA while exposing StockSharp-friendly parameters and risk controls suitable for
Designer, Runner, or any custom host built on the StockSharp API.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// EMA RSI VA Crossover: Fast/slow EMA crossover with RSI volatility filter.
/// </summary>
public class EmaRsiVaCrossStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _rsiLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public EmaRsiVaCrossStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 10)
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 40)
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI 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;
}
/// <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 = 14 };
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 (close >= _entryPrice + atrVal * 3m || close <= _entryPrice - atrVal * 2m || (fastVal < slowVal && _prevFast >= _prevSlow))
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 3m || close >= _entryPrice + atrVal * 2m || (fastVal > slowVal && _prevFast <= _prevSlow))
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 40 && rsiVal < 70)
{
_entryPrice = close;
BuyMarket();
}
else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal > 30 && rsiVal < 60)
{
_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_rsi_va_cross_strategy(Strategy):
def __init__(self):
super(ema_rsi_va_cross_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 10) \
.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 40) \
.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI 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
def OnStarted2(self, time):
super(ema_rsi_va_cross_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 = 14
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 close >= self._entry_price + av * 3.0 or close <= self._entry_price - av * 2.0 or (fv < sv and self._prev_fast >= self._prev_slow):
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 3.0 or close >= self._entry_price + av * 2.0 or (fv > sv and self._prev_fast <= self._prev_slow):
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow and rv > 40 and rv < 70:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow and rv > 30 and rv < 60:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(ema_rsi_va_cross_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return ema_rsi_va_cross_strategy()