Ver en GitHub

4218 RSI Estrategia MA

Descripción general

Esta estrategia es un puerto C# del asesor experto original MetaTrader ubicado en MQL/9925. Recrea el oscilador de impulso RSI_MA combinando un RSI clásico con la pendiente de una media móvil exponencial construida sobre el precio ponderado (High + Low + 2 * Close) / 4. Las señales se generan únicamente en velas completadas, manteniendo el comportamiento idéntico a la implementación fuente.

El script está diseñado para velas EURUSD diarias (período de tiempo D1) y abre una única posición a la vez. Sin embargo, se puede utilizar cualquier instrumento con un incremento de precio significativo siempre que el tipo de vela esté configurado en consecuencia.

Lógica estratégica

  1. Cálculo del indicador
    • Se calcula un índice de fuerza relativa con longitud configurable sobre los precios de cierre.
    • Se calcula una media móvil exponencial con la misma longitud sobre el precio ponderado.
    • El valor del indicador es igual a RSI * (EMA(current) - EMA(previous)) / pipSize y se recorta al rango [1, 99].
  2. Entrada larga
    • Valor del indicador anterior por debajo del extremo de sobreventa (predeterminado 5).
    • Último valor del indicador por encima del umbral de activación de sobreventa (predeterminado 20).
    • Ninguna posición abierta o una posición corta existente (la corta se cierra antes de abrir una nueva larga).
  3. Entrada corta
    • Valor del indicador anterior por encima del extremo de sobrecompra (predeterminado 95).
    • Último valor del indicador por debajo del umbral de activación de sobrecompra (predeterminado 80).
    • Ninguna posición abierta o una posición larga existente (la posición larga se cierra antes de abrir una nueva corta).
  4. Salida basada en indicadores
    • Las posiciones largas se cierran cuando el indicador cae desde arriba del extremo de sobrecompra hasta debajo del nivel de activación (95 → 80 por defecto).
    • Las posiciones cortas se cierran cuando el indicador sube desde debajo del extremo de sobreventa hasta por encima del nivel de activación (5 → 20 por defecto).
  5. Salidas de protección
    • Las distancias opcionales de stop-loss, take-profit y trailing stop se expresan en pips. Las distancias se convierten automáticamente en precio utilizando la seguridad PriceStep (respaldo 0,0001).
    • El ajuste del trailing stop sigue el comportamiento del EA original: se activa solo después de que el precio se mueve más que la distancia configurada en la dirección favorable.

Parámetros

Parámetro Descripción
RsiPeriod RSI y EMA longitud.
OversoldActivationLevel Umbral que confirma una configuración larga después de un extremo de sobreventa.
OversoldExtremeLevel Extremo al que hay que llegar antes de que se permitan largos.
OverboughtActivationLevel Umbral que confirma una configuración corta después de un extremo de sobrecompra.
OverboughtExtremeLevel Extremo que se debe alcanzar antes de que se permitan los pantalones cortos.
StopLossPips Distancia para el stop-loss de protección. Activar/desactivar mediante UseStopLoss.
TakeProfitPips Distancia para el objetivo de ganancias. Activar/desactivar mediante UseTakeProfit.
TrailingStopPips Distancia para el trailing stop. Activar/desactivar mediante UseTrailingStop.
UseStopLoss Activa la gestión de stop-loss.
UseTakeProfit Activa la gestión del take-profit.
UseTrailingStop Activa las actualizaciones de trailing stop.
UseMoneyManagement Habilita el tamaño de posición basado en RiskPercent.
RiskPercent Porcentaje de cartera arriesgado por operación cuando la gestión del dinero está activa.
TradeVolume Volumen fijo utilizado cuando la administración del dinero está deshabilitada.
CandleType Tipo de datos de velas procesadas por la estrategia (por defecto Diaria).

Notas de uso

  • Adjunte la estrategia a las velas diarias EURUSD para reproducir el comportamiento del EA original. Se admiten otros instrumentos/plazos de tiempo después de ajustar CandleType y umbrales.
  • Sólo se mantiene abierta una posición en cualquier momento. Al ingresar una nueva operación, se cierra automáticamente primero la dirección opuesta.
  • La administración del dinero recurre al TradeVolume fijo cuando la información de la cartera no está disponible o el volumen calculado deja de ser positivo.
  • Asegúrese de que el valor PriceStep refleje un pip (0,0001 para la mayoría de los pares de divisas). De lo contrario, ajuste los parámetros en consecuencia.

