Auf GitHub ansehen

Strategie IFS Fractals

Überblick

IFS Fractals ist ein Port des MetaTrader 5-Skripts IFS_Fractals. Der ursprüngliche Experte rendert eine IFS-Bitmap (Iterated Function System) des „Fraktalworts“, indem er wiederholt 28 affine Transformationen auf eine Punktwolke anwendet. Die StockSharp-Version verwandelt denselben chaotischen Prozess in einen Richtungsoszillator: Die X-Koordinate der generierten Punkte wird skaliert, mit einem exponentiellen gleitenden Durchschnitt (EMA) geglättet und als Impulsmaß interpretiert, das Long- und Short-Einstiege steuert.

Strategielogik

Iteriertes Funktionssystem

  • Affine Transformationen – jede fertige Kerze löst eine Reihe von Iterationen aus (konfigurierbar). Während jeder Iteration wird eine der 28 Transformationen entsprechend den ursprünglichen Wahrscheinlichkeitsgewichten (alle gleich 35) ausgewählt. Die Transformation aktualisiert den aktuellen Punkt (x, y) unter Verwendung der Koeffizienten, die wörtlich aus dem MQL5-Code übernommen wurden.
  • Wahrscheinlichkeitstabelle – die Strategie berechnet einmal beim Start ein kumulatives Wahrscheinlichkeitsarray vor und ermöglicht so eine schnelle Auswahl der nächsten Transformation mithilfe einer einzigen Zufallsziehung innerhalb der gesamten Wahrscheinlichkeitsmasse.

Signalbau

  • Normalisierung – die X-Koordinate wird durch denselben Skalierungsfaktor (standardmäßig 50) geteilt, den das Skript beim Projizieren des Fraktals auf die Bitmap verwendet hat. Dadurch bleibt das Signal unabhängig vom Instrumentenpreis in einem stabilen numerischen Bereich.
  • EMA-Glättung – die normalisierte Reihe speist einen EMA, dessen Periode konfigurierbar ist. Der EMA fungiert als Tiefpassfilter, der die dominante Drift der chaotischen Iterationen extrahiert.
  • Einstiegslogik – wenn der EMA über die positive Einstiegsschwelle steigt, öffnet sich die Strategie oder kehrt sich in eine Long-Position um. Wenn der EMA unter den negativen Schwellenwert fällt, öffnet er sich symmetrisch oder kehrt sich in einen Short um.
  • Ausstiegslogik – offene Long-Positionen steigen aus, sobald der EMA wieder auf oder unter die Ausstiegsschwelle fällt, während Short-Positionen aussteigen, wenn der EMA wieder über die negative Ausstiegsschwelle steigt. Dadurch entsteht ein Hystereseband, das ein schnelles Umkippen um den Nullpunkt herum verhindert.

Risikomanagement

  • Positionsschutz – optionale absolute Stop-Loss- und Take-Profit-Distanzen können über StartProtection aktiviert werden. Ein Wert von 0 deaktiviert die entsprechende Ebene und entspricht dem Verhalten des Quellskripts, das ohne Schutzbefehle ausgeführt wurde.
  • Volumenkontrolle – Einträge verwenden einen festen Marktvolumenparameter. Jedes bestehende gegenläufige Engagement wird geschlossen, bevor ein neuer Handel eröffnet wird, um eine Position in einer einzigen Richtung aufrechtzuerhalten.

Parameter

  • Volumen – Marktvolumen für Neuzugänge.
  • Kerzentyp – Zeitrahmen, der die fraktalen Iterationen steuert (Standard: 5-Minuten-Kerzen).
  • Iterationen – Anzahl der IFS-Iterationen, die nach jeder fertigen Kerze verarbeitet werden.
  • Skalierung – Divisor, der auf die X-Koordinate angewendet wird, bevor sie in EMA eingespeist wird.
  • Einstiegsschwelle – absoluter EMA-Wert, der zum Öffnen einer Position erforderlich ist (positiv für Long-Positionen, negativ gespiegelt für Short-Positionen).
  • Ausgangsschwellenwert – EMA-Wert, der Ausgänge auslöst, wenn das Signal in Richtung Null zurückkehrt.
  • EMA Periode – Glättungsperiode des exponentiellen gleitenden Durchschnitts, der auf das normalisierte fraktale Signal angewendet wird.
  • Take Profit – absolute Take-Profit-Distanz; zum Deaktivieren auf 0 setzen.
  • Stop-Loss – absolute Stop-Loss-Distanz; zum Deaktivieren auf 0 setzen.

Zusätzliche Hinweise

  • Jeder Lauf erzeugt eine andere Handelssequenz, es sei denn, durch Modifizieren der Quelle wird ein deterministischer Zufallsstartwert eingefügt. Dies spiegelt die Zufälligkeit des ursprünglichen Bitmap-Rendering-Skripts wider.
  • Die Strategie erfordert keine vom Markt abgeleiteten Indikatoren. Alle Daten werden intern aus den IFS-Koeffizienten generiert, sodass die abonnierten Kerzen lediglich das Timing für die Iterationen liefern.
  • In diesem Paket ist keine Python-Implementierung enthalten. Unter CS/ ist nur die C#-Strategie verfügbar.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// IFS Fractals strategy: Williams %R oscillator crossover.
/// Buys when WPR crosses above oversold, sells when crosses below overbought.
/// </summary>
public class IfsFractalsStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _wprPeriod;
	private readonly StrategyParam<decimal> _oversold;
	private readonly StrategyParam<decimal> _overbought;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _prevWpr;
	private int _candlesSinceTrade;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int WprPeriod { get => _wprPeriod.Value; set => _wprPeriod.Value = value; }
	public decimal Oversold { get => _oversold.Value; set => _oversold.Value = value; }
	public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public IfsFractalsStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_wprPeriod = Param(nameof(WprPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("WPR Period", "Williams %R period", "Indicators");
		_oversold = Param(nameof(Oversold), -85m)
			.SetDisplay("Oversold", "WPR oversold level", "Signals");
		_overbought = Param(nameof(Overbought), -15m)
			.SetDisplay("Overbought", "WPR overbought level", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevWpr = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevWpr = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
		var wpr = new WilliamsR { Length = WprPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(wpr, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (_hasPrev)
		{
			if (_prevWpr < Oversold && wprValue >= Oversold && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (_prevWpr > Overbought && wprValue <= Overbought && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevWpr = wprValue;
		_hasPrev = true;
	}
}