Ver en GitHub

Estrategia Hpcs Inter6 RSI

Descripción general

La estrategia Hpcs Inter6 RSI transfiere el MetaTrader experto _HPCS_Inter6_MT4_EA_V01_WE al API de alto nivel de StockSharp. El algoritmo observa el índice de fuerza relativa (RSI) en una serie de velas configurables y reacciona a reversiones rápidas alrededor de los umbrales clásicos 70/30. Siempre que RSI cruza por encima de 70, la estrategia pasa a una posición corta, mientras que un cruce por debajo de 30 pasa a una posición larga. Cada operación adjunta inmediatamente niveles simétricos de obtención de beneficios y limitación de pérdidas medidos en pips.

Datos e indicadores

  • Fuente de vela: período de tiempo seleccionado por el usuario (predeterminado 1 hora).
  • Indicador – Índice de fuerza relativa con longitud configurable (predeterminado 14). El indicador se recalcula a través del proceso de vinculación del indicador StockSharp.

Lógica de entrada

  1. La estrategia espera a que se complete la vela para evitar operar con datos incompletos.
  2. En cada vela completa, compara el nuevo valor RSI con el valor anterior.
  3. Configuración breve: si RSI acaba de cruzar por encima de UpperLevel (70 predeterminado) desde abajo, la estrategia vende usando una orden de mercado. La exposición larga existente se cierra antes de que se establezca la venta corta, por lo que la posición neta resultante es corta exactamente en el volumen configurado.
  4. Configuración larga: si RSI acaba de cruzar por debajo de LowerLevel (predeterminado 30) desde arriba, la estrategia compra usando una orden de mercado. Las posiciones cortas existentes se cubren primero, de modo que la posición neta se vuelve larga según el volumen configurado.
  5. Sólo se permite una entrada por vela. Se ignoran varias señales dentro de la misma barra para reflejar la implementación MetaTrader que utiliza la protección de marca de tiempo de la barra.

Lógica de salida

  • Cada entrada define un objetivo fijo y se detiene a la misma distancia medida en pips.
  • Mientras se está en una posición larga, la estrategia sale si el máximo de la vela toca el objetivo o si el mínimo toca el stop protector.
  • Mientras se está en una posición corta, la estrategia cubre si el mínimo de la vela alcanza el objetivo o si el máximo alcanza el tope protector.
  • Cuando la posición es plana, se borran todos los niveles de protección.

La distancia del pip se traduce en unidades de precio utilizando el tamaño del tick del instrumento. Para instrumentos con tres o cinco decimales, el algoritmo multiplica la distancia por diez para que coincida con la noción MetaTrader de un pip.

Parámetros

Parámetro Predeterminado Descripción
CandleType plazo de 1 hora Marco temporal que alimenta el indicador RSI.
RsiLength 14 Período retrospectivo del RSI.
UpperLevel 70 RSI nivel que activa entradas cortas cuando se cruza desde abajo.
LowerLevel 30 RSI nivel que activa entradas largas cuando se cruza desde arriba.
TradeVolume 1 Tamaño de la orden para entradas al mercado. La exposición existente se cierra antes de revertirse.
OffsetInPips 10 Distancia de la toma de ganancias y del stop-loss desde el precio de entrada, expresada en pips.

Todos los parámetros están expuestos a través de objetos StrategyParam para que puedan optimizarse dentro de StockSharp.

Notas

  • La estrategia se basa en velas altas y bajas para simular tomas de ganancias y límites de pérdidas, coincidiendo con el comportamiento de los objetivos de precio fijo en MetaTrader.
  • No se realizan pedidos pendientes; Todas las ejecuciones son órdenes de mercado manejadas por el núcleo de la estrategia.
  • Los enlaces de indicador y gráfico se crean automáticamente cuando hay un área de gráfico disponible, lo que proporciona una superposición visual de velas y RSI.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Port of the MetaTrader strategy _HPCS_Inter6_MT4_EA_V01_WE.
