Ver en GitHub

Estrategia de Trading con RSI Cualificado

Descripción general

Esta estrategia reproduce el asesor experto de MetaTrader "Trade on qualified RSI" usando la API de alto nivel de StockSharp. Se comporta como un sistema contrario: interpreta lecturas extendidas del Índice de Fuerza Relativa (RSI) como agotamiento y abre una posición contra el movimiento predominante después de que el momentum persiste durante varias velas. Los stops trailing se gestionan en pasos de precio para que el stop siga la operación solo cuando el precio se mueve a favor de la misma.

Lógica de señal

Indicador

  • Índice de Fuerza Relativa con un período configurable (predeterminado: 28).
  • Calculado sobre la suscripción de velas seleccionada (predeterminado: velas de 15 minutos).

Entrada corta

  1. La última vela cerrada tiene RSI mayor o igual al umbral superior (predeterminado: 55).
  2. Cada una de las CountBars velas cerradas anteriores también tuvo RSI por encima del mismo umbral. Internamente la estrategia cuenta barras consecutivas; la señal se dispara una vez que el contador alcanza CountBars + 1.
  3. No hay posición activa abierta. Cuando se activa, la estrategia vende a mercado con el volumen configurado y almacena el cierre de la vela como precio de entrada.

Entrada larga

  1. La última vela cerrada tiene RSI menor o igual al umbral inferior (predeterminado: 45).
  2. Cada una de las CountBars velas cerradas anteriores también tuvo RSI por debajo del mismo umbral (se requieren CountBars + 1 lecturas consecutivas).
  3. No existe posición abierta. Cuando se activa, la estrategia compra a mercado con el volumen configurado y registra el precio de entrada.

Gestión de posición

  • Stop inicial: justo después de la entrada el precio de stop se coloca a StopLossPoints pasos de precio de la distancia del cierre de entrada (debajo para largos, encima para cortos). Los pasos de precio se obtienen de Security.PriceStep; si el instrumento no lo define la estrategia recurre a 1.
  • Trailing: en cada vela terminada el stop se ajusta hacia el cierre actual. Para posiciones largas el stop se convierte en Cierre - StopLossPoints * PriceStep cuando ese valor está por encima del stop anterior. Para posiciones cortas el stop se convierte en Cierre + StopLossPoints * PriceStep cuando ese valor está por debajo del stop anterior.
  • Salida: si el mínimo de la vela cruza por debajo del stop mientras está largo, o el máximo de la vela cruza por encima del stop mientras está corto, la estrategia sale de toda la posición a mercado. No hay objetivos de ganancia adicionales ni señales de reversión; las nuevas entradas ocurren solo después de que la posición anterior esté cerrada.

Parámetros

Nombre Descripción Predeterminado
RsiPeriod Longitud de retrospección para el indicador RSI. 28
UpperThreshold Nivel de RSI que califica una configuración corta. 55
LowerThreshold Nivel de RSI que califica una configuración larga. 45
CountBars Cuántas barras anteriores deben mantenerse más allá del umbral (CountBars + 1 barras consecutivas en total). 5
StopLossPoints Distancia del stop expresada en pasos de precio. El desplazamiento de precio real es igual a StopLossPoints * PriceStep. 21
TradeVolume Volumen enviado con cada orden de entrada. 1
CandleType Suscripción de velas usada para los cálculos del indicador. Velas de 15 minutos

Todos los parámetros pueden optimizarse. Los umbrales permiten valores decimales, por lo que es posible una sintonía precisa de los límites del RSI.

Notas de implementación

  • La estrategia usa SubscribeCandles(...).Bind(...) para alimentar el indicador RSI y reaccionar solo cuando la vela está completamente formada.
  • Los valores del RSI no se leen de vuelta desde el indicador por índice; en cambio, los contadores rastrean cuántas velas terminadas consecutivas respetan los umbrales.
  • Los stops protectores se simulan dentro de la estrategia. Las órdenes se cierran a mercado cuando se cruza el nivel de stop en lugar de colocar órdenes de stop separadas.
  • Se producen mensajes de registro para entradas y salidas, reflejando la salida detallada del asesor experto original.

