Auf GitHub ansehen

Strategie FuturePatternMemoryStrategy

Überblick

FuturePatternMemoryStrategy ist eine StockSharp-Portierung der klassischen MetaTrader-Experten FutureMA und FutureMACD. Die ursprünglichen Roboter zeichneten Sequenzen von Indikatorunterschieden in CSV-Dateien auf, verwendeten die gespeicherten Statistiken wieder und entschieden, ob die aktuellen Bedingungen bullische oder bärische Ausbrüche begünstigten. Diese C#-Version behält die gleiche Idee bei, ersetzt aber das Dateisystem durch ein In-Memory-Pattern-Warehouse und macht jeden Knopf als Strategieparameter verfügbar. Die Strategie kann entweder auf der geglätteten gleitenden Durchschnittsspanne (die FutureMA-Logik) oder auf dem MACD-Histogramm (die FutureMACD-Logik) basieren.

Die Strategie bewertet jede fertige Kerze in fünf Stufen:

  1. Indikatorprojektion – Berechnen Sie den ausgewählten Oszillator (MA-Spreizung oder MACD-Histogramm) und normalisieren Sie ihn mit einem konfigurierbaren Skalierungsfaktor. Die Werte werden in Ganzzahlen diskretisiert, um kompakte Mustersignaturen zu erstellen.
  2. Muster-Hashing – verwaltet ein gleitendes Fenster der neuesten AnalysisBars normalisierten Werte. Jedes Mal, wenn ein neuer Balken geschlossen wird, wird das Fenster in einen eindeutigen Hash-String umgewandelt, der den aktuellen Marktkontext identifiziert.
  3. Historische Swing-Analyse – Untersuchen Sie die vorherigen FractalDepth-Kerzen, messen Sie den Abstand zwischen der ältesten Eröffnung und den höchsten/tiefsten Extremwerten und wandeln Sie diese Bereiche in Punkte um. Diese Distanzen sind die Belohnungserwartungen, die die ursprünglichen Roboter in ihren CSV-Dateien gesammelt haben.
  4. Gewichtete Speicheraktualisierung – der Hash-Schlüssel wird verwendet, um einen Eintrag im Musterwörterbuch abzurufen oder zu erstellen. Die bullischen und bärischen Take-Profit-Erwartungen werden mit einem gewichteten gleitenden Durchschnitt aktualisiert, der durch ForgettingFactor gesteuert wird und den „Vergesslichkeits“-Koeffizienten (zabyvaemost) aus dem MQL-Code reproduziert.
  5. Signalauswertung und -ausführung – wenn die bullische Erwartung dominiert, das Muster mehr als MinimumMatches Mal gesehen wurde und der prognostizierte Gewinn über MinimumTakeProfit liegt, geht die Strategie eine Long-Position ein oder erhöht diese. Der bärische Zweig funktioniert symmetrisch. Schutzniveaus werden aus den gespeicherten Statistiken abgeleitet und optional verfolgt, wenn sich der Handel positiv entwickelt.

Konvertierungshinweise

  • Beide MetaTrader-Experten werden über den Parameter Source zu einer konfigurierbaren Strategie zusammengeführt, sodass Sie ohne Neukompilierung zwischen der MA-basierten Engine und der MACD-basierten Engine wechseln können.
  • Die dateibasierte Persistenz wurde durch ein Dictionary<string, PatternStats> ersetzt, das alle Statistiken während der Ausführung im Speicher behält. Dies vermeidet Datei-E/A und bleibt innerhalb des StockSharp-Sandbox-Modells.
  • Das Positionsmanagement repliziert die ursprüngliche Stop-/Zielplatzierung: Der Stop nutzt den vollen durchschnittlichen Swing, während der Take-Profit StatisticalTakeRatio (das Original Stat_Take_Profit) verwendet. Wenn EnableTrailingStop wahr ist, wird der Stop in Viertelschritten der Gewinndistanz verschoben, genau so, wie der MQL-Experte seine Orders geändert hat.
  • Der manuelle Modus (ManualMode) deaktiviert die automatische Auftragserteilung, sammelt jedoch weiterhin Statistiken, was dem ursprünglichen Verhalten der Ruchnik-Flagge entspricht.
  • Das Einskalieren (AllowAddOn) ahmt die Flagge dokupka nach und ermöglicht der Strategie, das Volumen immer dann hinzuzufügen, wenn sich das Muster auf einem neuen Balken wiederholt.

