Esta estrategia porta el RSI RFTL EA de MetaTrader 5 a la API de alto nivel de StockSharp. Conserva la idea original de operar con líneas de tendencia oscilantes del RSI, mejorada con la Recursive Filter Trend Line (RFTL) como filtro direccional. La implementación reproduce la toma de decisiones barra a barra del experto mientras usa construcciones idiomáticas de StockSharp como StrategyParam, enlaces de indicadores y suscripciones de velas.
Cómo Funciona
Detección de oscilaciones RSI – se escanean los últimos 500 valores de RSI en busca de máximos y mínimos locales. Los picos deben superar 40 y 60, mientras que los valles deben caer por debajo de 60 y 40, coincidiendo con la lógica de puntos de inflexión de MQL.
Proyección de línea de tendencia – una vez encontrados dos máximos o mínimos válidos, la estrategia proyecta la correspondiente línea de tendencia del RSI hacia la barra actual y la anterior. Las oscilaciones intermedias que rompen los umbrales 40/60 invalidan la línea, igual que en el experto.
Confirmación RFTL – el valor anterior de la Recursive Filter Trend Line (calculado con la tabla de coeficientes original) debe estar por encima del cierre anterior para cortos o por debajo para largos. Esto mantiene las entradas alineadas con el filtro RFTL.
Filtrado de entrada – el RSI también debe mantenerse en el lado apropiado del neutro: los cortos requieren RSI por encima de 47/50, mientras que los largos requieren RSI por debajo de 55/50.
Capa de riesgo – las distancias de stop de protección, take-profit y trailing stop se expresan en pips y se actualizan en cada vela cerrada, imitando la rutina de modificación de trailing de MQL. Salidas adicionales se activan cuando el RSI supera 70 (cerrar largos) o cae por debajo de 30 (cerrar cortos).
Lógica de Entrada
Configuración corta
Dos mínimos de RSI por debajo de 60/40 definen una línea de tendencia ascendente cuya proyección ahora se rompe a la baja (RSI[1] < línea, RSI[2] > línea(anterior)).
El valor anterior de RFTL está por encima del cierre anterior, confirmando presión descendente.
RSI permanece en el lado alcista (RSI[2] > 50, RSI[0] > 47) y los máximos detectados se encuentran más lejos en la historia que los mínimos (pos₂ > pos₄), coincidiendo con la restricción de ordenamiento de MQL.
Configuración larga
Dos máximos de RSI por encima de 40/60 definen una línea de tendencia descendente cuya proyección ahora se rompe al alza (RSI[1] > línea, RSI[2] < línea(anterior)).
El valor anterior de RFTL está por debajo del cierre anterior.
RSI permanece en el lado bajista (RSI[2] < 50, RSI[0] < 55) y los mínimos recientes son más recientes que los máximos (pos₄ > pos₂).
Las señales se evalúan solo después de que todos los indicadores estén formados y se haya acumulado el historial necesario, evitando operaciones prematuras con datos parciales.
Gestión de Riesgo
Stop Loss / Take Profit – configurables en pips. Si la vela actual opera más allá del nivel de precio respectivo, la posición se cierra inmediatamente y el estado de trailing se reinicia.
Trailing Stop – opcional. Una vez que el precio se mueve por TrailingStopPips + TrailingStepPips a favor de la operación, el stop sigue al cierre mientras se aplica el mismo avance mínimo (TrailingStepPips) antes de ajustarse nuevamente.
Salida de Emergencia RSI – los largos se cierran cuando el RSI cruza 70; los cortos se cierran cuando cae por debajo de 30. Esto refleja las salidas hard codificadas en el EA original.
Parámetros
Parámetro
Predeterminado
Descripción
CandleType
1 hora
Marco temporal utilizado para los cálculos de RSI y RFTL.
TradeVolume
1
Volumen de orden enviado en cada entrada.
RsiPeriod
30
Período de lookback del oscilador RSI.
StopLossPips
50
Distancia del stop de protección en pips (0 deshabilita el stop).
TakeProfitPips
50
Distancia del take-profit en pips (0 deshabilita el objetivo).
TrailingStopPips
5
Desplazamiento del trailing stop en pips (0 deshabilita el trailing).
TrailingStepPips
5
Mejora adicional en pips requerida antes de que se actualice el trailing.
Todas las distancias se multiplican por el PriceStep del instrumento, coincidiendo con el manejo de punto/pip de la versión MQL.
Uso
Adjuntar la estrategia a un instrumento y configurar CandleType al tamaño de barra usado en sus pruebas de MetaTrader.
Ajustar los parámetros de riesgo (stop, take, trailing) a las distancias en pips utilizadas anteriormente. Establecer un parámetro en 0 deshabilita esa protección.
Iniciar la estrategia; se suscribirá a las velas especificadas, calculará RSI y RFTL, y comenzará a monitorear señales una vez que se haya recolectado suficiente historial.
Monitorear los widgets del gráfico – el área de precio muestra velas y la línea RFTL, mientras que el segundo panel muestra el oscilador RSI.
Notas y Diferencias
El indicador RFTL está implementado directamente en C# con la tabla de coeficientes original; no se requieren archivos externos.
La gestión de operaciones se mantiene en posición única: la estrategia alterna entre largo, corto y plano igual que el EA que solo rastreaba una posición por símbolo/mágico.
Como los exits de stop y trailing se gestionan dentro de la estrategia (StockSharp no ejecuta automáticamente los stops de MT5), las reentradas se omiten en la barra donde se activa una salida de protección, lo cual es una aproximación conservadora pero segura.
Los búferes de historial están limitados a 600 registros para reflejar los arreglos de 500 elementos usados en el código fuente y evitar el crecimiento ilimitado de memoria.
Todos los comentarios inline se reescribieron en inglés y el código sigue las pautas de estilo de la API de alto nivel de StockSharp.
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()