Uso

  1. Agregue la estrategia a una aplicación StockSharp, asigne el instrumento y portafolio deseados, y configure la serie de velas.
  2. Ajuste los umbrales del RSI, el número de barras calificadas y la distancia del stop para que coincidan con la volatilidad del instrumento objetivo.
  3. Inicie la estrategia. Monitoree el registro para ver cuándo ocurren las señales y cómo evoluciona el stop trailing.
  4. Considere ejecutar el optimizador incorporado para buscar mejores combinaciones de umbrales o distancias de stop para mercados específicos.
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Contrarian RSI strategy converted from the "Trade on qualified RSI" expert advisor.
/// </summary>
public class TradeOnQualifiedRSIStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _upperThreshold;
	private readonly StrategyParam<decimal> _lowerThreshold;
	private readonly StrategyParam<int> _countBars;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private RelativeStrengthIndex _rsi;
	private decimal? _stopPrice;
	private decimal _entryPrice;
	private int _aboveCounter;
	private int _belowCounter;

	/// <summary>
	/// RSI lookback period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// Upper RSI threshold used to qualify short entries.
	/// </summary>
	public decimal UpperThreshold
	{
		get => _upperThreshold.Value;
		set => _upperThreshold.Value = value;
	}

	/// <summary>
	/// Lower RSI threshold used to qualify long entries.
	/// </summary>
	public decimal LowerThreshold
	{
		get => _lowerThreshold.Value;
		set => _lowerThreshold.Value = value;
	}

	/// <summary>
	/// Number of previous RSI bars that must stay beyond the threshold.
	/// </summary>
	public int CountBars
	{
		get => _countBars.Value;
		set => _countBars.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in price steps.
	/// </summary>
	public int StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Order volume used for entries.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Candle type used as the RSI data source.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="TradeOnQualifiedRSIStrategy"/>.
	/// </summary>
	public TradeOnQualifiedRSIStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 28)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "Lookback period for RSI calculation.", "RSI")
			
			.SetOptimize(10, 50, 2);

		_upperThreshold = Param(nameof(UpperThreshold), 65m)
			.SetDisplay("Upper Threshold", "RSI level used to qualify short signals.", "RSI")

			.SetOptimize(50m, 70m, 1m);

		_lowerThreshold = Param(nameof(LowerThreshold), 35m)
			.SetDisplay("Lower Threshold", "RSI level used to qualify long signals.", "RSI")

			.SetOptimize(30m, 50m, 1m);

		_countBars = Param(nameof(CountBars), 8)
			.SetGreaterThanZero()
			.SetDisplay("Qualification Bars", "How many previous RSI bars must stay beyond the threshold.", "Signals")

			.SetOptimize(1, 10, 1);

		_stopLossPoints = Param(nameof(StopLossPoints), 1000)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss Points", "Stop loss distance expressed in price steps.", "Risk")

			.SetOptimize(5, 50, 5);

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order volume used for entries.", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Source timeframe for RSI calculation.", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		Volume = TradeVolume;
		_stopPrice = null;
		_entryPrice = 0m;
		_aboveCounter = 0;
		_belowCounter = 0;
	}

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

		Volume = TradeVolume;

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

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

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _rsi);
			DrawOwnTrades(area);
		}
	}

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

		if (_rsi == null || !_rsi.IsFormed)
		{
			_aboveCounter = 0;
			_belowCounter = 0;
			return;
		}

		if (Volume <= 0)
			return;

		var distance = CalculateStopDistance();
		if (distance <= 0)
			return;

		UpdateCounters(rsiValue);

		var requiredBars = CountBars + 1;

		if (Position == 0)
		{
			_stopPrice = null;
			_entryPrice = 0m;

			var shortSignal = rsiValue >= UpperThreshold && _aboveCounter >= requiredBars;
			var longSignal = rsiValue <= LowerThreshold && _belowCounter >= requiredBars;

			if (shortSignal)
			{
				this.LogInfo($"Open short: RSI={rsiValue:F2}, counter={_aboveCounter}");
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = candle.ClosePrice + distance;
				return;
			}

			if (longSignal)
			{
				this.LogInfo($"Open long: RSI={rsiValue:F2}, counter={_belowCounter}");
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = candle.ClosePrice - distance;
			}

			return;
		}

		if (Position > 0)
		{
			if (_stopPrice == null)
				_stopPrice = _entryPrice - distance;

			var newStop = candle.ClosePrice - distance;
			if (_stopPrice == null || newStop > _stopPrice)
				_stopPrice = newStop;

			if (_stopPrice != null && candle.LowPrice <= _stopPrice)
			{
				this.LogInfo($"Exit long via stop at {_stopPrice:F5}");
				SellMarket();
				_stopPrice = null;
				_entryPrice = 0m;
			}

			return;
		}

		if (Position < 0)
		{
			if (_stopPrice == null)
				_stopPrice = _entryPrice + distance;

			var newStop = candle.ClosePrice + distance;
			if (_stopPrice == null || newStop < _stopPrice)
				_stopPrice = newStop;

			if (_stopPrice != null && candle.HighPrice >= _stopPrice)
			{
				this.LogInfo($"Exit short via stop at {_stopPrice:F5}");
				BuyMarket();
				_stopPrice = null;
				_entryPrice = 0m;
			}
		}
	}

	private decimal CalculateStopDistance()
	{
		var step = Security?.PriceStep ?? 1m;
		if (step <= 0)
			step = 1m;

		return StopLossPoints * step;
	}

	private void UpdateCounters(decimal rsiValue)
	{
		// Track consecutive closes above and below the thresholds.
		if (rsiValue >= UpperThreshold)
		{
			_aboveCounter++;
		}
		else
		{
			_aboveCounter = 0;
		}

		if (rsiValue <= LowerThreshold)
		{
			_belowCounter++;
		}
		else
		{
			_belowCounter = 0;
		}
	}
}