Handelslogik im Detail

  • Indikatorquelle
    • MA-Spread: Berechnet zwei geglättete gleitende Durchschnitte (SMMA 6 und SMMA 24) auf Medianpreisen und verwendet deren Differenz.
    • MACD-Histogramm: Berechnet die Differenz zwischen der MACD-Hauptlinie und der Signallinie (Standardkonfiguration 12/26/9).
  • Normalisierung: NormalizationFactor reproduziert tocnost; Es skaliert die Rohdifferenz, bevor es in eine ganzzahlige Signatur konvertiert wird. Die Umrechnung wird durch 100 * MinPriceStep dividiert, um Pip-basierte Einheiten beizubehalten.
  • Musterspeicher: Das Wörterbuch speichert für jede Signatur die Anzahl der bullischen Übereinstimmungen, die durchschnittliche bullische Distanz, die Anzahl der bärischen Übereinstimmungen und die durchschnittliche bärische Distanz. Die Werte werden mit der gewichteten Formel (current + input * ForgettingFactor) / (1 + ForgettingFactor) aktualisiert.
  • Eintrittsregeln:
    • Long: bullische Erwartung ≥ bärische Erwartung, bullische Übereinstimmungen > MinimumMatches, erwartete bullische Distanz > MinimumTakeProfit.
    • Kurz: bärische Erwartung ≥ bullische Erwartung, bärische Übereinstimmungen > MinimumMatches, erwartete bärische Distanz > MinimumTakeProfit.
  • Risikomanagement: Stop-Loss wird auf einen vollen durchschnittlichen Swing gegenüber der Position eingestellt; Take-Profit nutzt StatisticalTakeRatio dieses Swings. Trailing-Stops bewegen sich, nachdem der Preis ein Viertel der Strecke zurückgelegt hat, genau wie die ursprüngliche Trailing-Routine.

Parameter

Parameter Beschreibung Standard
CandleType Hauptzeitraum für Berechnungen. 30-Minuten-Kerzen
Source Wählen Sie zwischen MA-Spread (FutureMA) und MACD Histogramm (FutureMACD). MaSpread
FastMaLength / SlowMaLength SMMA-Längen bei Source = MaSpread. 6 / 24
MacdFastLength / MacdSlowLength / MacdSignalLength MACD Zeiträume, wenn Source = MacdHistogram. 26.12.9
AnalysisBars Anzahl der Balken, die die Mustersignatur bilden. 8
FractalDepth Anzahl der vergangenen Kerzen, die zur Messung der Ausbruchsentfernungen verwendet werden. 4
MinimumMatches Erforderliche Anzahl gespeicherter Vorkommnisse vor Abschluss eines Handels. 5
MinimumTakeProfit Erwartete Mindestentfernung (in Punkten), um das Signal zu akzeptieren. 30
NormalizationFactor Skalierungsfaktor, der auf die Indikatordifferenz angewendet wird. 10
ForgettingFactor Auf neue Messungen im Musterspeicher angewendetes Gewicht. 1.5
StatisticalTakeRatio Take-Profit-Verhältnis im Verhältnis zum gemessenen Swing. 0,5
EnableTrailingStop Aktiviert eine Viertelschritt-Trailing-Stop-Logik. false
ManualMode Sammeln Sie Statistiken, aber überspringen Sie die Auftragserteilung. false
AllowAddOn Ermöglichen Sie die Skalierung, wenn sich ein Muster wiederholt. true
Volume Auftragsgröße, die für Einträge verwendet wird. 0,1

Praktische Ratschläge

  • Die Strategie basiert auf diskretisierten Hashes. Wählen Sie daher NormalizationFactor und AnalysisBars sorgfältig aus: Zu große Werte erzeugen spärliche Signaturen, während zu kleine Werte unterschiedliche Zustände miteinander vermischen.
  • Wenn Sie den Live-Handel ausführen, sollten Sie erwägen, das Musterwörterbuch nach der Sitzung zu exportieren, wenn Sie Beständigkeit zwischen den Läufen benötigen.
  • Da die MQL-Version Daten pro Symbol/Zeitraum speicherte, wird empfohlen, eine dedizierte Strategieinstanz pro Instrument/Zeitrahmen zu behalten, um eine Kreuzkontamination der Statistiken zu vermeiden.
