Diese Strategie portiert den RSI RFTL EA von MetaTrader 5 zur StockSharp-High-Level-API. Sie behält die ursprüngliche Idee bei, RSI-Swing-Trendlinien zu handeln, ergänzt durch die Recursive Filter Trend Line (RFTL) als Richtungsfilter. Die Implementierung reproduziert die Balken-für-Balken-Entscheidungsfindung des Experten und verwendet dabei idiomatische StockSharp-Konstrukte wie StrategyParam, Indikator-Bindungen und Kerzen-Abonnements.
Funktionsweise
RSI-Swing-Erkennung – die letzten 500 RSI-Werte werden auf lokale Hochs und Tiefs gescannt. Gipfel müssen über 40 und 60 steigen, während Täler unter 60 und 40 fallen müssen, entsprechend der MQL-Wendepunktlogik.
Trendlinienprojektion – sobald zwei gültige Hochs oder Tiefs gefunden wurden, projiziert die Strategie die entsprechende RSI-Trendlinie auf den aktuellen und den vorherigen Balken. Zwischenliegende Swings, die die 40/60-Schwellen brechen, machen die Linie ungültig, genau wie im Experten.
RFTL-Bestätigung – der vorherige Wert der Recursive Filter Trend Line (berechnet mit der originalen Koeffiziententabelle) muss für Shorts über dem vorherigen Schlusskurs oder für Longs darunter liegen. Dies hält Einstiege mit dem RFTL-Filter ausgerichtet.
Einstiegsfilterung – RSI muss sich auch auf der richtigen Seite des Neutralpunkts befinden: Shorts erfordern RSI über 47/50, während Longs RSI unter 55/50 erfordern.
Risikoschicht – Schutz-Stop, Take-Profit und Trailing-Stop-Abstände werden in Pips ausgedrückt und bei jeder abgeschlossenen Kerze aktualisiert, womit die MQL-Trailing-Modifikationsroutine imitiert wird. Zusätzliche Ausstiege erfolgen, wenn RSI 70 überschreitet (Longs schließen) oder unter 30 fällt (Shorts schließen).
Einstiegslogik
Short-Aufstellung
Zwei RSI-Tiefs unter 60/40 definieren eine steigende Trendlinie, deren Projektion jetzt nach unten gebrochen wird (RSI[1] < Linie, RSI[2] > Linie(vorherige)).
Der vorherige RFTL-Wert liegt über dem vorherigen Schluss, was Abwärtsdruck bestätigt.
RSI bleibt auf der bullischen Seite (RSI[2] > 50, RSI[0] > 47) und die erkannten Hochs liegen weiter zurück in der Geschichte als die Tiefs (pos₂ > pos₄), entsprechend der MQL-Reihenfolgebeschränkung.
Long-Aufstellung
Zwei RSI-Hochs über 40/60 definieren eine fallende Trendlinie, deren Projektion jetzt nach oben gebrochen wird (RSI[1] > Linie, RSI[2] < Linie(vorherige)).
Der vorherige RFTL-Wert liegt unter dem vorherigen Schluss.
RSI bleibt auf der bärischen Seite (RSI[2] < 50, RSI[0] < 55) und die jüngsten Tiefs sind aktueller als die Hochs (pos₄ > pos₂).
Signale werden nur ausgewertet, nachdem alle Indikatoren gebildet sind und die notwendige Geschichte angesammelt wurde, was vorzeitige Trades auf unvollständigen Daten verhindert.
Risikomanagement
Stop Loss / Take Profit – in Pips konfigurierbar. Wenn die aktuelle Kerze über das jeweilige Preisniveau hinaus handelt, wird die Position sofort geschlossen und der Trailing-Zustand zurückgesetzt.
Trailing Stop – optional. Sobald sich der Preis um TrailingStopPips + TrailingStepPips zugunsten des Trades bewegt, folgt der Stop dem Schlusskurs, wobei vor dem nächsten Anziehen dieselbe Mindestvorwärtsbewegung (TrailingStepPips) durchgesetzt wird.
RSI-Notausstieg – Longs schließen, wenn RSI 70 kreuzt; Shorts schließen, wenn er unter 30 fällt. Dies spiegelt die Hard-Exits im Original-EA wider.
Parameter
Parameter
Standard
Beschreibung
CandleType
1 Stunde
Zeitrahmen für RSI- und RFTL-Berechnungen.
TradeVolume
1
Ordervolumen bei jedem Einstieg.
RsiPeriod
30
Lookback-Periode des RSI-Oszillators.
StopLossPips
50
Schutz-Stop-Abstand in Pips (0 deaktiviert den Stop).
TakeProfitPips
50
Take-Profit-Abstand in Pips (0 deaktiviert das Ziel).
TrailingStopPips
5
Trailing-Stop-Offset in Pips (0 deaktiviert das Trailing).
TrailingStepPips
5
Zusätzliche Pip-Verbesserung vor dem Trailing-Update.
Alle Abstände werden mit dem Instrument-PriceStep multipliziert, entsprechend der Punkt/Pip-Handhabung der MQL-Version.
Verwendung
Die Strategie an ein Wertpapier anhängen und CandleType auf die in MetaTrader-Tests verwendete Balkengröße setzen.
Die Risikoparameter (Stop, Take, Trailing) auf die zuvor verwendeten Pip-Abstände anpassen. Ein Parameter auf 0 zu setzen deaktiviert diesen Schutz.
Strategie starten; sie wird die angegebenen Kerzen abonnieren, RSI und RFTL berechnen und mit der Signalüberwachung beginnen, sobald genug Geschichte gesammelt wurde.
Die Diagramm-Widgets überwachen – der Preisbereich zeigt Kerzen und die RFTL-Linie, während das zweite Fenster den RSI-Oszillator anzeigt.
Hinweise und Unterschiede
Der RFTL-Indikator ist direkt in C# mit der originalen Koeffiziententabelle implementiert; keine externen Dateien erforderlich.
Das Trade-Management bleibt auf eine einzelne Position beschränkt: Die Strategie wechselt zwischen Long, Short und Flat, genau wie der EA, der nur eine Position pro Symbol/Magic verfolgte.
Da Stop- und Trailing-Ausstiege innerhalb der Strategie gehandhabt werden (StockSharp führt MT5-Stops nicht automatisch aus), werden Wiedereinstiege auf dem Balken übersprungen, wo ein Schutzausstieg ausgelöst wird, was eine konservative, aber sichere Näherung ist.
Historische Puffer sind auf 600 Datensätze begrenzt, um die 500-Element-Arrays im Quellcode zu spiegeln und gleichzeitig unbegrenztes Speicherwachstum zu vermeiden.
Alle Inline-Kommentare wurden in Englisch umgeschrieben und der Code folgt den StockSharp-High-Level-API-Stilrichtlinien.
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()