Auf GitHub ansehen

Hpcs Inter6 RSI Strategie

Überblick

Die Hpcs Inter6 RSI-Strategie portiert den MetaTrader-Experten _HPCS_Inter6_MT4_EA_V01_WE zum StockSharp-High-Level-API. Der Algorithmus beobachtet den Relative Strength Index (RSI) einer konfigurierbaren Kerzenreihe und reagiert auf schnelle Umkehrungen um die klassischen 70/30-Schwellenwerte. Immer wenn RSI 70 überschreitet, geht die Strategie in eine Short-Position über, während ein Wert unter 30 in eine Long-Position übergeht. Mit jedem Trade werden sofort symmetrische Take-Profit- und Stop-Loss-Werte verbunden, die in Pips gemessen werden.

Daten und Indikatoren

  • Kerzenquelle – vom Benutzer ausgewählter Zeitrahmen (Standard 1 Stunde).
  • Indikator – Relativer Stärkeindex mit konfigurierbarer Länge (Standard 14). Der Indikator wird über die Indikatorbindungspipeline StockSharp neu berechnet.

Eingabelogik

  1. Die Strategie wartet auf eine fertige Kerze, um den Handel mit unvollständigen Daten zu vermeiden.
  2. Bei jeder abgeschlossenen Kerze wird der neue RSI-Wert mit dem vorherigen Wert verglichen.
  3. Kurzeinstellung: Wenn RSI gerade von unten über UpperLevel (Standard 70) gekreuzt ist, verkauft die Strategie mithilfe einer Marktorder. Bestehende Long-Positionen werden geschlossen, bevor die Short-Position hergestellt wird, sodass die resultierende Nettoposition genau um das konfigurierte Volumen short ist.
  4. Langes Setup: Wenn RSI gerade von oben unter LowerLevel (Standard 30) gekreuzt ist, kauft die Strategie mithilfe einer Marktorder. Vorhandene Short-Positionen werden zunächst abgedeckt, so dass die Nettoposition um das konfigurierte Volumen long wird.
  5. Es ist nur ein Eintrag pro Kerze zulässig. Mehrere Signale innerhalb desselben Balkens werden ignoriert, um die MetaTrader-Implementierung widerzuspiegeln, die den Zeitstempelschutz des Balkens verwendet.

Exit-Logik

  • Jeder Eintrag definiert ein festes Ziel und einen Stopp bei derselben Entfernung, gemessen in Pips.
  • Bei einer Long-Position wird die Strategie beendet, wenn das Kerzenhoch das Ziel oder das Tief den Schutzstopp berührt.
  • In einer Short-Position deckt die Strategie ab, ob das Kerzentief das Ziel erreicht oder ob das Hoch den Schutzstopp erreicht.
  • Wenn die Position flach ist, werden alle Schutzebenen gelöscht.

Der Pip-Abstand wird anhand der Tick-Größe des Instruments in Preiseinheiten umgerechnet. Bei Instrumenten mit drei oder fünf Dezimalstellen multipliziert der Algorithmus den Abstand mit zehn, um dem MetaTrader-Konzept eines Pip zu entsprechen.

Parameter

Parameter Standard Beschreibung
CandleType 1-stündiger Zeitrahmen Zeitrahmen, der den Indikator RSI speist.
RsiLength 14 Lookback-Zeitraum des RSI.
UpperLevel 70 RSI-Stufe, die bei Überschreitung von unten kurze Einstiege auslöst.
LowerLevel 30 RSI-Ebene, die bei Überschreitung von oben lange Einträge auslöst.
TradeVolume 1 Ordergröße für Markteintritte. Vorhandenes Exposure wird vor der Umkehrung geschlossen.
OffsetInPips 10 Abstand von Take-Profit und Stop-Loss vom Einstiegspreis, ausgedrückt in Pips.

Alle Parameter werden über StrategyParam-Objekte verfügbar gemacht, sodass sie innerhalb von StockSharp optimiert werden können.

Notizen

  • Die Strategie basiert auf dem Hoch/Tief der Kerze, um Take-Profit- und Stop-Loss-Füllungen zu simulieren, was dem Verhalten von Festpreiszielen in MetaTrader entspricht.
  • Es werden keine ausstehenden Bestellungen aufgegeben; Alle Ausführungen sind Marktaufträge, die vom Strategiekern abgewickelt werden.
  • Die Indikator- und Diagrammbindungen werden automatisch erstellt, wenn ein Diagrammbereich verfügbar ist, und bieten eine visuelle Überlagerung von Kerzen und 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;
	}
}