using System;
using System.Collections.Generic;
using System.Text;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Pattern memory strategy that records normalized MA spread sequences,
/// tracks fractal outcomes, and trades when a recognized pattern has favorable statistics.
/// </summary>
public class FuturePatternMemoryStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastMaLength;
	private readonly StrategyParam<int> _slowMaLength;
	private readonly StrategyParam<int> _patternLength;
	private readonly StrategyParam<int> _minMatches;

	private readonly Queue<int> _patternWindow = new();
	private readonly Dictionary<string, (int buyCount, int sellCount)> _patterns = new();
	private decimal _entryPrice;
	private int _barIndex;
	private int _lastEntryBar;

	public FuturePatternMemoryStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for analysis.", "General");

		_fastMaLength = Param(nameof(FastMaLength), 6)
			.SetDisplay("Fast MA", "Fast EMA period.", "Indicators");

		_slowMaLength = Param(nameof(SlowMaLength), 24)
			.SetDisplay("Slow MA", "Slow EMA period.", "Indicators");

		_patternLength = Param(nameof(PatternLength), 5)
			.SetDisplay("Pattern Length", "Number of bars in pattern signature.", "Pattern");

		_minMatches = Param(nameof(MinMatches), 3)
			.SetDisplay("Min Matches", "Minimum pattern occurrences before trading.", "Pattern");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int FastMaLength
	{
		get => _fastMaLength.Value;
		set => _fastMaLength.Value = value;
	}

	public int SlowMaLength
	{
		get => _slowMaLength.Value;
		set => _slowMaLength.Value = value;
	}

	public int PatternLength
	{
		get => _patternLength.Value;
		set => _patternLength.Value = value;
	}

	public int MinMatches
	{
		get => _minMatches.Value;
		set => _minMatches.Value = value;
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_patternWindow.Clear();
		_patterns.Clear();
		_entryPrice = 0;
		_barIndex = 0;
		_lastEntryBar = 0;
	}

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

		_patternWindow.Clear();
		_patterns.Clear();
		_entryPrice = 0;
		_barIndex = 0;
		_lastEntryBar = 0;

		var fastEma = new ExponentialMovingAverage { Length = FastMaLength };
		var slowEma = new ExponentialMovingAverage { Length = SlowMaLength };

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

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

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

		_barIndex++;
		var close = candle.ClosePrice;

		// Normalize the MA spread into a discrete value
		var spread = fastValue - slowValue;
		var normalized = spread > 0 ? 1 : (spread < 0 ? -1 : 0);

		_patternWindow.Enqueue(normalized);
		while (_patternWindow.Count > PatternLength)
			_patternWindow.Dequeue();

		if (_patternWindow.Count < PatternLength)
			return;

		var key = BuildPatternKey(_patternWindow);

		// Record outcome: if price went up, it's a buy match, otherwise sell
		if (!_patterns.TryGetValue(key, out var stats))
			stats = (0, 0);

		if (close > fastValue)
			stats = (stats.buyCount + 1, stats.sellCount);
		else if (close < fastValue)
			stats = (stats.buyCount, stats.sellCount + 1);

		_patterns[key] = stats;

		// Cooldown: minimum SlowMaLength*4 bars between any trade action
		var cooldownBars = SlowMaLength * 4;
		var cooledDown = _barIndex - _lastEntryBar >= cooldownBars;

		// Position management
		var exitedThisBar = false;
		if (Position > 0)
		{
			if (cooledDown && (spread < 0 || (_entryPrice > 0 && close < _entryPrice * 0.985m)))
			{
				SellMarket();
				_lastEntryBar = _barIndex;
				exitedThisBar = true;
			}
		}
		else if (Position < 0)
		{
			if (cooledDown && (spread > 0 || (_entryPrice > 0 && close > _entryPrice * 1.015m)))
			{
				BuyMarket();
				_lastEntryBar = _barIndex;
				exitedThisBar = true;
			}
		}

		// Entry based on pattern statistics (cooldown between entries)
		if (!exitedThisBar && Position == 0 && cooledDown)
		{
			var total = stats.buyCount + stats.sellCount;
			if (total >= MinMatches)
			{
				if (stats.buyCount > stats.sellCount && spread > 0)
				{
					_entryPrice = close;
					_lastEntryBar = _barIndex;
					BuyMarket();
				}
				else if (stats.sellCount > stats.buyCount && spread < 0)
				{
					_entryPrice = close;
					_lastEntryBar = _barIndex;
					SellMarket();
				}
			}
		}
	}

	private static string BuildPatternKey(IEnumerable<int> values)
	{
		var sb = new StringBuilder();
		var first = true;
		foreach (var v in values)
		{
			if (!first) sb.Append('_');
			sb.Append(v);
			first = false;
		}
		return sb.ToString();
	}
}