Gestión de riesgos

  • Los niveles de stop-loss y take-profit se evalúan en cada vela completa utilizando rangos máximos/bajos de velas.
  • El trailing stop se actualiza solo cuando la operación genera ganancias por más de la distancia configurada y nunca se mueve en una dirección desfavorable.
  • Las salidas basadas en indicadores siguen funcionando incluso cuando los controles de riesgo están deshabilitados, lo que garantiza una degradación elegante similar a la versión MQL.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

using StockSharp.Algo.Candles;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// RSI and EMA based strategy converted from the original MQL implementation.
/// Combines a custom RSI*EMA momentum oscillator with basic risk management.
/// </summary>
public class RsiMaStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _oversoldActivationLevel;
	private readonly StrategyParam<decimal> _oversoldExtremeLevel;
	private readonly StrategyParam<decimal> _overboughtActivationLevel;
	private readonly StrategyParam<decimal> _overboughtExtremeLevel;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<bool> _useStopLoss;
	private readonly StrategyParam<bool> _useTakeProfit;
	private readonly StrategyParam<bool> _useTrailingStop;
	private readonly StrategyParam<bool> _useMoneyManagement;
	private readonly StrategyParam<decimal> _riskPercent;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;
	
	private RelativeStrengthIndex _rsi;
	private ExponentialMovingAverage _ema;
	
	private decimal? _previousIndicatorValue;
	
	private decimal? _stopLossPrice;
	private decimal? _takeProfitPrice;
	private decimal _entryPrice;
	
	public RsiMaStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
		.SetDisplay("RSI Period", string.Empty, "Oscillator")
		;
		
		_oversoldActivationLevel = Param(nameof(OversoldActivationLevel), 40m)
		.SetDisplay("Oversold Activation", string.Empty, "Oscillator")
		;
		
		_oversoldExtremeLevel = Param(nameof(OversoldExtremeLevel), 30m)
		.SetDisplay("Oversold Extreme", string.Empty, "Oscillator");
		
		_overboughtActivationLevel = Param(nameof(OverboughtActivationLevel), 60m)
		.SetDisplay("Overbought Activation", string.Empty, "Oscillator")
		;
		
		_overboughtExtremeLevel = Param(nameof(OverboughtExtremeLevel), 70m)
		.SetDisplay("Overbought Extreme", string.Empty, "Oscillator");
		
		_stopLossPips = Param(nameof(StopLossPips), 399m)
		.SetDisplay("Stop Loss (pips)", string.Empty, "Risk");
		
		_takeProfitPips = Param(nameof(TakeProfitPips), 999m)
		.SetDisplay("Take Profit (pips)", string.Empty, "Risk");
		
		_trailingStopPips = Param(nameof(TrailingStopPips), 299m)
		.SetDisplay("Trailing Stop (pips)", string.Empty, "Risk");
		
		_useStopLoss = Param(nameof(UseStopLoss), true)
		.SetDisplay("Use Stop Loss", string.Empty, "Risk");
		
		_useTakeProfit = Param(nameof(UseTakeProfit), true)
		.SetDisplay("Use Take Profit", string.Empty, "Risk");
		
		_useTrailingStop = Param(nameof(UseTrailingStop), true)
		.SetDisplay("Use Trailing Stop", string.Empty, "Risk");
		
		_useMoneyManagement = Param(nameof(UseMoneyManagement), false)
		.SetDisplay("Use Risk Percent Position Sizing", string.Empty, "Position");
		
		_riskPercent = Param(nameof(RiskPercent), 10m)
		.SetDisplay("Risk Percent", string.Empty, "Position");
		
		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
		.SetDisplay("Fixed Volume", string.Empty, "Position");
		
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
		.SetDisplay("Candle TimeFrame", string.Empty, "General");
	}
	
	/// <summary>
	/// RSI calculation period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}
	
	/// <summary>
	/// Activation threshold after an oversold extreme.
	/// </summary>
	public decimal OversoldActivationLevel
	{
		get => _oversoldActivationLevel.Value;
		set => _oversoldActivationLevel.Value = value;
	}
	
	/// <summary>
	/// Oversold extreme required before a long setup becomes valid.
	/// </summary>
	public decimal OversoldExtremeLevel
	{
		get => _oversoldExtremeLevel.Value;
		set => _oversoldExtremeLevel.Value = value;
	}
	
	/// <summary>
	/// Activation threshold after an overbought extreme.
	/// </summary>
	public decimal OverboughtActivationLevel
	{
		get => _overboughtActivationLevel.Value;
		set => _overboughtActivationLevel.Value = value;
	}
	
	/// <summary>
	/// Overbought extreme required before a short setup becomes valid.
	/// </summary>
	public decimal OverboughtExtremeLevel
	{
		get => _overboughtExtremeLevel.Value;
		set => _overboughtExtremeLevel.Value = value;
	}
	
	/// <summary>
	/// Stop-loss distance measured in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}
	
	/// <summary>
	/// Take-profit distance measured in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}
	
	/// <summary>
	/// Trailing-stop distance measured in pips.
	/// </summary>
	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}
	
	/// <summary>
	/// Enable or disable stop-loss management.
	/// </summary>
	public bool UseStopLoss
	{
		get => _useStopLoss.Value;
		set => _useStopLoss.Value = value;
	}
	
	/// <summary>
	/// Enable or disable take-profit management.
	/// </summary>
	public bool UseTakeProfit
	{
		get => _useTakeProfit.Value;
		set => _useTakeProfit.Value = value;
	}
	
	/// <summary>
	/// Enable or disable trailing stop adjustments.
	/// </summary>
	public bool UseTrailingStop
	{
		get => _useTrailingStop.Value;
		set => _useTrailingStop.Value = value;
	}
	
	/// <summary>
	/// Enable or disable percent based position sizing.
	/// </summary>
	public bool UseMoneyManagement
	{
		get => _useMoneyManagement.Value;
		set => _useMoneyManagement.Value = value;
	}
	
	/// <summary>
	/// Portfolio risk percentage when money management is enabled.
	/// </summary>
	public decimal RiskPercent
	{
		get => _riskPercent.Value;
		set => _riskPercent.Value = value;
	}
	
	/// <summary>
	/// Fixed volume used when money management is disabled.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}
	
	/// <summary>
	/// Candle type used for signal generation.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, CandleType);
	}
	
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_rsi = null;
		_ema = null;
		_previousIndicatorValue = null;
		_stopLossPrice = null;
		_takeProfitPrice = null;
		_entryPrice = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		
		_rsi = new RelativeStrengthIndex
		{
			Length = RsiPeriod
		};
		
		_ema = new ExponentialMovingAverage
		{
			Length = RsiPeriod
		};
		
		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_rsi, ProcessCandle)
			.Start();
		
		StartProtection(null, null);
	}
	
	private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_rsi.IsFormed)
			return;

		var indicatorValue = rsiValue;

		if (_previousIndicatorValue is decimal previousValue)
		{
			ManageOpenPosition(candle, previousValue, indicatorValue);
			EvaluateEntries(candle, previousValue, indicatorValue);
		}

		_previousIndicatorValue = indicatorValue;
	}
	
	private void ManageOpenPosition(ICandleMessage candle, decimal previousValue, decimal currentValue)
	{
		if (Position > 0)
		{
			var exitSignal = previousValue > OverboughtExtremeLevel && currentValue < OverboughtActivationLevel;
			if (exitSignal)
			{
				SellMarket();
				ResetRiskLevels();
				return;
			}

			UpdateTrailingStopForLong(candle);

			if (ShouldCloseLong(candle))
			{
				SellMarket();
				ResetRiskLevels();
			}
		}
		else if (Position < 0)
		{
			var exitSignal = previousValue < OversoldExtremeLevel && currentValue > OversoldActivationLevel;
			if (exitSignal)
			{
				BuyMarket();
				ResetRiskLevels();
				return;
			}

			UpdateTrailingStopForShort(candle);

			if (ShouldCloseShort(candle))
			{
				BuyMarket();
				ResetRiskLevels();
			}
		}
	}
	
	private void EvaluateEntries(ICandleMessage candle, decimal previousValue, decimal currentValue)
	{
		var price = candle.ClosePrice;
		if (price <= 0m)
			return;
		
		if (previousValue < OversoldExtremeLevel && currentValue > OversoldActivationLevel && Position <= 0)
		{
			_entryPrice = price;
			InitializeRiskLevelsForLong(price);
			BuyMarket();
		}
		else if (previousValue > OverboughtExtremeLevel && currentValue < OverboughtActivationLevel && Position >= 0)
		{
			_entryPrice = price;
			InitializeRiskLevelsForShort(price);
			SellMarket();
		}
	}
	
	private void InitializeRiskLevelsForLong(decimal price)
	{
		var pipDistance = GetPipSize();
		
		if (UseStopLoss && StopLossPips > 0m)
			_stopLossPrice = price - pipDistance * StopLossPips;
		else
			_stopLossPrice = null;
		
		if (UseTakeProfit && TakeProfitPips > 0m)
			_takeProfitPrice = price + pipDistance * TakeProfitPips;
		else
			_takeProfitPrice = null;
	}
	
	private void InitializeRiskLevelsForShort(decimal price)
	{
		var pipDistance = GetPipSize();
		
		if (UseStopLoss && StopLossPips > 0m)
			_stopLossPrice = price + pipDistance * StopLossPips;
		else
			_stopLossPrice = null;
		
		if (UseTakeProfit && TakeProfitPips > 0m)
			_takeProfitPrice = price - pipDistance * TakeProfitPips;
		else
			_takeProfitPrice = null;
	}
	
	private void UpdateTrailingStopForLong(ICandleMessage candle)
	{
		if (!UseTrailingStop || TrailingStopPips <= 0m)
			return;
		
		var pipDistance = GetPipSize() * TrailingStopPips;
		if (pipDistance <= 0m)
			return;
		
		var profit = candle.ClosePrice - _entryPrice;
		if (profit <= pipDistance)
			return;
		
		var newStop = candle.ClosePrice - pipDistance;
		if (_stopLossPrice is null || newStop > _stopLossPrice)
			_stopLossPrice = newStop;
	}
	
	private void UpdateTrailingStopForShort(ICandleMessage candle)
	{
		if (!UseTrailingStop || TrailingStopPips <= 0m)
			return;
		
		var pipDistance = GetPipSize() * TrailingStopPips;
		if (pipDistance <= 0m)
			return;
		
		var profit = _entryPrice - candle.ClosePrice;
		if (profit <= pipDistance)
			return;
		
		var newStop = candle.ClosePrice + pipDistance;
		if (_stopLossPrice is null || newStop < _stopLossPrice)
			_stopLossPrice = newStop;
	}
	
	private bool ShouldCloseLong(ICandleMessage candle)
	{
		var stopHit = UseStopLoss && _stopLossPrice is decimal stop && candle.LowPrice <= stop;
		var takeProfitHit = UseTakeProfit && _takeProfitPrice is decimal takeProfit && candle.HighPrice >= takeProfit;
		return stopHit || takeProfitHit;
	}
	
	private bool ShouldCloseShort(ICandleMessage candle)
	{
		var stopHit = UseStopLoss && _stopLossPrice is decimal stop && candle.HighPrice >= stop;
		var takeProfitHit = UseTakeProfit && _takeProfitPrice is decimal takeProfit && candle.LowPrice <= takeProfit;
		return stopHit || takeProfitHit;
	}
	
	private void ResetRiskLevels()
	{
		_stopLossPrice = null;
		_takeProfitPrice = null;
		_entryPrice = 0m;
	}
	
	private decimal GetPipSize()
	{
		var priceStep = Security?.PriceStep;
		if (priceStep is null || priceStep == 0m)
			return 0.0001m;
		
		return priceStep.Value;
	}
	
	private decimal GetOrderVolume(decimal price)
	{
		var volume = TradeVolume;

		if (!UseMoneyManagement || Portfolio is null || price <= 0m)
			return volume;

		var portfolioValue = Portfolio.CurrentValue ?? 0m;
		if (portfolioValue <= 0m)
			return volume;

		var riskAmount = portfolioValue * RiskPercent / 100m;
		if (riskAmount <= 0m)
			return volume;

		var estimatedVolume = riskAmount / price;

		var volumeStep = Security?.VolumeStep ?? 0m;
		if (volumeStep > 0m)
		{
			estimatedVolume = Math.Floor(estimatedVolume / volumeStep) * volumeStep;
		}

		if (estimatedVolume <= 0m)
			estimatedVolume = volume;

		return estimatedVolume;
	}
}