Auf GitHub ansehen

4218 RSI MA-Strategie

Überblick

Diese Strategie ist eine C#-Portierung des ursprünglichen MetaTrader Expert Advisors, der sich in MQL/9925 befindet. Es bildet den Impulsoszillator RSI_MA nach, indem ein klassischer RSI mit der Steigung eines exponentiellen gleitenden Durchschnitts kombiniert wird, der auf dem gewichteten Preis (High + Low + 2 * Close) / 4 basiert. Signale werden nur für abgeschlossene Kerzen generiert, sodass das Verhalten mit der Quellimplementierung identisch bleibt.

Das Skript ist für tägliche EURUSD-Kerzen (D1-Zeitrahmen) konzipiert und eröffnet jeweils eine einzelne Position. Dennoch kann jedes Instrument mit einem sinnvollen Preissprung verwendet werden, sofern der Kerzentyp entsprechend konfiguriert ist.

Strategielogik

  1. Indikatorberechnung
    • Auf Basis der Schlusskurse wird ein Relative-Stärke-Index mit konfigurierbarer Länge berechnet.
    • Aus dem gewichteten Preis wird ein exponentieller gleitender Durchschnitt mit derselben Länge berechnet.
    • Der Indikatorwert beträgt RSI * (EMA(current) - EMA(previous)) / pipSize und ist auf den Bereich [1, 99] begrenzt.
  2. Langer Eintrag
    • Vorheriger Indikatorwert unterhalb des überverkauften Extremwerts (Standard 5).
    • Letzter Indikatorwert über dem überverkauften Aktivierungsschwellenwert (Standard 20).
    • Keine offene Position oder bestehende Short-Position (der Short wird geschlossen, bevor eine neue Long-Position eröffnet wird).
  3. Kurzer Eintrag
    • Vorheriger Indikatorwert über dem überkauften Extremwert (Standard 95).
    • Letzter Indikatorwert unterhalb der überkauften Aktivierungsschwelle (Standard 80).
    • Keine offene Position oder bestehende Long-Position (die Long-Position wird geschlossen, bevor eine neue Short-Position eröffnet wird).
  4. Indikatorbasierter Ausstieg
    • Long-Positionen werden geschlossen, wenn der Indikator von über dem überkauften Extremwert auf unter das Aktivierungsniveau fällt (standardmäßig 95 → 80).
    • Short-Positionen werden geschlossen, wenn der Indikator von unterhalb des überverkauften Extremwerts auf über das Aktivierungsniveau (standardmäßig 5 → 20) steigt.
  5. Schutzausgänge
    • Optionale Stop-Loss-, Take-Profit- und Trailing-Stop-Abstände werden in Pips ausgedrückt. Entfernungen werden mithilfe der Sicherheit PriceStep (Fallback 0,0001) automatisch in Preise umgerechnet.
    • Die Straffung des Trailing-Stops folgt dem Verhalten des ursprünglichen EA: Sie wird erst aktiviert, wenn sich der Preis um mehr als die konfigurierte Distanz in die günstige Richtung bewegt.

Parameter

Parameter Beschreibung
RsiPeriod RSI und EMA Länge.
OversoldActivationLevel Schwellenwert, der einen langen Aufbau nach einem überverkauften Extrem bestätigt.
OversoldExtremeLevel Extremwert, der erreicht werden muss, bevor Long-Positionen zugelassen werden.
OverboughtActivationLevel Schwellenwert, der einen Short-Setup nach einem überkauften Extrem bestätigt.
OverboughtExtremeLevel Extremwert, der erreicht werden muss, bevor Shorts erlaubt sind.
StopLossPips Abstand für den schützenden Stop-Loss. Aktivieren/deaktivieren über UseStopLoss.
TakeProfitPips Distanz zum Gewinnziel. Aktivieren/deaktivieren über UseTakeProfit.
TrailingStopPips Distanz für den Trailing Stop. Aktivieren/deaktivieren über UseTrailingStop.
UseStopLoss Aktiviert das Stop-Loss-Management.
UseTakeProfit Aktiviert das Take-Profit-Management.
UseTrailingStop Aktiviert Trailing-Stop-Updates.
UseMoneyManagement Aktiviert die Positionsgröße basierend auf RiskPercent.
RiskPercent Risikoprozentsatz des Portfolios pro Trade, wenn das Geldmanagement aktiv ist.
TradeVolume Festes Volumen, das verwendet wird, wenn die Geldverwaltung deaktiviert ist.
CandleType Datentyp der von der Strategie verarbeiteten Kerzen (Standard: Täglich).

