Auf GitHub ansehen

Strategie zum Trading mit qualifiziertem RSI

Übersicht

Diese Strategie reproduziert den MetaTrader-Expertenberater "Trade on qualified RSI" mit der High-Level API von StockSharp. Sie verhält sich als konträres System: Sie interpretiert verlängerte RSI-Messwerte als Erschöpfung und eröffnet eine Position gegen die vorherrschende Bewegung, nachdem der Momentum für mehrere Kerzen anhält. Trailing-Stops werden in Preisschritten verwaltet, sodass der Stop der Position nur folgt, wenn sich der Preis zugunsten der Position bewegt.

Signallogik

Indikator

  • Relativer Stärke-Index mit einem konfigurierbaren Zeitraum (Standard: 28).
  • Berechnet auf dem ausgewählten Kerzen-Abonnement (Standard: 15-Minuten-Kerzen).

Short-Einstieg

  1. Die letzte geschlossene Kerze hat einen RSI größer oder gleich dem oberen Schwellenwert (Standard: 55).
  2. Jede der vorherigen CountBars geschlossenen Kerzen hatte ebenfalls einen RSI über demselben Schwellenwert. Intern zählt die Strategie aufeinanderfolgende Bars; das Signal wird ausgelöst, wenn der Zähler CountBars + 1 erreicht.
  3. Es ist keine aktive Position offen. Wenn ausgelöst, verkauft die Strategie zum Marktpreis mit dem konfigurierten Volumen und speichert den Kerzenschluss als Einstiegspreis.

Long-Einstieg

  1. Die letzte geschlossene Kerze hat einen RSI kleiner oder gleich dem unteren Schwellenwert (Standard: 45).
  2. Jede der vorherigen CountBars geschlossenen Kerzen hatte ebenfalls einen RSI unter demselben Schwellenwert (CountBars + 1 aufeinanderfolgende Messungen sind erforderlich).
  3. Es gibt keine offene Position. Wenn ausgelöst, kauft die Strategie zum Marktpreis mit dem konfigurierten Volumen und notiert den Einstiegspreis.

Positionsverwaltung

  • Anfangs-Stop: Direkt nach dem Einstieg wird der Stop-Preis StopLossPoints Preisschritte vom Eintrittskurs entfernt platziert (unterhalb für Longs, oberhalb für Shorts). Preisschritte werden von Security.PriceStep ermittelt; wenn das Wertpapier es nicht definiert, fällt die Strategie auf 1 zurück.
  • Trailing: Bei jeder abgeschlossenen Kerze wird der Stop in Richtung des aktuellen Schlusskurses gestrafft. Für Long-Positionen wird der Stop zu Schluss - StopLossPoints * PriceStep, wenn dieser Wert über dem vorherigen Stop liegt. Für Short-Positionen wird der Stop zu Schluss + StopLossPoints * PriceStep, wenn dieser Wert unter dem vorherigen Stop liegt.
  • Ausstieg: Wenn das Kerzentief den Stop bei einer Long-Position unterschreitet oder das Kerzenhoch den Stop bei einer Short-Position überschreitet, schließt die Strategie die gesamte Position zum Marktpreis. Es gibt keine zusätzlichen Gewinnziele oder Umkehrsignale; neue Einstiege erfolgen nur nach dem Schließen der vorherigen Position.

Parameter

Name Beschreibung Standard
RsiPeriod Rückblicklänge für den RSI-Indikator. 28
UpperThreshold RSI-Level, das ein Short-Setup qualifiziert. 55
LowerThreshold RSI-Level, das ein Long-Setup qualifiziert. 45
CountBars Wie viele vorherige Bars über dem Schwellenwert bleiben müssen (CountBars + 1 aufeinanderfolgende Bars insgesamt). 5
StopLossPoints Stop-Abstand ausgedrückt in Preisschritten. Der tatsächliche Preisoffset entspricht StopLossPoints * PriceStep. 21
TradeVolume Mit jeder Einstiegsorder gesendetes Volumen. 1
CandleType Für Indikatorberechnungen verwendetes Kerzen-Abonnement. 15-Minuten-Kerzen

Alle Parameter können optimiert werden. Die Schwellenwerte erlauben Dezimalwerte, sodass eine feinkörnige Abstimmung der RSI-Grenzen möglich ist.

Implementierungshinweise

  • Die Strategie verwendet SubscribeCandles(...).Bind(...), um den RSI-Indikator zu speisen und nur zu reagieren, wenn die Kerze vollständig ausgebildet ist.
  • RSI-Werte werden nicht per Index vom Indikator zurückgelesen; stattdessen verfolgen Zähler, wie viele aufeinanderfolgende abgeschlossene Kerzen die Schwellenwerte respektieren.
  • Schutz-Stops werden innerhalb der Strategie simuliert. Orders werden zum Marktpreis geschlossen, wenn das Stop-Level überschritten wird, anstatt separate Stop-Orders zu platzieren.
  • Protokollmeldungen werden für Ein- und Ausstiege erzeugt, was die ausführliche Ausgabe des ursprünglichen Expertenberaters spiegelt.

Verwendung

  1. Fügen Sie die Strategie einer StockSharp-Anwendung hinzu, weisen Sie das gewünschte Wertpapier und Portfolio zu und konfigurieren Sie die Kerzenserie.
  2. Passen Sie die RSI-Schwellenwerte, die Anzahl der qualifizierenden Bars und den Stop-Abstand an die Volatilität des Zielinstruments an.
  3. Starten Sie die Strategie. Überwachen Sie das Protokoll, um zu sehen, wann Signale auftreten und wie sich der Trailing-Stop entwickelt.
  4. Erwägen Sie, den integrierten Optimierer auszuführen, um bessere Kombinationen von Schwellenwerten oder Stop-Abständen für spezifische Märkte zu suchen.
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;
		}
	}
}