This strategy ports the RSI RFTL EA from MetaTrader 5 to the StockSharp high-level API. It keeps the original idea of trading RSI swing trendlines, enhanced with the Recursive Filter Trend Line (RFTL) as a directional filter. The implementation reproduces the bar-by-bar decision making of the expert advisor while using idiomatic StockSharp constructs such as StrategyParam, indicator bindings and candle subscriptions.
How It Works
RSI swing detection – the latest 500 RSI values are scanned for local highs and lows. Peaks must rise above 40 and 60, while troughs must fall below 60 and 40, matching the MQL turning-point logic.
Trendline projection – once two valid highs or lows are found, the strategy projects the corresponding RSI trendline to the current bar and to the prior bar. Intermediate swings that break the 40/60 thresholds invalidate the line, just as in the expert advisor.
RFTL confirmation – the previous value of the Recursive Filter Trend Line (calculated with the original coefficient table) must sit above the previous close for shorts or below it for longs. This keeps entries aligned with the RFTL filter.
Entry gating – RSI must also sit on the appropriate side of neutral: shorts require RSI to stay above 47/50, while longs require RSI to remain below 55/50.
Risk layer – protective stop, take-profit and trailing-stop distances are expressed in pips and updated on every finished candle, mimicking the MQL trailing modification routine. Additional exits fire when RSI exceeds 70 (close longs) or drops below 30 (close shorts).
Entry Logic
Short setup
Two RSI lows below 60/40 define a rising trendline whose projection is now broken to the downside (RSI[1] < line, RSI[2] > line(previous)).
Previous RFTL value is above the previous close, confirming downward pressure.
RSI stays on the bullish side (RSI[2] > 50, RSI[0] > 47) and the detected tops lie farther in history than the lows (pos₂ > pos₄), matching the MQL ordering constraint.
Long setup
Two RSI highs above 40/60 define a falling trendline whose projection is now broken to the upside (RSI[1] > line, RSI[2] < line(previous)).
Previous RFTL value is below the previous close.
RSI remains on the bearish side (RSI[2] < 50, RSI[0] < 55) and the recent lows are more recent than the highs (pos₄ > pos₂).
Signals are evaluated only after all indicators are formed and the necessary history is accumulated, preventing premature trades on partial data.
Risk Management
Stop Loss / Take Profit – configurable in pips. If the current candle trades beyond the respective price level, the position is closed immediately and trailing state is reset.
Trailing Stop – optional. Once price moves by TrailingStopPips + TrailingStepPips in favour of the trade, the stop trails the close while enforcing the same minimum advance (TrailingStepPips) before tightening again.
RSI Emergency Exit – longs close when RSI crosses 70; shorts close when it falls below 30. This mirrors the hard exits coded in the original EA.
Parameters
Parameter
Default
Description
CandleType
1 hour
Timeframe used for both RSI and RFTL calculations.
TradeVolume
1
Order volume submitted on each entry.
RsiPeriod
30
Lookback period of the RSI oscillator.
StopLossPips
50
Protective stop distance in pips (0 disables the stop).
TakeProfitPips
50
Take-profit distance in pips (0 disables the target).
TrailingStopPips
5
Trailing stop offset in pips (0 disables trailing).
TrailingStepPips
5
Additional pip improvement required before trailing updates.
All distances are multiplied by the instrument PriceStep, matching the point/ pip handling of the MQL version.
Usage
Attach the strategy to a security and set CandleType to the bar size used in your MetaTrader tests.
Adjust the risk parameters (stop, take, trailing) to the pip distances you used previously. Setting a parameter to 0 disables that protection.
Start the strategy; it will subscribe to the specified candles, compute RSI and RFTL, and begin monitoring signals once enough history is collected.
Monitor the chart widgets – the price area displays candles and the RFTL line, while the second pane shows the RSI oscillator.
Notes & Differences
The RFTL indicator is implemented directly in C# with the original coefficient table; no external files are required.
Trade management stays single-position: the strategy flips between long, short and flat just like the EA that only tracked one position per symbol/magic.
Because stop and trailing exits are handled inside the strategy (StockSharp does not auto-execute MT5 stops), re-entries are skipped on the bar where a protective exit fires, which is a conservative but safe approximation.
History buffers are capped at 600 records to mirror the 500-element arrays used in the source code while avoiding unbounded memory growth.
All inline comments were rewritten in English and the code follows the StockSharp high-level API style guidelines.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// RSI-based trend strategy with a simple recursive filter (EMA) as trend confirmation.
/// Buys when RSI crosses above oversold level and EMA confirms uptrend.
/// Sells when RSI crosses below overbought level and EMA confirms downtrend.
/// </summary>
public class RsiRftlStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<decimal> _oversold;
private RelativeStrengthIndex _rsi;
private ExponentialMovingAverage _ema;
private decimal _prevRsi;
private decimal _entryPrice;
private int _cooldown;
/// <summary>
/// RSI lookback period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// EMA period for trend filter.
/// </summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>
/// Overbought RSI level.
/// </summary>
public decimal Overbought
{
get => _overbought.Value;
set => _overbought.Value = value;
}
/// <summary>
/// Oversold RSI level.
/// </summary>
public decimal Oversold
{
get => _oversold.Value;
set => _oversold.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public RsiRftlStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Length of the RSI oscillator", "Indicator");
_emaPeriod = Param(nameof(EmaPeriod), 44)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Length of the trend EMA filter", "Indicator");
_overbought = Param(nameof(Overbought), 75m)
.SetDisplay("Overbought", "RSI overbought level", "Levels");
_oversold = Param(nameof(Oversold), 25m)
.SetDisplay("Oversold", "RSI oversold level", "Levels");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsi = null;
_ema = null;
_prevRsi = 0;
_entryPrice = 0;
_cooldown = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_rsi, _ema, OnProcess);
subscription.Start();
}
private void OnProcess(ICandleMessage candle, decimal rsiValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed || !_ema.IsFormed)
{
_prevRsi = rsiValue;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevRsi = rsiValue;
return;
}
var close = candle.ClosePrice;
var trendUp = close > emaValue;
var trendDown = close < emaValue;
// Buy: RSI crosses above oversold + uptrend
if (_prevRsi < Oversold && rsiValue >= Oversold && trendUp && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_entryPrice = close;
_cooldown = 10;
}
// Sell: RSI crosses below overbought + downtrend
else if (_prevRsi > Overbought && rsiValue <= Overbought && trendDown && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_entryPrice = close;
_cooldown = 10;
}
// Exit long on overbought
if (Position > 0 && rsiValue > 80m)
{
SellMarket();
_entryPrice = 0;
_cooldown = 10;
}
// Exit short on oversold
else if (Position < 0 && rsiValue < 20m)
{
BuyMarket();
_entryPrice = 0;
_cooldown = 10;
}
_prevRsi = rsiValue;
}
}
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 RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class rsi_rftl_strategy(Strategy):
def __init__(self):
super(rsi_rftl_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "Length of the RSI oscillator", "Indicator")
self._ema_period = self.Param("EmaPeriod", 44) \
.SetDisplay("EMA Period", "Length of the trend EMA filter", "Indicator")
self._overbought = self.Param("Overbought", 75.0) \
.SetDisplay("Overbought", "RSI overbought level", "Levels")
self._oversold = self.Param("Oversold", 25.0) \
.SetDisplay("Oversold", "RSI oversold level", "Levels")
self._rsi = None
self._ema = None
self._prev_rsi = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def rsi_period(self):
return self._rsi_period.Value
@property
def ema_period(self):
return self._ema_period.Value
@property
def overbought(self):
return self._overbought.Value
@property
def oversold(self):
return self._oversold.Value
def OnReseted(self):
super(rsi_rftl_strategy, self).OnReseted()
self._rsi = None
self._ema = None
self._prev_rsi = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(rsi_rftl_strategy, self).OnStarted2(time)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.rsi_period
self._ema = ExponentialMovingAverage()
self._ema.Length = self.ema_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._rsi, self._ema, self._process_candle)
subscription.Start()
def _process_candle(self, candle, rsi_value, ema_value):
if candle.State != CandleStates.Finished:
return
rsi_val = float(rsi_value)
ema_val = float(ema_value)
if not self._rsi.IsFormed or not self._ema.IsFormed:
self._prev_rsi = rsi_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_rsi = rsi_val
return
close = float(candle.ClosePrice)
trend_up = close > ema_val
trend_down = close < ema_val
ob = float(self.overbought)
os_level = float(self.oversold)
# Buy: RSI crosses above oversold + uptrend
if self._prev_rsi < os_level and rsi_val >= os_level and trend_up and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 10
# Sell: RSI crosses below overbought + downtrend
elif self._prev_rsi > ob and rsi_val <= ob and trend_down and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 10
# Exit long on strong overbought
if self.Position > 0 and rsi_val > 80.0:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 10
# Exit short on strong oversold
elif self.Position < 0 and rsi_val < 20.0:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 10
self._prev_rsi = rsi_val
def CreateClone(self):
return rsi_rftl_strategy()