Nutzungshinweise

  • Hängen Sie die Strategie an EURUSD-Tageskerzen an, um das Verhalten des ursprünglichen EA zu reproduzieren. Andere Instrumente/Zeitrahmen werden nach Anpassung von CandleType und Schwellenwerten unterstützt.
  • Es bleibt immer nur eine Position offen. Bei der Eingabe eines neuen Trades wird automatisch zuerst die Gegenrichtung geschlossen.
  • Das Geldmanagement greift auf den festen TradeVolume zurück, wenn keine Portfolioinformationen verfügbar sind oder das berechnete Volumen nicht mehr positiv ist.
  • Stellen Sie sicher, dass das Wertpapier PriceStep einen Pip widerspiegelt (0,0001 für die meisten FX-Paare). Ansonsten passen Sie die Parameter entsprechend an.

Risikomanagement

  • Stop-Loss- und Take-Profit-Level werden für jede abgeschlossene Kerze anhand der Hoch-/Tiefstbereiche der Kerze bewertet.
  • Der Trailing Stop wird nur aktualisiert, wenn der Trade mehr als die konfigurierte Distanz im Gewinn ist und sich nie in eine ungünstige Richtung bewegt.
  • Indikatorbasierte Exits funktionieren auch dann noch, wenn die Risikokontrollen deaktiviert sind, und sorgen so für eine sanfte Herabstufung ähnlich der MQL-Version.
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;

