Ver en GitHub

Estrategia RSI Trader

Descripción general

Esta estrategia replica el asesor experto de MetaTrader "RSI trader v0.15" en la API de alto nivel de StockSharp. Alinea la dirección de la tendencia entre la acción del precio y un Índice de Fuerza Relativa (RSI) suavizado. El trading se realiza en un único instrumento usando velas de una hora por defecto, pero el marco temporal es configurable a través del parámetro CandleType.

Lógica de trading

  1. Calcular un RSI estándar con un período configurable.
  2. Suavizar el RSI con dos medias móviles simples (SMA): un promedio de señal rápido y uno de confirmación más lento.
  3. Rastrear dos medias móviles del precio de cierre: una media móvil simple corta y una media móvil ponderada larga para aproximar el par SMA/LWMA del MQL original.
  4. Generar estados de tendencia en cada vela terminada:
    • Alineación alcista: SMA de precio corta por encima de la larga y SMA RSI rápida por encima de la lenta.
    • Alineación bajista: SMA de precio corta por debajo de la larga y SMA RSI rápida por debajo de la lenta.
    • Lateral / desacuerdo: las medias móviles apuntan en direcciones opuestas, señalando que no hay tendencia clara.
  5. Actuar sobre el estado detectado:
    • Abrir una posición larga cuando aparece alineación alcista y no hay posición actualmente abierta.
    • Abrir una posición corta cuando aparece alineación bajista y no hay posición actualmente abierta.
    • Cerrar inmediatamente cualquier posición abierta cuando se detecta el estado lateral, reflejando la salida protectora de la versión MQL.
  6. El modo de reversión opcional invierte todas las direcciones de entrada, permitiendo al usuario operar contra la tendencia con respecto a las señales detectadas.

La estrategia respeta el manejo de protección incorporado de StockSharp y requiere velas completadas antes de tomar cualquier acción.

Parámetros

Parámetro Descripción Predeterminado
RsiPeriod Período de retrospección usado para el cálculo del RSI. 14
ShortRsiMaPeriod Longitud de la SMA rápida aplicada a los valores del RSI. 9
LongRsiMaPeriod Longitud de la SMA lenta aplicada a los valores del RSI. 45
ShortPriceMaPeriod Longitud de la SMA corta aplicada a los precios de cierre. 9
LongPriceMaPeriod Longitud de la media móvil ponderada larga aplicada a los precios. 45
Reverse Cuando es true, las órdenes de compra y venta se intercambian (refleja la entrada "Reverse" original). false
CandleType Tipo de datos para velas de precio. Predeterminado es marco temporal de una hora. 1h

Todos los parámetros enteros exponen rangos de optimización que reflejan la flexibilidad de los ajustes de entrada del experto de MetaTrader.

Gestión de riesgo

  • Las posiciones se cierran tan pronto como los trends de precio y RSI no coinciden (estado lateral), reproduciendo el comportamiento de salida inmediata del EA.
  • StartProtection() se habilita al inicio para cooperar con la infraestructura protectora de StockSharp.

Notas

  • La estrategia depende de la propiedad base Volume de Strategy para definir el tamaño de la operación.
  • Solo se procesan velas completadas; las actualizaciones parciales se ignoran para evitar señales prematuras.
  • Se usa la media móvil ponderada para coincidir con la LWMA larga original aplicada a los cierres de precio.
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

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

