Auf GitHub ansehen

RNN Probability Strategie

Übersicht

Die RNN Probability Strategie ist eine Konvertierung des MetaTrader-Experten RNN (barabashkakvn's edition). Der ursprüngliche Roboter sammelt drei RSI-Schnappschüsse, die durch den RSI-Zeitraum getrennt sind, und speist sie in ein handgefertigtes Wahrscheinlichkeitsgitter, das ein rekurrentes neuronales Netzwerk emuliert. Der StockSharp-Port repliziert dieses Verhalten mit der High-Level-Kerzenabonnement-API und konvertiert automatisch die MetaTrader-Lots, Preisschritte und Stop/Ziel-Abstände in StockSharp-Konzepte.

Sobald der RSI-Wert der letzten abgeschlossenen Kerze verfügbar ist, schaut die Strategie um ein und zwei RSI-Perioden zurück, um eine Drei-Punkte-Geschichte zu erstellen. Diese normalisierten Ablesungen werden mit den acht MetaTrader-Gewichten (Weight0Weight7) kombiniert, um eine Wahrscheinlichkeit zu erzeugen, dass der Markt fallen sollte. Die Wahrscheinlichkeit wird in den Bereich [-1; 1] umgerechnet, und das Vorzeichen bestimmt, ob eine Long- oder Short-Position eröffnet wird. Es wird jeweils nur eine Position gehalten, was dem ursprünglichen Expert Advisor entspricht.

Handelslogik

  1. Die konfigurierte Kerzenserie abonnieren und den Indikator RelativeStrengthIndex manuell mit dem ausgewählten AppliedPrice-Eingang verarbeiten (standardmäßig Open).
  2. Die abgeschlossenen RSI-Werte in einem Rollpuffer speichern, der groß genug ist, um auf die RSI-Ablesung von ein und zwei vollständigen Perioden zurück zuzugreifen.
  3. Die drei RSI-Werte auf den Bereich [0; 1] normalisieren und das neuronale Netzgitter auswerten:
    • Der erste Zweig (Weight0, Weight1, Weight2, Weight3) behandelt den Fall, wenn der aktuelle RSI in der unteren Hälfte liegt (unter 50).
    • Der zweite Zweig (Weight4, Weight5, Weight6, Weight7) behandelt den Fall, wenn der aktuelle RSI in der oberen Hälfte liegt.
  4. Die resultierende Wahrscheinlichkeit in ein Handelssignal zwischen -1 und +1 transformieren.
  5. Wenn keine Position offen ist und das Signal negativ ist, TradeVolume Lots kaufen. Wenn das Signal nicht-negativ ist, stattdessen TradeVolume Lots verkaufen.
  6. Optional symmetrische Stop-Loss- und Take-Profit-Niveaus in Pips aktivieren. Die Strategie konvertiert automatisch den Pip-Abstand in einen absoluten Preisversatz, einschließlich der Extraziffer-Anpassung von MetaTrader für 3- und 5-stellige Forex-Symbole.
  7. Jede Entscheidung mit den RSI-Eingaben, der Wahrscheinlichkeit und dem resultierenden Signal protokollieren, das geschwätzige Verhalten des Quell-Experten widerspiegelnd.

Parameter

Name Typ Standard Beschreibung
CandleType DataType 1-Stunden-Zeitrahmen Primäre Kerzenserie für Indikatoraktualisierungen und Signalgenerierung.
TradeVolume decimal 1 Lotgröße mit jeder Marktorder.
RsiPeriod int 9 Länge des RSI-Indikators. Definiert auch den Abstand zwischen den historischen RSI-Stichproben.
AppliedPrice AppliedPriceType Open Preiskomponente für den RSI (Open, Close, High, Low, Median, Typical, Weighted).
StopLossTakeProfitPips decimal 100 Pip-Abstand für Stop-Loss und Take-Profit. Auf null setzen, um Schutzorders zu deaktivieren.
Weight0Weight7 decimal 6, 96, 90, 35, 64, 83, 66, 50 Wahrscheinlichkeitsgewichte für die acht Gitterzweige. Jeder Wert stellt einen Prozentsatz zwischen 0 und 100 dar.

Unterschiede gegenüber dem ursprünglichen MetaTrader-Experten

  • E-Mail-Benachrichtigungen wurden entfernt. StockSharp-Protokolle bieten dieselbe Einsicht ohne Abhängigkeit von einem SMTP-Server.
  • Die Positionsgröße ist auf ein einziges TradeVolume festgelegt. Teilschließungen oder inkrementelles Skalieren sind absichtlich weggelassen, um dem Ein-Positions-Design des Quellcodes zu entsprechen.
  • Indikatordaten werden über das High-Level-Kerzenabonnement von StockSharp geliefert, was manuelle CopyBuffer-Aufrufe und Zeigerarithmetik eliminiert.
  • Die Pip-Konvertierung verwendet den PriceStep des Instruments und kompensiert automatisch 3/5-stellige Forex-Symbole, anstatt auf fest codierte Tick-Größen zurückzugreifen.

Nutzungstipps

  • TradeVolume vor dem Start der Strategie mit dem Mindest-Lot-Schritt des Instruments abstimmen; der Konstruktor überträgt den Wert auch in Strategy.Volume.
  • Die acht Gewichte während der Optimierung einstellen, um das Wahrscheinlichkeitsgitter an verschiedene Märkte anzupassen. Alle Gewichte sind als Optimierungsparameter zugänglich.
  • StopLossTakeProfitPips verringern oder auf null setzen, wenn auf Symbolen mit breiten Spreads oder bei diskretionären Ausstiegen gehandelt wird.
  • Die Strategie einem Diagramm hinzufügen, um Kerzen, RSI und ausgeführte Trades zur einfacheren Validierung der neuronalen Netzausgabe zu visualisieren.

Indikatoren

  • Ein RelativeStrengthIndex, berechnet aus dem gewählten angewendeten Preis.
namespace StockSharp.Samples.Strategies;

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;

/// <summary>
/// Probabilistic strategy converted from the RNN MetaTrader expert.
/// It feeds three delayed RSI readings into the original probability lattice and
/// trades in the direction suggested by the neural network output.
/// </summary>
public class RnnProbabilityStrategy : Strategy
{
	public enum AppliedPriceTypes
	{
		Open,
		High,
		Low,
		Close,
		Median,
		Typical,
		Weighted
	}

	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<AppliedPriceTypes> _appliedPrice;
	private readonly StrategyParam<decimal> _stopLossTakeProfitPips;
	private readonly StrategyParam<decimal> _weight0;
	private readonly StrategyParam<decimal> _weight1;
	private readonly StrategyParam<decimal> _weight2;
	private readonly StrategyParam<decimal> _weight3;
	private readonly StrategyParam<decimal> _weight4;
	private readonly StrategyParam<decimal> _weight5;
	private readonly StrategyParam<decimal> _weight6;
	private readonly StrategyParam<decimal> _weight7;
	private readonly StrategyParam<DataType> _candleType;

	private RelativeStrengthIndex _rsi;
	private readonly List<decimal> _rsiHistory = new();
	private decimal _pipSize;

	/// <summary>
	/// Trade volume expressed in lots.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Averaging period for the RSI indicator.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// Price source forwarded to the RSI indicator.
	/// </summary>
	public AppliedPriceTypes AppliedPrice
	{
		get => _appliedPrice.Value;
		set => _appliedPrice.Value = value;
	}

	/// <summary>
	/// Symmetric stop-loss and take-profit distance expressed in pips.
	/// </summary>
	public decimal StopLossTakeProfitPips
	{
		get => _stopLossTakeProfitPips.Value;
		set => _stopLossTakeProfitPips.Value = value;
	}

	/// <summary>
	/// Neural network weight for the (low, low, low) RSI combination.
	/// </summary>
	public decimal Weight0
	{
		get => _weight0.Value;
		set => _weight0.Value = value;
	}

	/// <summary>
	/// Neural network weight for the (low, low, high) RSI combination.
	/// </summary>
	public decimal Weight1
	{
		get => _weight1.Value;
		set => _weight1.Value = value;
	}

	/// <summary>
	/// Neural network weight for the (low, high, low) RSI combination.
	/// </summary>
	public decimal Weight2
	{
		get => _weight2.Value;
		set => _weight2.Value = value;
	}

	/// <summary>
	/// Neural network weight for the (low, high, high) RSI combination.
	/// </summary>
	public decimal Weight3
	{
		get => _weight3.Value;
		set => _weight3.Value = value;
	}

	/// <summary>
	/// Neural network weight for the (high, low, low) RSI combination.
	/// </summary>
	public decimal Weight4
	{
		get => _weight4.Value;
		set => _weight4.Value = value;
	}

	/// <summary>
	/// Neural network weight for the (high, low, high) RSI combination.
	/// </summary>
	public decimal Weight5
	{
		get => _weight5.Value;
		set => _weight5.Value = value;
	}

	/// <summary>
	/// Neural network weight for the (high, high, low) RSI combination.
	/// </summary>
	public decimal Weight6
	{
		get => _weight6.Value;
		set => _weight6.Value = value;
	}

	/// <summary>
	/// Neural network weight for the (high, high, high) RSI combination.
	/// </summary>
	public decimal Weight7
	{
		get => _weight7.Value;
		set => _weight7.Value = value;
	}

	/// <summary>
	/// Candle series used for indicator calculations and trading decisions.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="RnnProbabilityStrategy"/> class.
	/// </summary>
	public RnnProbabilityStrategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetDisplay("Trade Volume", "Lot size used for each market entry.", "General")
			.SetGreaterThanZero()
			;

		_rsiPeriod = Param(nameof(RsiPeriod), 9)
			.SetDisplay("RSI Period", "Length of the RSI indicator feeding the neural network.", "Indicator")
			.SetRange(2, 200)
			;

		_appliedPrice = Param(nameof(AppliedPrice), AppliedPriceTypes.Open)
			.SetDisplay("Applied Price", "Price type forwarded to the RSI indicator.", "Indicator");

		_stopLossTakeProfitPips = Param(nameof(StopLossTakeProfitPips), 100m)
			.SetDisplay("Stop Loss & Take Profit (pips)", "Distance used for both stop-loss and take-profit levels.", "Risk")
			.SetRange(0m, 1000m)
			;

		_weight0 = Param(nameof(Weight0), 6m)
			.SetDisplay("Weight 0", "Probability weight applied when all RSI inputs are low.", "Model")
			.SetRange(0m, 100m)
			;

		_weight1 = Param(nameof(Weight1), 96m)
			.SetDisplay("Weight 1", "Probability weight for the (low, low, high) branch.", "Model")
			.SetRange(0m, 100m)
			;

		_weight2 = Param(nameof(Weight2), 90m)
			.SetDisplay("Weight 2", "Probability weight for the (low, high, low) branch.", "Model")
			.SetRange(0m, 100m)
			;

		_weight3 = Param(nameof(Weight3), 35m)
			.SetDisplay("Weight 3", "Probability weight for the (low, high, high) branch.", "Model")
			.SetRange(0m, 100m)
			;

		_weight4 = Param(nameof(Weight4), 64m)
			.SetDisplay("Weight 4", "Probability weight for the (high, low, low) branch.", "Model")
			.SetRange(0m, 100m)
			;

		_weight5 = Param(nameof(Weight5), 83m)
			.SetDisplay("Weight 5", "Probability weight for the (high, low, high) branch.", "Model")
			.SetRange(0m, 100m)
			;

		_weight6 = Param(nameof(Weight6), 66m)
			.SetDisplay("Weight 6", "Probability weight for the (high, high, low) branch.", "Model")
			.SetRange(0m, 100m)
			;

		_weight7 = Param(nameof(Weight7), 50m)
			.SetDisplay("Weight 7", "Probability weight for the (high, high, high) branch.", "Model")
			.SetRange(0m, 100m)
			;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe used for signal generation.", "General");
	}

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

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

		_rsi = default;
		_rsiHistory.Clear();
		_pipSize = 0m;
	}

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

		Volume = TradeVolume;

		_pipSize = CalculatePipSize();

		Unit stopLossUnit = null;
		Unit takeProfitUnit = null;

		if (StopLossTakeProfitPips > 0m && _pipSize > 0m)
		{
			var distance = StopLossTakeProfitPips * _pipSize;
			stopLossUnit = new Unit(distance, UnitTypes.Absolute);
			takeProfitUnit = new Unit(distance, UnitTypes.Absolute);
		}

		if (stopLossUnit != null || takeProfitUnit != null)
		{
			StartProtection(
				takeProfit: takeProfitUnit,
				stopLoss: stopLossUnit,
				isStopTrailing: false,
				useMarketOrders: true);
		}

		_rsi = new RelativeStrengthIndex
		{
			Length = RsiPeriod
		};

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

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

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

		if (_rsi == null)
			return;

		if (RsiPeriod <= 0)
			return;

		var price = AppliedPrice switch
		{
			AppliedPriceTypes.Open => candle.OpenPrice,
			AppliedPriceTypes.High => candle.HighPrice,
			AppliedPriceTypes.Low => candle.LowPrice,
			AppliedPriceTypes.Close => candle.ClosePrice,
			AppliedPriceTypes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
			AppliedPriceTypes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
			AppliedPriceTypes.Weighted => (candle.HighPrice + candle.LowPrice + 2m * candle.ClosePrice) / 4m,
			_ => candle.ClosePrice,
		};
		var rsiIndicatorValue = _rsi.Process(new DecimalIndicatorValue(_rsi, price, candle.OpenTime) { IsFinal = true });

		if (!_rsi.IsFormed || rsiIndicatorValue.IsEmpty)
			return;

		var rsiValue = rsiIndicatorValue.ToDecimal();

		_rsiHistory.Add(rsiValue);
		TrimHistory(_rsiHistory, GetHistoryLimit());

		var lastIndex = _rsiHistory.Count - 1;
		var delayedIndex = lastIndex - RsiPeriod;
		var delayedTwiceIndex = lastIndex - (2 * RsiPeriod);

		if (delayedIndex < 0 || delayedTwiceIndex < 0)
			return;

		var p1 = _rsiHistory[lastIndex] / 100m;
		var p2 = _rsiHistory[delayedIndex] / 100m;
		var p3 = _rsiHistory[delayedTwiceIndex] / 100m;

		var probability = CalculateProbability(p1, p2, p3);
		var signal = probability * 2m - 1m;

		LogInfo($"RSI inputs: p1={p1:F4}, p2={p2:F4}, p3={p3:F4}, probability={probability:F4}, signal={signal:F4}");

		if (TradeVolume <= 0m)
			return;

		if (signal < 0m)
		{
			// want long
			if (Position <= 0m)
			{
				var vol = Math.Abs(Position) + TradeVolume;
				BuyMarket(vol);
			}
		}
		else
		{
			// want short
			if (Position >= 0m)
			{
				var vol = Position + TradeVolume;
				SellMarket(vol);
			}
		}
	}

	private decimal CalculateProbability(decimal p1, decimal p2, decimal p3)
	{
		var pn1 = 1m - p1;
		var pn2 = 1m - p2;
		var pn3 = 1m - p3;

		var probability =
			pn1 * (pn2 * (pn3 * Weight0 + p3 * Weight1) +
			        p2 * (pn3 * Weight2 + p3 * Weight3)) +
			p1 * (pn2 * (pn3 * Weight4 + p3 * Weight5) +
			        p2 * (pn3 * Weight6 + p3 * Weight7));

		return probability / 100m;
	}

	private int GetHistoryLimit()
	{
		return Math.Max((2 * RsiPeriod) + 5, RsiPeriod + 1);
	}

	private static void TrimHistory<T>(List<T> source, int maxSize)
	{
		if (maxSize <= 0)
			return;

		if (source.Count <= maxSize)
			return;

		var removeCount = source.Count - maxSize;
		source.RemoveRange(0, removeCount);
	}

	private decimal CalculatePipSize()
	{
		if (Security == null)
			return 0m;

		var step = Security.PriceStep ?? 0m;
		if (step <= 0m)
			return 0m;

		var decimals = GetDecimalPlaces(step);
		if (decimals == 3 || decimals == 5)
			return step * 10m;

		return step;
	}

	private static int GetDecimalPlaces(decimal value)
	{
		value = Math.Abs(value);

		var decimals = 0;

		while (value != Math.Truncate(value) && decimals < 10)
		{
			value *= 10m;
			decimals++;
		}

		return decimals;
	}
}