using StockSharp.Algo.Candles;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// RSI and EMA based strategy converted from the original MQL implementation.
/// Combines a custom RSI*EMA momentum oscillator with basic risk management.
/// </summary>
public class RsiMaStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _oversoldActivationLevel;
	private readonly StrategyParam<decimal> _oversoldExtremeLevel;
	private readonly StrategyParam<decimal> _overboughtActivationLevel;
	private readonly StrategyParam<decimal> _overboughtExtremeLevel;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<bool> _useStopLoss;
	private readonly StrategyParam<bool> _useTakeProfit;
	private readonly StrategyParam<bool> _useTrailingStop;
	private readonly StrategyParam<bool> _useMoneyManagement;
	private readonly StrategyParam<decimal> _riskPercent;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;
	
	private RelativeStrengthIndex _rsi;
	private ExponentialMovingAverage _ema;
	
	private decimal? _previousIndicatorValue;
	
	private decimal? _stopLossPrice;
	private decimal? _takeProfitPrice;
	private decimal _entryPrice;
	
	public RsiMaStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
		.SetDisplay("RSI Period", string.Empty, "Oscillator")
		;
		
		_oversoldActivationLevel = Param(nameof(OversoldActivationLevel), 40m)
		.SetDisplay("Oversold Activation", string.Empty, "Oscillator")
		;
		
		_oversoldExtremeLevel = Param(nameof(OversoldExtremeLevel), 30m)
		.SetDisplay("Oversold Extreme", string.Empty, "Oscillator");
		
		_overboughtActivationLevel = Param(nameof(OverboughtActivationLevel), 60m)
		.SetDisplay("Overbought Activation", string.Empty, "Oscillator")
		;
		
		_overboughtExtremeLevel = Param(nameof(OverboughtExtremeLevel), 70m)
		.SetDisplay("Overbought Extreme", string.Empty, "Oscillator");
		
		_stopLossPips = Param(nameof(StopLossPips), 399m)
		.SetDisplay("Stop Loss (pips)", string.Empty, "Risk");
		
		_takeProfitPips = Param(nameof(TakeProfitPips), 999m)
		.SetDisplay("Take Profit (pips)", string.Empty, "Risk");
		
		_trailingStopPips = Param(nameof(TrailingStopPips), 299m)
		.SetDisplay("Trailing Stop (pips)", string.Empty, "Risk");
		
		_useStopLoss = Param(nameof(UseStopLoss), true)
		.SetDisplay("Use Stop Loss", string.Empty, "Risk");
		
		_useTakeProfit = Param(nameof(UseTakeProfit), true)
		.SetDisplay("Use Take Profit", string.Empty, "Risk");
		
		_useTrailingStop = Param(nameof(UseTrailingStop), true)
		.SetDisplay("Use Trailing Stop", string.Empty, "Risk");
		
		_useMoneyManagement = Param(nameof(UseMoneyManagement), false)
		.SetDisplay("Use Risk Percent Position Sizing", string.Empty, "Position");
		
		_riskPercent = Param(nameof(RiskPercent), 10m)
		.SetDisplay("Risk Percent", string.Empty, "Position");
		
		_tradeVolume = Param(nameof(TradeVolume), 0.1m)
		.SetDisplay("Fixed Volume", string.Empty, "Position");
		
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
		.SetDisplay("Candle TimeFrame", string.Empty, "General");
	}
	
	/// <summary>
	/// RSI calculation period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}
	
	/// <summary>
	/// Activation threshold after an oversold extreme.
	/// </summary>
	public decimal OversoldActivationLevel
	{
		get => _oversoldActivationLevel.Value;
		set => _oversoldActivationLevel.Value = value;
	}
	
	/// <summary>
	/// Oversold extreme required before a long setup becomes valid.
	/// </summary>
	public decimal OversoldExtremeLevel
	{
		get => _oversoldExtremeLevel.Value;
		set => _oversoldExtremeLevel.Value = value;
	}
	
	/// <summary>
	/// Activation threshold after an overbought extreme.
	/// </summary>
	public decimal OverboughtActivationLevel
	{
		get => _overboughtActivationLevel.Value;
		set => _overboughtActivationLevel.Value = value;
	}
	
	/// <summary>
	/// Overbought extreme required before a short setup becomes valid.
	/// </summary>
	public decimal OverboughtExtremeLevel
	{
		get => _overboughtExtremeLevel.Value;
		set => _overboughtExtremeLevel.Value = value;
	}
	
	/// <summary>
	/// Stop-loss distance measured in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}
	
	/// <summary>
	/// Take-profit distance measured in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}
	
	/// <summary>
	/// Trailing-stop distance measured in pips.
	/// </summary>
	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}
	
	/// <summary>
	/// Enable or disable stop-loss management.
	/// </summary>
	public bool UseStopLoss
	{
		get => _useStopLoss.Value;
		set => _useStopLoss.Value = value;
	}
	
	/// <summary>
	/// Enable or disable take-profit management.
	/// </summary>
	public bool UseTakeProfit
	{
		get => _useTakeProfit.Value;
		set => _useTakeProfit.Value = value;
	}
	
	/// <summary>
	/// Enable or disable trailing stop adjustments.
	/// </summary>
	public bool UseTrailingStop
	{
		get => _useTrailingStop.Value;
		set => _useTrailingStop.Value = value;
	}
	
	/// <summary>
	/// Enable or disable percent based position sizing.
	/// </summary>
	public bool UseMoneyManagement
	{
		get => _useMoneyManagement.Value;
		set => _useMoneyManagement.Value = value;
	}
	
	/// <summary>
	/// Portfolio risk percentage when money management is enabled.
	/// </summary>
	public decimal RiskPercent
	{
		get => _riskPercent.Value;
		set => _riskPercent.Value = value;
	}
	
	/// <summary>
	/// Fixed volume used when money management is disabled.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}
	
	/// <summary>
	/// Candle type used for signal generation.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, CandleType);
	}
	
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_rsi = null;
		_ema = null;
		_previousIndicatorValue = null;
		_stopLossPrice = null;
		_takeProfitPrice = null;
		_entryPrice = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		
		_rsi = new RelativeStrengthIndex
		{
			Length = RsiPeriod
		};
		
		_ema = new ExponentialMovingAverage
		{
			Length = RsiPeriod
		};
		
		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_rsi, ProcessCandle)
			.Start();
		
		StartProtection(null, null);
	}
	
	private void ProcessCandle(ICandleMessage candle, decimal rsiValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!_rsi.IsFormed)
			return;

		var indicatorValue = rsiValue;

		if (_previousIndicatorValue is decimal previousValue)
		{
			ManageOpenPosition(candle, previousValue, indicatorValue);
			EvaluateEntries(candle, previousValue, indicatorValue);
		}

		_previousIndicatorValue = indicatorValue;
	}
	
	private void ManageOpenPosition(ICandleMessage candle, decimal previousValue, decimal currentValue)
	{
		if (Position > 0)
		{
			var exitSignal = previousValue > OverboughtExtremeLevel && currentValue < OverboughtActivationLevel;
			if (exitSignal)
			{
				SellMarket();
				ResetRiskLevels();
				return;
			}

			UpdateTrailingStopForLong(candle);

			if (ShouldCloseLong(candle))
			{
				SellMarket();
				ResetRiskLevels();
			}
		}
		else if (Position < 0)
		{
			var exitSignal = previousValue < OversoldExtremeLevel && currentValue > OversoldActivationLevel;
			if (exitSignal)
			{
				BuyMarket();
				ResetRiskLevels();
				return;
			}

			UpdateTrailingStopForShort(candle);

			if (ShouldCloseShort(candle))
			{
				BuyMarket();
				ResetRiskLevels();
			}
		}
	}
	
	private void EvaluateEntries(ICandleMessage candle, decimal previousValue, decimal currentValue)
	{
		var price = candle.ClosePrice;
		if (price <= 0m)
			return;
		
		if (previousValue < OversoldExtremeLevel && currentValue > OversoldActivationLevel && Position <= 0)
		{
			_entryPrice = price;
			InitializeRiskLevelsForLong(price);
			BuyMarket();
		}
		else if (previousValue > OverboughtExtremeLevel && currentValue < OverboughtActivationLevel && Position >= 0)
		{
			_entryPrice = price;
			InitializeRiskLevelsForShort(price);
			SellMarket();
		}
	}
	
	private void InitializeRiskLevelsForLong(decimal price)
	{
		var pipDistance = GetPipSize();
		
		if (UseStopLoss && StopLossPips > 0m)
			_stopLossPrice = price - pipDistance * StopLossPips;
		else
			_stopLossPrice = null;
		
		if (UseTakeProfit && TakeProfitPips > 0m)
			_takeProfitPrice = price + pipDistance * TakeProfitPips;
		else
			_takeProfitPrice = null;
	}
	
	private void InitializeRiskLevelsForShort(decimal price)
	{
		var pipDistance = GetPipSize();
		
		if (UseStopLoss && StopLossPips > 0m)
			_stopLossPrice = price + pipDistance * StopLossPips;
		else
			_stopLossPrice = null;
		
		if (UseTakeProfit && TakeProfitPips > 0m)
			_takeProfitPrice = price - pipDistance * TakeProfitPips;
		else
			_takeProfitPrice = null;
	}
	
	private void UpdateTrailingStopForLong(ICandleMessage candle)
	{
		if (!UseTrailingStop || TrailingStopPips <= 0m)
			return;
		
		var pipDistance = GetPipSize() * TrailingStopPips;
		if (pipDistance <= 0m)
			return;
		
		var profit = candle.ClosePrice - _entryPrice;
		if (profit <= pipDistance)
			return;
		
		var newStop = candle.ClosePrice - pipDistance;
		if (_stopLossPrice is null || newStop > _stopLossPrice)
			_stopLossPrice = newStop;
	}
	
	private void UpdateTrailingStopForShort(ICandleMessage candle)
	{
		if (!UseTrailingStop || TrailingStopPips <= 0m)
			return;
		
		var pipDistance = GetPipSize() * TrailingStopPips;
		if (pipDistance <= 0m)
			return;
		
		var profit = _entryPrice - candle.ClosePrice;
		if (profit <= pipDistance)
			return;
		
		var newStop = candle.ClosePrice + pipDistance;
		if (_stopLossPrice is null || newStop < _stopLossPrice)
			_stopLossPrice = newStop;
	}
	
	private bool ShouldCloseLong(ICandleMessage candle)
	{
		var stopHit = UseStopLoss && _stopLossPrice is decimal stop && candle.LowPrice <= stop;
		var takeProfitHit = UseTakeProfit && _takeProfitPrice is decimal takeProfit && candle.HighPrice >= takeProfit;
		return stopHit || takeProfitHit;
	}
	
	private bool ShouldCloseShort(ICandleMessage candle)
	{
		var stopHit = UseStopLoss && _stopLossPrice is decimal stop && candle.HighPrice >= stop;
		var takeProfitHit = UseTakeProfit && _takeProfitPrice is decimal takeProfit && candle.LowPrice <= takeProfit;
		return stopHit || takeProfitHit;
	}
	
	private void ResetRiskLevels()
	{
		_stopLossPrice = null;
		_takeProfitPrice = null;
		_entryPrice = 0m;
	}
	
	private decimal GetPipSize()
	{
		var priceStep = Security?.PriceStep;
		if (priceStep is null || priceStep == 0m)
			return 0.0001m;
		
		return priceStep.Value;
	}
	
	private decimal GetOrderVolume(decimal price)
	{
		var volume = TradeVolume;

		if (!UseMoneyManagement || Portfolio is null || price <= 0m)
			return volume;

		var portfolioValue = Portfolio.CurrentValue ?? 0m;
		if (portfolioValue <= 0m)
			return volume;

		var riskAmount = portfolioValue * RiskPercent / 100m;
		if (riskAmount <= 0m)
			return volume;

		var estimatedVolume = riskAmount / price;

		var volumeStep = Security?.VolumeStep ?? 0m;
		if (volumeStep > 0m)
		{
			estimatedVolume = Math.Floor(estimatedVolume / volumeStep) * volumeStep;
		}

		if (estimatedVolume <= 0m)
			estimatedVolume = volume;

		return estimatedVolume;
	}
}