/// Trades RSI reversals at the 70/30 levels with symmetric fixed targets and stops.
/// </summary>
public class HpcsInter6RsiStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<decimal> _upperLevel;
	private readonly StrategyParam<decimal> _lowerLevel;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<decimal> _offsetInPips;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private RelativeStrengthIndex _rsi;
	private decimal? _previousRsi;
	private DateTimeOffset? _lastSignalTime;
	private decimal? _targetPrice;
	private decimal? _stopPrice;
	private bool _isLongPosition;
	private int _candlesSinceTrade;

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

	/// <summary>
	/// RSI lookback length.
	/// </summary>
	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	/// <summary>
	/// Upper RSI level that triggers short entries when crossed from below.
	/// </summary>
	public decimal UpperLevel
	{
		get => _upperLevel.Value;
		set => _upperLevel.Value = value;
	}

	/// <summary>
	/// Lower RSI level that triggers long entries when crossed from above.
	/// </summary>
	public decimal LowerLevel
	{
		get => _lowerLevel.Value;
		set => _lowerLevel.Value = value;
	}

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

	/// <summary>
	/// Target and stop distance expressed in pips.
	/// </summary>
	public decimal OffsetInPips
	{
		get => _offsetInPips.Value;
		set => _offsetInPips.Value = value;
	}

	public int SignalCooldownCandles
	{
		get => _signalCooldownCandles.Value;
		set => _signalCooldownCandles.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="HpcsInter6RsiStrategy"/> class.
	/// </summary>
	public HpcsInter6RsiStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Time frame for RSI evaluation", "General");

		_rsiLength = Param(nameof(RsiLength), 7)
			.SetGreaterThanZero()
			.SetDisplay("RSI Length", "Lookback period for RSI", "Parameters")

			.SetOptimize(5, 40, 1);

		_upperLevel = Param(nameof(UpperLevel), 65m)
			.SetDisplay("Upper RSI", "Upper RSI level for shorts", "Parameters")

			.SetOptimize(60m, 90m, 5m);

		_lowerLevel = Param(nameof(LowerLevel), 35m)
			.SetDisplay("Lower RSI", "Lower RSI level for longs", "Parameters")

			.SetOptimize(10m, 40m, 5m);

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

		_offsetInPips = Param(nameof(OffsetInPips), 30m)
			.SetGreaterThanZero()
			.SetDisplay("Offset (pips)", "Target and stop distance in pips", "Risk")
			
			.SetOptimize(5m, 30m, 5m);

		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_rsi = null;
		_previousRsi = null;
		_lastSignalTime = null;
		_targetPrice = null;
		_stopPrice = null;
		_isLongPosition = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_previousRsi = null;
		_lastSignalTime = null;
		_targetPrice = null;
		_stopPrice = null;
		_isLongPosition = false;
		_candlesSinceTrade = SignalCooldownCandles;

		_rsi = new RelativeStrengthIndex
		{
			Length = RsiLength
		};

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(_rsi, ProcessCandle)
			.Start();

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

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

		if (!_rsi.IsFormed)
			return;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		UpdateActivePositionTargets(candle);

		var previousRsi = _previousRsi;
		_previousRsi = rsiValue;

		if (previousRsi is null)
			return;

		var candleTime = candle.OpenTime;

		if (_lastSignalTime.HasValue && _lastSignalTime.Value == candleTime)
			return;

		if (_candlesSinceTrade >= SignalCooldownCandles && TryEnterShort(candle, rsiValue, previousRsi.Value))
		{
			_lastSignalTime = candleTime;
			_candlesSinceTrade = 0;
			return;
		}

		if (_candlesSinceTrade >= SignalCooldownCandles && TryEnterLong(candle, rsiValue, previousRsi.Value))
		{
			_lastSignalTime = candleTime;
			_candlesSinceTrade = 0;
		}
	}

	private void UpdateActivePositionTargets(ICandleMessage candle)
	{
		if (Position > 0)
		{
			if (!_isLongPosition)
			{
				_targetPrice = null;
				_stopPrice = null;
				return;
			}

			var shouldExit = (_targetPrice.HasValue && candle.HighPrice >= _targetPrice.Value)
				|| (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value);

			if (shouldExit)
			{
				SellMarket(Math.Abs(Position));
				_targetPrice = null;
				_stopPrice = null;
			}
		}
		else if (Position < 0)
		{
			if (_isLongPosition)
			{
				_targetPrice = null;
				_stopPrice = null;
				return;
			}

			var shouldExit = (_targetPrice.HasValue && candle.LowPrice <= _targetPrice.Value)
				|| (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value);

			if (shouldExit)
			{
				BuyMarket(Math.Abs(Position));
				_targetPrice = null;
				_stopPrice = null;
			}
		}
		else
		{
			_targetPrice = null;
			_stopPrice = null;
			_isLongPosition = false;
		}
	}

	private bool TryEnterShort(ICandleMessage candle, decimal currentRsi, decimal previousRsi)
	{
		if (!(currentRsi > UpperLevel && previousRsi <= UpperLevel))
			return false;

		var volume = TradeVolume;
		if (volume <= 0m)
			return false;

		if (Position > 0)
		{
			volume += Math.Abs(Position);
		}

		SellMarket(volume);

		var offset = CalculateOffset();
		if (offset > 0m)
		{
			var entryPrice = candle.ClosePrice;
			_targetPrice = entryPrice - offset;
			_stopPrice = entryPrice + offset;
			_isLongPosition = false;
		}
		else
		{
			_targetPrice = null;
			_stopPrice = null;
			_isLongPosition = false;
		}

		return true;
	}

	private bool TryEnterLong(ICandleMessage candle, decimal currentRsi, decimal previousRsi)
	{
		if (!(currentRsi < LowerLevel && previousRsi >= LowerLevel))
			return false;

		var volume = TradeVolume;
		if (volume <= 0m)
			return false;

		if (Position < 0)
		{
			volume += Math.Abs(Position);
		}

		BuyMarket(volume);

		var offset = CalculateOffset();
		if (offset > 0m)
		{
			var entryPrice = candle.ClosePrice;
			_targetPrice = entryPrice + offset;
			_stopPrice = entryPrice - offset;
			_isLongPosition = true;
		}
		else
		{
			_targetPrice = null;
			_stopPrice = null;
			_isLongPosition = true;
		}

		return true;
	}

	private decimal CalculateOffset()
	{
		var priceStep = Security?.PriceStep ?? 0.01m;
		if (priceStep <= 0m)
			priceStep = 0.01m;

		var decimals = Security?.Decimals ?? 0;
		var factor = decimals is 3 or 5 ? 10m : 1m;

		return OffsetInPips * priceStep * factor;
	}
}