/// <summary>
/// RSI trader strategy aligning price and RSI moving-average trends.
/// </summary>
public class RsiTraderStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _shortRsiMaPeriod;
	private readonly StrategyParam<int> _longRsiMaPeriod;
	private readonly StrategyParam<int> _shortPriceMaPeriod;
	private readonly StrategyParam<int> _longPriceMaPeriod;
	private readonly StrategyParam<bool> _reverse;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _closes = new();
	private readonly List<decimal> _rsiValues = new();

	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int ShortRsiMaPeriod { get => _shortRsiMaPeriod.Value; set => _shortRsiMaPeriod.Value = value; }
	public int LongRsiMaPeriod { get => _longRsiMaPeriod.Value; set => _longRsiMaPeriod.Value = value; }
	public int ShortPriceMaPeriod { get => _shortPriceMaPeriod.Value; set => _shortPriceMaPeriod.Value = value; }
	public int LongPriceMaPeriod { get => _longPriceMaPeriod.Value; set => _longPriceMaPeriod.Value = value; }
	public bool Reverse { get => _reverse.Value; set => _reverse.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public RsiTraderStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14).SetDisplay("RSI Period", "RSI calculation length", "RSI").SetGreaterThanZero();
		_shortRsiMaPeriod = Param(nameof(ShortRsiMaPeriod), 12).SetDisplay("Short RSI MA", "Short moving average on RSI", "RSI").SetGreaterThanZero();
		_longRsiMaPeriod = Param(nameof(LongRsiMaPeriod), 60).SetDisplay("Long RSI MA", "Long moving average on RSI", "RSI").SetGreaterThanZero();
		_shortPriceMaPeriod = Param(nameof(ShortPriceMaPeriod), 12).SetDisplay("Short Price MA", "Short simple moving average", "Price").SetGreaterThanZero();
		_longPriceMaPeriod = Param(nameof(LongPriceMaPeriod), 60).SetDisplay("Long Price MA", "Long weighted moving average", "Price").SetGreaterThanZero();
		_reverse = Param(nameof(Reverse), false).SetDisplay("Reverse", "Flip buy/sell signals", "Trading");
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Primary candle type", "Data");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, CandleType);
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_closes.Clear();
		_rsiValues.Clear();
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_closes.Add(candle.ClosePrice);

		var maxCache = Math.Max(LongPriceMaPeriod, Math.Max(LongRsiMaPeriod + RsiPeriod, 300));
		if (_closes.Count > maxCache)
			_closes.RemoveAt(0);

		var rsi = CalculateRsi();
		if (rsi is null)
			return;

		_rsiValues.Add(rsi.Value);
		if (_rsiValues.Count > maxCache)
			_rsiValues.RemoveAt(0);

		if (_rsiValues.Count < LongRsiMaPeriod || _closes.Count < LongPriceMaPeriod)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var shortRsi = AverageLast(_rsiValues, ShortRsiMaPeriod);
		var longRsi = AverageLast(_rsiValues, LongRsiMaPeriod);
		var shortPrice = AverageLast(_closes, ShortPriceMaPeriod);
		var longPrice = WeightedAverageLast(_closes, LongPriceMaPeriod);

		var goLong = shortPrice > longPrice && shortRsi > longRsi;
		var goShort = shortPrice < longPrice && shortRsi < longRsi;
		var sideways = !goLong && !goShort;

		if (sideways && Position != 0)
		{
			if (Position > 0)
				SellMarket(Position);
			else
				BuyMarket(Math.Abs(Position));

			return;
		}

		if (Position != 0)
			return;

		if (goLong)
		{
			if (Reverse)
				SellMarket();
			else
				BuyMarket();
		}
		else if (goShort)
		{
			if (Reverse)
				BuyMarket();
			else
				SellMarket();
		}
	}

	private decimal? CalculateRsi()
	{
		if (_closes.Count <= RsiPeriod)
			return null;

		decimal gainSum = 0m;
		decimal lossSum = 0m;
		var start = _closes.Count - RsiPeriod;

		for (var i = start; i < _closes.Count; i++)
		{
			var change = _closes[i] - _closes[i - 1];
			if (change > 0m)
				gainSum += change;
			else
				lossSum -= change;
		}

		var averageGain = gainSum / RsiPeriod;
		var averageLoss = lossSum / RsiPeriod;

		if (averageLoss == 0m)
			return 100m;

		var rs = averageGain / averageLoss;
		return 100m - 100m / (1m + rs);
	}

	private static decimal AverageLast(IReadOnlyList<decimal> values, int length)
	{
		decimal sum = 0m;
		var start = values.Count - length;

		for (var i = start; i < values.Count; i++)
			sum += values[i];

		return sum / length;
	}

	private static decimal WeightedAverageLast(IReadOnlyList<decimal> values, int length)
	{
		decimal weightedSum = 0m;
		decimal weightSum = 0m;
		var start = values.Count - length;
		var weight = 1m;

		for (var i = start; i < values.Count; i++)
		{
			weightedSum += values[i] * weight;
			weightSum += weight;
			weight += 1m;
		}

		return weightSum > 0m ? weightedSum / weightSum : values[^1];
	}
}