Graphentheorie: Tiefensuche (DFS) zur Traversierung von Marktstrukturen im Handel
Inhaltsverzeichnis
Einführung
Um herauszufinden, wie sich der Markt bewegt, wenden wir in diesem Abschnitt den Algorithmus der Tiefensuche (Depth-First Search, DFS) aus der Graphentheorie auf die Struktur der Kursentwicklung im Handel an. Eine große Herausforderung bei dem Versuch, Kursbewegungen algorithmisch zu erfassen, besteht darin, dass Konzepte wie Swing-Hochs, Swing-Tiefs und Marktfortsetzung in der Regel eher qualitativ beschrieben werden als anhand strenger, überprüfbarer Regeln. Um dieses Problem zu lösen, wird die Marktstruktur als Graph formalisiert, wobei jeder bestätigte Swing zu einem Knoten wird und die Übergänge zwischen den Swings als Kanten dargestellt werden, die diese Knoten miteinander verbinden. Da sich im Kursverlauf neue Kerzen bilden und neue Swings bestätigt werden, entwickelt sich dieser Graph dynamisch weiter und liefert eine strukturierte Darstellung der zeitlichen Entwicklung des Marktes.
In diesem Rahmen ermöglicht es DFS dem System, einem strukturellen Zweig tiefgreifend zu folgen – beispielsweise einer Entwicklung von einem Swing-Tief hin zu höheren Hochs und höheren Tiefs –, bevor alternative strukturelle Möglichkeiten in Betracht gezogen werden. Der Algorithmus folgt diesem Pfad so lange, wie die strukturellen Bedingungen gültig bleiben, und misst dessen Stärke anhand objektiver Kriterien wie Pfadtiefe, wichtigen Strukturniveaus und Zieltoleranz. Wenn der Pfad unterbrochen wird – beispielsweise wenn ein kritisches Swing-Niveau invalidiert wird –, führt der Algorithmus ein Backtracking zum vorherigen Knoten durch und untersucht andere mögliche Pfade. Auf diese Weise wird die Abfolge der Kurs-Swings als begehbare Route durch die Marktstruktur betrachtet, wodurch die subjektive Analyse der Kursentwicklung in einen disziplinierten, programmierbaren Prozess umgewandelt wird, der sich für den systematischen und automatisierten Handel eignet.
Überblick und Verständnis des Systems
Begriffliche Zuordnung:| Graphentheorie | Marktäquivalent |
|---|---|
| Knoten | Swing-Hoch/Swing-Tief |
| Kante | Übergang durch Ausbruch oder Rücksetzer |
| Zweig | Aufwärts- oder Abwärtstrendfortsetzung |
| Zielknoten | Liquiditätsziel |
| Tiefe | Stärke der strukturellen Fortsetzung |
| Backtracking | Invalidierung der Struktur |
In der Graphentheorie funktioniert die Tiefensuche (DFS) so, dass zunächst ein Zweig ausgewählt und dieser so tief wie möglich erkundet wird, bevor Alternativen in Betracht gezogen werden. Auf die Marktstruktur angewendet bedeutet dies, dass der Algorithmus einer einzelnen strukturellen Preisentwicklung über aufeinanderfolgende Swing-Punkte folgt und dabei jedes bestätigte Swing-Hoch oder Swing-Tief als Knoten in einem Graphen behandelt. Beispielsweise kann der Kurs bei einem bestätigten Swing-Tief (Knoten A) beginnen, sich zu einem Swing-Hoch (Knoten B) bewegen, zurücklaufen und dabei Knoten C bilden, dann nach oben durchbrechen und Knoten D erreichen und schließlich bis zu Knoten E ansteigen. In DFS-Begriffen folgt das System dieser Abfolge: A -> B -> C -> D -> E, solange die strukturellen Bedingungen weiterhin gelten.
Bevor ein Trade jedoch ausgeführt werden kann, muss der Algorithmus eine minimale strukturelle Tiefe erreichen, d. h. eine Abfolge bestätigter Kurs-Swings, die eine klare Richtungsentwicklung aufzeigen (z. B. höheres Hoch → höheres Tief → Durchbruch der Struktur bei bullischen Bedingungen oder tieferes Tief → tieferes Hoch → Fortsetzung bei bärischen Bedingungen). Erst wenn diese Strukturkette erkannt und validiert wurde, erwägt das System, eine Marktrichtungstendenz festzulegen.

Im Handelskontext bedeutet dies, einem Pfad nur dann ‚tief‘ zu folgen und sich daher erst dann auf eine strukturelle Marktrichtung festzulegen, wenn der Markt diese durch messbare Swing-Zusammenhänge bestätigt hat. Eine bullische Tendenz wird bestätigt, wenn der DFS-Pfad eine gültige Abfolge von höheren Hochs und höheren Tiefs identifiziert, wobei das jüngste höhere Tief als entscheidendes Ungültigkeitsniveau fungiert.
Ebenso bestätigt sich eine bärische Tendenz, wenn der Kursverlauf niedrigere Tiefs und niedrigere Hochs bildet, wobei das jüngste niedrigere Hoch als strukturelle Grenze dient. Sobald diese Tendenz bestätigt ist und die Pfadtiefe die Mindestanforderung erfüllt, prüft das System, ob der Pfad realistischerweise ein aussagekräftiges Ziel erreichen kann, wie beispielsweise einen Liquiditätspool, Angebots- oder Nachfragzonen oder ein vordefiniertes Chance-Risiko-Ziel. Sind diese Bedingungen erfüllt, wird ein Trade in Richtung des aktiven Pfades ausgeführt. Wenn der Kurs stattdessen das strukturelle Ungültigkeitsniveau durchbricht, wertet der Algorithmus dies als Scheitern des Pfads aus, führt ein DFS-Backtracking bis zum letzten gültigen Knoten durch und beginnt, die alternative Marktrichtung zu untersuchen.
Die ersten Schritte
//+------------------------------------------------------------------+ //| DFS.mq5 | //| GIT under Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/johnhlomohang/ | //+------------------------------------------------------------------+ #property copyright "GIT under Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/johnhlomohang/" #property version "1.00" #property strict //+------------------------------------------------------------------+ //| Include trade class | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> CTrade TradeManager; //+------------------------------------------------------------------+ //| Input parameters | //+------------------------------------------------------------------+ input int SwingPeriod = 7; // Bars left/right for pivot input int MinDepth = 7; // Minimum structural depth input double TargetTolerance = 60; // Tolerance for target hit (in points) input bool EnableVisual = true; // Draw nodes and paths input double FixedLotSize = 0.01; // Fixed lot size (if risk management disabled) input bool UseRiskManagement = true; // Use risk-based position sizing input double RiskPercent = 1.0; // Risk percentage per trade input int StopLossPoints = 600; // Stop loss in points input int TakeProfitPoints = 800; // Take profit in points input string TradeComment = "SwingGraph"; // Trade comment input int MagicNumber = 123456; // Expert magic number input bool EnableDailyReset = true; // Reset nodes at start of each day input int MaxNodesToKeep = 100; // Maximum nodes to keep (0 = unlimited) input bool EnableMemoryOptimization = true; // Enable memory optimization //+------------------------------------------------------------------+ //| Structures | //+------------------------------------------------------------------+ struct SwingNode { datetime time; // Bar time double price; // Swing price int type; // 1 = high, -1 = low int index; // Node index bool visited; // For DFS };
Zu Beginn legen wir die Grundlage für den Expert Advisor, indem wir die Handelsfunktionalität importieren und die zentralen Konfigurationsparameter festlegen, die das Verhalten des Systems steuern. Die Bibliothek #include <Trade/Trade.mqh> ermöglicht den Zugriff auf die Handelsfunktionen von MetaTrader, während das Objekt TradeManager der Klasse CTrade zur Ausführung von Kauf- und Verkaufsaufträgen verwendet wird. Über die Eingabeparameter kann der Benutzer wichtige Aspekte der Strategie konfigurieren, wie beispielsweise die Empfindlichkeit der Swing-Erkennung (SwingPeriod), die für die Analyse erforderliche minimale Strukturtiefe (MinDepth) und die Toleranz bei der Erkennung, wann der Kurs ein Ziel erreicht hat (TargetTolerance).
Zusätzliche Eingabewerte steuern die Visualisierung, die Positionsgröße und das Risikomanagement, sodass der EA entweder eine feste Lotgröße verwenden oder die Handelsgröße dynamisch auf der Grundlage eines festgelegten Risikoprozentsatzes berechnen kann. Parameter für Stop-Loss, Take-Profit und die MagicNumber stellen sicher, dass Trades ordnungsgemäß verwaltet und innerhalb des Handelskontos eindeutig identifizierbar sind.
Außerdem führen wir Betriebskontrollen und Einstellungen für die Speicherverwaltung ein, die dazu beitragen, die Stabilität und Effizienz des EA während der kontinuierlichen Ausführung aufrechtzuerhalten. So ermöglicht beispielsweise EnableDailyReset dem System, seine Strukturknoten zu Beginn jedes Handelstags zu löschen und neu aufzubauen, während MaxNodesToKeep und EnableMemoryOptimization die Anhäufung übermäßiger Datenmengen verhindern, die den Algorithmus verlangsamen könnten.
Schließlich definiert die Struktur SwingNode die zentrale Dateneinheit zur Modellierung der Marktstruktur, wobei jeder Knoten ein erkanntes Swing-Hoch oder Swing-Tief darstellt. Jeder Knoten speichert den Zeitpunkt des Swings, das Kursniveau, seinen Typ (Hoch oder Tief), einen eindeutigen Index sowie ein Besucht-Flag, das beim Durchlaufverfahren der Tiefensuche (DFS) verwendet wird.
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ SwingNode nodes[]; int nodeCount = 0; datetime lastBarTime = 0; datetime lastDayDate = 0; // Track last day for reset //--- For DFS state int currentPath[]; int pathDepth = 0; int currentDirection = 0; // 1 bullish, -1 bearish, 0 none double lastHigherLow = 0; double lastLowerHigh = 0; //--- Trade flags bool inTrade = false; ulong tradeTicket = 0; MqlTick currentTick; //--- Rate arrays for timeseries data double high[]; double low[]; datetime time[]; int barsCount = 0; //--- Diagnostic counters int tickCount = 0; datetime lastCleanupTime = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { Print("=========================================="); Print("SwingGraphTrader initialized"); Print("SwingPeriod = ", SwingPeriod); Print("MinDepth = ", MinDepth); Print("TargetTolerance = ", TargetTolerance); Print("EnableDailyReset = ", EnableDailyReset); Print("MaxNodesToKeep = ", MaxNodesToKeep); Print("=========================================="); //--- Initialize arrays with optimal size ArrayResize(nodes, 0, MaxNodesToKeep > 0 ? MaxNodesToKeep + 50 : 1000); nodeCount = 0; //--- Get initial time datetime currentTime[]; if(CopyTime(_Symbol, _Period, 0, 1, currentTime) <= 0) { Print("Failed to copy initial time. Error: ", GetLastError()); return INIT_FAILED; } lastBarTime = currentTime[0]; //--- Set initial day MqlDateTime dt; TimeToStruct(lastBarTime, dt); dt.hour = 0; dt.min = 0; dt.sec = 0; lastDayDate = StructToTime(dt); Print("Initial lastBarTime = ", TimeToString(lastBarTime)); Print("Initial lastDayDate = ", TimeToString(lastDayDate)); TradeManager.SetExpertMagicNumber(MagicNumber); //--- Pre-allocate arrays for better performance ArrayResize(high, 0, 10000); ArrayResize(low, 0, 10000); ArrayResize(time, 0, 10000); return(INIT_SUCCEEDED); }
Anschließend deklarieren wir eine Reihe globaler Variablen, die den internen Zustand des Expert Advisors während seiner Ausführung verwalten. Das Array nodes[] speichert die Sammlung der erkannten Swing-Punkte, die jeweils durch die Struktur SwingNode dargestellt werden, während nodeCount die Anzahl der derzeit vorhandenen Knoten erfasst. Die Variablen lastBarTime und lastDayDate helfen dem EA dabei, die Bildung neuer Kerzen zu erkennen und festzustellen, wann ein täglicher Reset erfolgen sollte. Der Abschnitt mit der Bezeichnung DFS-Zustand enthält Variablen, die die Durchsuchungslogik der Tiefensuche (DFS) unterstützen, die zur Analyse der Marktstruktur verwendet wird.
Beispielsweise erfasst currentPath[] die Abfolge der Swing-Knoten, die den aktiven Strukturpfad bilden, pathDepth verfolgt, wie tief die Durchquerung bereits fortgeschritten ist, und currentDirection gibt an, ob das System derzeit einem bullischen oder bärischen Pfad folgt. Zusätzliche Variablen wie lastHigherLow und lastLowerHigh helfen dabei, die strukturelle Validierung im Verlauf der Kursentwicklung zu überwachen. Außerdem deklarieren wir handelsbezogene Flags wie inTrade und tradeTicket, um zu verfolgen, ob der EA derzeit eine Position hält, sowie Kurs-Arrays (high[], low[], time[]), die zur Speicherung historischer Kerzendaten für die Swing-Erkennung und die Strukturanalyse dienen.
Die Funktion OnInit() ist für die Initialisierung des EAs zuständig, wenn dieser zum ersten Mal an das Chart angehängt oder neu gestartet wird. Zunächst werden Diagnoseinformationen in das Protokoll geschrieben, damit der Benutzer überprüfen kann, ob die Strategieparameter – wie beispielsweise SwingPeriod, MinDepth und die Einstellungen für die Knotenverwaltung – korrekt geladen wurden. Die Funktion bereitet anschließend die zentralen Datenstrukturen vor, indem sie die Größe des Knoten-Arrays anpasst und den Knotenzähler zurücksetzt, um sicherzustellen, dass das System mit einem leeren Strukturgraphen startet. Die Funktion ruft mithilfe von CopyTime() die aktuelle Barzeit ab, um den Ausgangswert für die Erkennung neuer Kerzen festzulegen, und berechnet zudem den täglichen Startzeitstempel, der für die optionale tägliche Rücksetzfunktion verwendet wird.
Nachdem die MagicNumber des Experten über den TradeManager festgelegt wurde, reserviert die Funktion im Voraus Speicherplatz für die Kurs-Arrays (Höchst-, Tiefst- und Zeitwerte), um die Laufzeitleistung bei der Verarbeitung großer Mengen historischer Daten zu verbessern. Sobald alle Initialisierungsaufgaben erfolgreich abgeschlossen sind, gibt die Funktion den Wert INIT_SUCCEEDED zurück, was bedeutet, dass der EA bereit ist, mit der Analyse der Marktdaten und der Ausführung seiner Handelslogik zu beginnen.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(EnableVisual) ObjectsDeleteAll(0, "SwingGraph_"); //--- Free memory ArrayFree(nodes); ArrayFree(high); ArrayFree(low); ArrayFree(time); ArrayFree(currentPath); Print("SwingGraphTrader deinitialized. Reason: ", reason); } //+------------------------------------------------------------------+ //| Reset all data at day change | //+------------------------------------------------------------------+ void CheckAndResetDaily() { if(!EnableDailyReset) return; //--- Get current day MqlDateTime currentDt; TimeToStruct(TimeCurrent(), currentDt); currentDt.hour = 0; currentDt.min = 0; currentDt.sec = 0; datetime currentDay = StructToTime(currentDt); //--- Check if day changed if(currentDay > lastDayDate) { Print("Day changed from ", TimeToString(lastDayDate), " to ", TimeToString(currentDay)); Print("Resetting all nodes and state for new day"); //--- Reset all nodes ArrayResize(nodes, 0, MaxNodesToKeep > 0 ? MaxNodesToKeep + 50 : 1000); nodeCount = 0; //--- Reset DFS state currentDirection = 0; pathDepth = 0; ArrayResize(currentPath, 0); lastHigherLow = 0; lastLowerHigh = 0; //--- Clear all drawings if(EnableVisual) ObjectsDeleteAll(0, "SwingGraph_"); //--- Free timeseries arrays to release memory if(EnableMemoryOptimization) { ArrayFree(high); ArrayFree(low); ArrayFree(time); } lastDayDate = currentDay; Print("Daily reset complete. Memory freed."); } } //+------------------------------------------------------------------+ //| Optimize memory usage | //+------------------------------------------------------------------+ void OptimizeMemory() { if(!EnableMemoryOptimization) return; //--- Periodically clean up old nodes if we exceed max nodes if(MaxNodesToKeep > 0 && nodeCount > MaxNodesToKeep) { Print("Node count (", nodeCount, ") exceeds MaxNodesToKeep (", MaxNodesToKeep, "). Cleaning up old nodes."); int nodesToRemove = nodeCount - MaxNodesToKeep; //--- Shift remaining nodes to beginning of array for(int i = 0; i < MaxNodesToKeep; i++) { nodes[i] = nodes[i + nodesToRemove]; nodes[i].index = i; // Update indices } nodeCount = MaxNodesToKeep; ArrayResize(nodes, nodeCount, MaxNodesToKeep + 50); //--- Reset path if it contains removed nodes bool pathNeedsReset = false; for(int i = 0; i < pathDepth; i++) { if(currentPath[i] < nodesToRemove) { pathNeedsReset = true; break; } //--- Adjust indices currentPath[i] -= nodesToRemove; } if(pathNeedsReset) { currentDirection = 0; pathDepth = 0; ArrayResize(currentPath, 0); Print("Path reset due to node cleanup"); } Print("Node cleanup complete. New node count: ", nodeCount); } //--- Periodically free timeseries arrays if they're too large if(ArraySize(high) > 10000) { ArrayResize(high, 0, 10000); ArrayResize(low, 0, 10000); ArrayResize(time, 0, 10000); Print("Timeseries arrays reset to save memory"); } }
Dieser Abschnitt des Codes regelt das Verhalten des Expert Advisors beim Herunterfahren und beim täglichen Zurücksetzen. Die Funktion OnDeinit() wird ausgeführt, wenn der EA aus dem Chart entfernt wird, die Plattform geschlossen wird oder das Programm neu kompiliert wird. Ihr Zweck besteht darin, die Umgebung sicher zu bereinigen, indem alle von der EA gezeichneten visuellen Objekte entfernt und der für Arrays wie nodes, high, low, time und currentPath zugewiesene Speicher freigegeben wird. Dadurch werden Speicherlecks verhindert und sichergestellt, dass das System die Plattform in einem stabilen Zustand verlässt. Die Funktion gibt außerdem eine Meldung aus, die darauf hinweist, dass der SwingGraphTrader deaktiviert wurde, zusammen mit dem angegebenen Grund, was bei der Fehlersuche und Überwachung hilfreich ist.
Anschließend sorgt die Funktion CheckAndResetDaily() dafür, dass das Handelssystem an jedem neuen Handelstag von vorne beginnt, sofern EnableDailyReset aktiviert ist. Es vergleicht das aktuelle Datum mit dem gespeicherten lastDayDate und setzt, falls ein neuer Tag erkannt wird, die Strukturknoten zurück, löscht den DFS-Durchlaufstatus, löscht Zeichnungen im Chart und gibt optional Zeitreihen-Arrays frei, um Speicherplatz freizugeben.
Der zweite Teil des Codes konzentriert sich auf die Speicheroptimierung und die Systemstabilität bei lang andauernden Vorgängen. Die Funktion OptimizeMemory() sorgt dafür, dass der EA im Laufe der Zeit keine übermäßigen Datenmengen ansammelt, die die Leistung beeinträchtigen oder unnötig Speicherplatz beanspruchen könnten. Wenn die Anzahl der gespeicherten Swing-Knoten den durch MaxNodesToKeep festgelegten Grenzwert überschreitet, entfernt die Funktion die ältesten Knoten und verschiebt die verbleibenden Knoten im Array nach vorne, wobei ihre Indizes aktualisiert werden. Da der DFS-Durchlaufpfad möglicherweise auf Knoten verweist, die entfernt wurden, prüft die Funktion, ob der aktuelle Pfad ungültig geworden ist, und setzt ihn gegebenenfalls zurück, um sicherzustellen, dass der Algorithmus nicht auf veraltete Strukturinformationen zurückgreift.
Zudem überwacht die Funktion die Größe der Arrays für den Kursverlauf (Höchst-, Tiefst- und Zeitwerte) und passt deren Größe an, falls sie zu groß werden, um so eine effiziente Speichernutzung zu gewährleisten. Zusammen ermöglichen diese Mechanismen einen kontinuierlichen Betrieb des EA, wobei dessen interne Datenstrukturen übersichtlich, effizient und stets auf dem neuesten Stand der Marktinformationen bleiben.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Check for daily reset CheckAndResetDaily(); //--- Periodically optimize memory (every 1000 ticks or 1 hour) tickCount++; if(tickCount % 1000 == 0 || TimeCurrent() - lastCleanupTime > 3600) { OptimizeMemory(); lastCleanupTime = TimeCurrent(); } if(inTrade) { if(!PositionSelectByTicket(tradeTicket)) { Print("Trade closed externally. Resetting state."); inTrade = false; tradeTicket = 0; currentDirection = 0; pathDepth = 0; ArrayResize(currentPath,0); } } //--- Get current tick data if(!SymbolInfoTick(_Symbol, currentTick)) { Print("Failed to get current tick data. Error: ", GetLastError()); return; } //--- Check new bar datetime currentTime[]; if(CopyTime(_Symbol, _Period, 0, 1, currentTime) <= 0) { Print("Failed to copy current time. Error: ", GetLastError()); return; } //--- Process only on new bar if(currentTime[0] == lastBarTime) return; lastBarTime = currentTime[0]; Print("New bar detected at ", TimeToString(lastBarTime)); //--- Update timeseries data - reuse arrays to minimize memory allocation barsCount = Bars(_Symbol, _Period); Print("Bars count: ", barsCount); if(barsCount < SwingPeriod * 2 + 5) { Print("Not enough bars: ", barsCount, " < ", SwingPeriod * 2 + 5); return; } //--- Set series as time series ArraySetAsSeries(high,true); ArraySetAsSeries(low,true); ArraySetAsSeries(time,true); //--- Reuse existing arrays instead of creating new ones int copiedHigh = CopyHigh(_Symbol, _Period, 0, barsCount, high); int copiedLow = CopyLow(_Symbol, _Period, 0, barsCount, low); int copiedTime = CopyTime(_Symbol, _Period, 0, barsCount, time); Print("Copied high: ", copiedHigh, ", low: ", copiedLow, ", time: ", copiedTime); if(copiedHigh <= 0 || copiedLow <= 0 || copiedTime <= 0) { Print("Failed to copy price data. Errors: ", GetLastError()); return; } //--- Update swings int oldNodeCount = nodeCount; DetectSwing(); if(nodeCount > oldNodeCount) Print("Added ", nodeCount - oldNodeCount, " new nodes. Total nodes: ", nodeCount); //--- Detect invalidation of current path if(!inTrade && currentDirection != 0) { if(IsPathInvalidated()) { Print("Path invalidated. Backtracking."); BacktrackAndSwitch(); } } //--- Run DFS to find new path if not in trade if(!inTrade) { FindBestPath(); } //--- Check if we should enter a trade if(!inTrade && currentDirection != 0 && pathDepth >= MinDepth) { Print("Trade condition satisfied: direction=", currentDirection, " depth=", pathDepth); if(currentDirection == 1) ExecuteTrade(ORDER_TYPE_BUY, TradeComment + " Bullish"); else ExecuteTrade(ORDER_TYPE_SELL, TradeComment + " Bearish"); } //--- Visualization - only if enabled if(EnableVisual) { DrawSwings(); DrawActivePath(); } } //+------------------------------------------------------------------+ //| Swing Detection | //+------------------------------------------------------------------+ void DetectSwing() { int currentBar = SwingPeriod + 1; // use previous completed bar if(currentBar >= barsCount - SwingPeriod) { Print("currentBar (", currentBar, ") >= barsCount - SwingPeriod (", barsCount - SwingPeriod, ") - returning"); return; } if(IsSwingHigh(currentBar, SwingPeriod)) { Print("Swing high detected at bar ", currentBar, ", price: ", high[currentBar]); AddNode(time[currentBar], high[currentBar], 1); } else if(IsSwingLow(currentBar, SwingPeriod)) { Print("Swing low detected at bar ", currentBar, ", price: ", low[currentBar]); AddNode(time[currentBar], low[currentBar], -1); } } //+------------------------------------------------------------------+ //| IsSwingHigh | //+------------------------------------------------------------------+ bool IsSwingHigh(int bar, int period) { double barHigh = high[bar]; for(int i = bar - period; i <= bar + period; i++) { if(i < 0 || i >= barsCount) continue; if(high[i] > barHigh) return false; } return true; } //+------------------------------------------------------------------+ //| IsSwingLow | //+------------------------------------------------------------------+ bool IsSwingLow(int bar, int period) { double barLow = low[bar]; for(int i = bar - period; i <= bar + period; i++) { if(i < 0 || i >= barsCount) continue; if(low[i] < barLow) return false; } return true; }
Dieser Abschnitt des Codes stellt den Hauptausführungszyklus des Expert Advisors dar, in dem die Handelslogik bei jedem Eintreffen eines neuen Ticks abgearbeitet wird. Die Funktion OnTick() prüft zunächst, ob ein täglicher Reset erforderlich ist, und führt in regelmäßigen Abständen eine Speicheroptimierung durch, um die Effizienz des Systems auch bei längerem Betrieb aufrechtzuerhalten. Außerdem wird überprüft, ob ein bestehender Trade noch aktiv ist, und der interne Status wird zurückgesetzt, falls die Position extern geschlossen wurde. Der EA ruft dann die aktuellsten Tick-Daten ab und stellt sicher, dass die Verarbeitung erst dann erfolgt, wenn sich eine neue Kerze (Bar) bildet, wodurch überflüssige Berechnungen bei jedem Tick vermieden werden. Sobald eine neue Bar erkannt wird, aktualisiert das Programm die Arrays mit den historischen Kurswerten (Höchst-, Tiefst- und Zeitangaben) und stellt so sicher, dass genügend Bars für die Swing-Analyse zur Verfügung stehen.
Die Funktion DetectSwing() durchsucht anschließend die Kursdaten, um neue Swing-Hochs und Swing-Tiefs zu identifizieren. Dabei werden die Hilfsfunktionen IsSwingHigh() und IsSwingLow() verwendet, um zu überprüfen, ob eine Bar höher oder niedriger ist als die umgebenden Bars innerhalb des definierten SwingPeriod-Zeitraums. Werden neue Strukturknoten erkannt, werden diese dem Knotengrafen hinzugefügt, wodurch das System eine sich ständig weiterentwickelnde Darstellung der Marktstruktur aufrechterhalten kann. Nach der Aktualisierung der Knoten prüft der EA, ob der aktuelle DFS-Pfad ungültig geworden ist, und führt ein Backtracking durch; anschließend wendet er die Logik der Tiefensuche an, um den vielversprechendsten Strukturpfad zu finden. Sobald eine gültige Marktrichtungstendenz festgestellt wurde und die strukturelle Tiefe die Mindestanforderung erfüllt, führt der EA einen Trade in der erkannten Richtung aus und zeichnet dabei optional Swing-Punkte und den aktiven Pfad zur Veranschaulichung in das Chart ein.
//+------------------------------------------------------------------+ //| AddNode | //+------------------------------------------------------------------+ void AddNode(datetime nodeTime, double price, int type) { //--- Avoid duplicate at same time if(nodeCount > 0 && nodes[nodeCount-1].time == nodeTime) { Print("Duplicate node at same time, skipping."); return; } //--- Check if we need to resize with extra capacity if(nodeCount >= ArraySize(nodes)) { int newSize = nodeCount + (MaxNodesToKeep > 0 ? 50 : 100); ArrayResize(nodes, newSize, MaxNodesToKeep > 0 ? MaxNodesToKeep + 50 : 1000); } nodes[nodeCount].time = nodeTime; nodes[nodeCount].price = price; nodes[nodeCount].type = type; nodes[nodeCount].index = nodeCount; nodes[nodeCount].visited = false; nodeCount++; Print("Node added: index=", nodeCount-1, ", time=", TimeToString(nodeTime), ", price=", price, ", type=", type); } //+------------------------------------------------------------------+ //| DFS Path Finding | //+------------------------------------------------------------------+ void FindBestPath() { if(nodeCount < 2) { Print("Not enough nodes for DFS: ", nodeCount); return; } //--- Reset visited flags for(int i = 0; i < nodeCount; i++) nodes[i].visited = false; int bestDepth = 0; int bestDirection = 0; int bestPath[]; //--- Try each node as starting point for(int start = 0; start < nodeCount; start++) { //--- Bullish from a low if(nodes[start].type == -1) { int path[]; int depth = DFS_Bullish(start, nodes[start].price, -1e9, path); if(depth > bestDepth) { bestDepth = depth; bestDirection = 1; ArrayResize(bestPath, ArraySize(path)); for(int i = 0; i < ArraySize(path); i++) bestPath[i] = path[i]; } } //--- Bearish from a high if(nodes[start].type == 1) { int path[]; int depth = DFS_Bearish(start, nodes[start].price, 1e9, path); if(depth > bestDepth) { bestDepth = depth; bestDirection = -1; ArrayResize(bestPath, ArraySize(path)); for(int i = 0; i < ArraySize(path); i++) bestPath[i] = path[i]; } } } Print("Best depth found: ", bestDepth, ", direction: ", bestDirection); //--- Set current path if depth meets minimum if(bestDepth >= MinDepth) { ArrayResize(currentPath, bestDepth); for(int i = 0; i < bestDepth; i++) currentPath[i] = bestPath[i]; pathDepth = bestDepth; currentDirection = bestDirection; UpdateKeyLevels(); Print("Path set: depth=", pathDepth, ", direction=", currentDirection, ", lastHigherLow=", lastHigherLow, ", lastLowerHigh=", lastLowerHigh); } if(bestDepth < MinDepth) { currentDirection = 0; pathDepth = 0; ArrayResize(currentPath,0); } } //+------------------------------------------------------------------+ //| Bullish DFS | //+------------------------------------------------------------------+ int DFS_Bullish(int nodeIdx, double lastLow, double lastHigh, int &path[]) { //--- nodeIdx is a low nodes[nodeIdx].visited = true; //--- Initialize path with current node ArrayResize(path, 1); path[0] = nodeIdx; int maxDepth = 1; //--- Look for a high after this low for(int i = nodeIdx+1; i < nodeCount; i++) { if(nodes[i].type == 1 && !nodes[i].visited) { if(lastHigh == -1e9 || nodes[i].price > lastHigh) { //--- Look for a subsequent low after this high for(int j = i+1; j < nodeCount; j++) { if(nodes[j].type == -1 && !nodes[j].visited) { if(nodes[j].price > lastLow) { int subPath[]; int subDepth = DFS_Bullish(j, nodes[j].price, nodes[i].price, subPath); if(subDepth + 2 > maxDepth) { maxDepth = subDepth + 2; ArrayResize(path, maxDepth); path[0] = nodeIdx; // current low path[1] = i; // intermediate high for(int k = 2; k < maxDepth; k++) path[k] = subPath[k-2]; // subsequent nodes } } } } } } } nodes[nodeIdx].visited = false; return maxDepth; }
Die Funktion AddNode() ist dafür zuständig, neu erkannte Swing-Punkte als Knoten im Strukturgraphen des Systems zu speichern. Bevor ein neuer Knoten hinzugefügt wird, prüft die Funktion, ob bereits ein Knoten mit demselben Zeitstempel vorhanden ist, um Duplikate zu vermeiden. Wenn das Knoten-Array seine aktuelle Kapazität erreicht, wird die Größe des Arrays dynamisch angepasst, um zusätzlichen Speicherplatz zu schaffen, wobei aus Effizienzgründen eine zusätzliche Pufferkapazität beibehalten wird. Sobald Speicherplatz verfügbar ist, speichert die Funktion den Zeitpunkt, den Kurs, den Typ (Swing-Hoch oder Swing-Tief), die Indexposition und das Status-Flag für besuchte Knoten, die später vom DFS-Algorithmus während der Durchquerung verwendet werden. Nachdem der Knoten erfolgreich gespeichert wurde, wird die Gesamtanzahl der Knoten erhöht und eine Diagnosemeldung ausgegeben, sodass das System verfolgen kann, wie sich der Graph der Marktstruktur im Laufe der Zeit entwickelt.
Die Funktion FindBestPath() wertet alle gespeicherten Swing-Knoten als mögliche Startpunkte aus und versucht, sowohl bullische als auch bärische Strukturverläufe zu konstruieren. Bei Aufwärtstrends beginnt der Algorithmus an einem Swing-Tief und sucht nach Sequenzen aus höheren Hochs und höheren Tiefs, während Abwärtstrends an Swing-Hochs beginnen und nach niedrigeren Tiefs und niedrigeren Hochs suchen. Diese Durchsuchung wird rekursiv durch die entsprechenden Funktionen, insbesondere DFSBullish() bzw. DFSBearish(), durchgeführt, die den Knotengrafen vorwärts durchläuft und einen Pfad aufbaut, solange die strukturellen Bedingungen gültig bleiben.
Während der Durchquerung verfolgt der Algorithmus den tiefsten gültigen Pfad, der den stärksten strukturellen Verlauf auf dem Markt widerspiegelt. Sobald der beste Pfad ermittelt wurde und dessen Tiefe den Mindestschwellenwert überschreitet, aktualisiert der EA den currentPath, legt die Handelsrichtung fest und erfasst wichtige strukturelle Niveaus wie das letzte höhere Tief oder das letzte niedrigere Hoch. Wenn kein Pfad die erforderliche strukturelle Tiefe erfüllt, löscht das System den aktiven Pfad und wartet darauf, dass sich eine neue Marktstruktur bildet.
//+------------------------------------------------------------------+ //| Bearish DFS | //+------------------------------------------------------------------+ int DFS_Bearish(int nodeIdx, double lastHigh, double lastLow, int &path[]) { //--- nodeIdx is a high nodes[nodeIdx].visited = true; //--- Initialize path with current node ArrayResize(path, 1); path[0] = nodeIdx; int maxDepth = 1; for(int i = nodeIdx+1; i < nodeCount; i++) { if(nodes[i].type == -1 && !nodes[i].visited) { if(lastLow == 1e9 || nodes[i].price < lastLow) { for(int j = i+1; j < nodeCount; j++) { if(nodes[j].type == 1 && !nodes[j].visited) { if(nodes[j].price < lastHigh) { int subPath[]; int subDepth = DFS_Bearish(j, nodes[j].price, nodes[i].price, subPath); if(subDepth + 2 > maxDepth) { maxDepth = subDepth + 2; ArrayResize(path, maxDepth); path[0] = nodeIdx; // current high path[1] = i; // intermediate low for(int k = 2; k < maxDepth; k++) path[k] = subPath[k-2]; // subsequent nodes } } } } } } } nodes[nodeIdx].visited = false; return maxDepth; } //+------------------------------------------------------------------+ //| Invalidation and Backtracking | //+------------------------------------------------------------------+ bool IsPathInvalidated() { if(pathDepth == 0) return false; double price = currentTick.bid; Print("Checking invalidation: price=", price, ", lastHigherLow=", lastHigherLow, ", lastLowerHigh=", lastLowerHigh); if(currentDirection == 1) // bullish { if(price < lastHigherLow - (TargetTolerance * Point())) { Print("Bullish path invalidated: price below lastHigherLow"); return true; } } else if(currentDirection == -1) // bearish { if(price > lastLowerHigh + (TargetTolerance * Point())) { Print("Bearish path invalidated: price above lastLowerHigh"); return true; } } return false; } //+------------------------------------------------------------------+ //| BacktrackAndSwitch | //+------------------------------------------------------------------+ void BacktrackAndSwitch() { currentDirection = 0; pathDepth = 0; ArrayResize(currentPath, 0); lastHigherLow = 0; lastLowerHigh = 0; Print("Backtrack: reset all path state."); } //+------------------------------------------------------------------+ //| UpdateKeyLvls | //+------------------------------------------------------------------+ void UpdateKeyLevels() { if(pathDepth == 0) return; if(currentDirection == 1) { for(int i = pathDepth-1; i >= 0; i--) { if(nodes[currentPath[i]].type == -1) { lastHigherLow = nodes[currentPath[i]].price; break; } } } else if(currentDirection == -1) { for(int i = pathDepth-1; i >= 0; i--) { if(nodes[currentPath[i]].type == 1) { lastLowerHigh = nodes[currentPath[i]].price; break; } } } }
Die Funktion DFS_Bearish() implementiert die Tiefensuche, um eine bärische Marktstruktur innerhalb des Knotengrafen zu identifizieren. Ausgehend von einem Swing-Hoch-Knoten untersucht der Algorithmus nachfolgende Knoten, um eine gültige bärische Sequenz aus niedrigeren Tiefs und niedrigeren Hochs zu bilden. Die Funktion markiert den aktuellen Knoten als besucht, um zu verhindern, dass derselbe Knoten während der Rekursion erneut aufgerufen wird, initialisiert den Pfad mit dem Startknoten und durchläuft dann die Knotenliste in Vorwärtsrichtung. Wird ein gültiges Swing-Tief gefunden, das unter dem vorherigen Tief liegt, sucht der Algorithmus anschließend nach einem darauf folgenden Swing-Hoch, das unter dem vorherigen Hoch liegt, wodurch die bärische Strukturregel beibehalten wird.
Wird eine solche Sequenz gefunden, ruft die Funktion sich selbst rekursiv auf, um die tieferen Verzweigungen der Struktur weiter zu untersuchen. Während dieses Vorgangs verfolgt das Programm den tiefsten gültigen Pfad und aktualisiert das Pfad-Array, sobald eine längere Abwärtssequenz entdeckt wird. Sobald die Erkundung des Zweigs abgeschlossen ist, wird die Markierung als besucht vom Knoten entfernt, damit andere Durchlaufpfade ihn auswerten können, und die Funktion gibt die gefundene maximale Tiefe zurück.
Der zweite Teil des Codes befasst sich mit der Pfadvalidierung, der strukturellen Ungültigkeitsprüfung und dem Backtracking – allesamt entscheidende Komponenten bei der Anwendung der DFS-Logik in einer Handelsumgebung. Die Funktion IsPathInvalidated() prüft, ob der aktuelle Strukturpfad weiterhin gültig ist, indem sie den aktuellen Marktkurs mit wichtigen Strukturniveaus vergleicht, wie beispielsweise dem jüngsten höheren Tiefpunkt bei bullischen Pfaden oder dem niedrigeren Hochpunkt bei bärischen Pfaden. Wenn der Kurs diese Niveaus innerhalb der festgelegten Toleranzgrenze überschreitet, betrachtet der Algorithmus den aktuellen strukturellen Verlauf als ungültig.
In diesem Fall setzt die Funktion BacktrackAndSwitch() den DFS-Zustand zurück, indem sie den aktiven Pfad löscht, die Richtungsvoreinstellung aufhebt und die Schlüsselebenen zurücksetzt, sodass das System nach einem neuen Strukturpfad suchen kann. Die Funktion UpdateKeyLevels() ergänzt diesen Vorgang, indem sie den aktiven Pfad durchsucht, um die jüngste Strukturebene zu ermitteln, die für die aktuelle Trendrichtung relevant ist. Diese Niveaus dienen als Anhaltspunkte, um festzustellen, ob die aktuelle Marktentwicklung den aktiven DFS-Pfad weiterhin stützt oder ob der Algorithmus ein Backtracking durchführt und alternative Marktrichtungen untersuchen muss.
//+------------------------------------------------------------------+ //| Target Identification | //+------------------------------------------------------------------+ bool IsTargetReached(SwingNode &node) { double targetPrice = 0; int barsToCheck = 20; if(barsCount < barsToCheck) { Print("Not enough bars for target check: ", barsCount, " < ", barsToCheck); return false; } if(currentDirection == 1) // bullish target is above { double highArray[]; int copied = CopyHigh(_Symbol, _Period, 1, barsToCheck, highArray); if(copied <= 0) { Print("Failed to copy highs for target. Error: ", GetLastError()); return false; } int highestIdx = ArrayMaximum(highArray); if(highestIdx >= 0) targetPrice = highArray[highestIdx]; } else // bearish { double lowArray[]; int copied = CopyLow(_Symbol, _Period, 1, barsToCheck, lowArray); if(copied <= 0) { Print("Failed to copy lows for target. Error: ", GetLastError()); return false; } int lowestIdx = ArrayMinimum(lowArray); if(lowestIdx >= 0) targetPrice = lowArray[lowestIdx]; } double distance = MathAbs(node.price - targetPrice); Print("Target check: node.price=", node.price, ", targetPrice=", targetPrice, ", distance=", distance, ", tolerance=", TargetTolerance); if(distance < TargetTolerance) return true; return false; } //+------------------------------------------------------------------+ //| Trade Execution | //+------------------------------------------------------------------+ void ExecuteTrade(ENUM_ORDER_TYPE orderType, string comment) { Print("Attempting to execute trade: ", (orderType==ORDER_TYPE_BUY?"BUY":"SELL"), ", comment: ", comment); //--- Calculate position size based on risk double lotSize = 0; if(UseRiskManagement) { lotSize = CalculateLotSize(orderType, StopLossPoints); if(lotSize <= 0) { Print("Failed to calculate position size, using fixed lot size: ", FixedLotSize); lotSize = FixedLotSize; } } else { lotSize = FixedLotSize; } if(lotSize <= 0) { Print("Invalid lot size. Trade not executed."); return; } Print("Lot size: ", lotSize); //--- Get current tick data if(!SymbolInfoTick(_Symbol, currentTick)) { Print("Failed to get current tick data. Error: ", GetLastError()); return; } //--- Calculate stop loss and take profit prices double stopLoss = 0.0, takeProfit = 0.0; double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); if(orderType == ORDER_TYPE_BUY) { stopLoss = NormalizeDouble(currentTick.bid - (StopLossPoints * point), digits); takeProfit = NormalizeDouble(currentTick.ask + (TakeProfitPoints * point), digits); } else if(orderType == ORDER_TYPE_SELL) { stopLoss = NormalizeDouble(currentTick.ask + (StopLossPoints * point), digits); takeProfit = NormalizeDouble(currentTick.bid - (TakeProfitPoints * point), digits); } Print("SL: ", stopLoss, ", TP: ", takeProfit); //--- Validate stop levels before sending the trade if(!ValidateStopLevels(orderType, currentTick.ask, currentTick.bid, stopLoss, takeProfit)) { Print("Invalid stop levels. Trade not executed."); return; } if(PositionSelect(_Symbol)) return; //--- Execute trade bool requestSent = false; if(orderType == ORDER_TYPE_BUY) { requestSent = TradeManager.Buy(lotSize, _Symbol, 0, stopLoss, takeProfit, comment); } else if(orderType == ORDER_TYPE_SELL) { requestSent = TradeManager.Sell(lotSize, _Symbol, 0, stopLoss, takeProfit, comment); } //--- Check if the request was sent successfully if(requestSent) { //--- Check the server's return code from the trade operation uint result = TradeManager.ResultRetcode(); Print("Trade request sent. Retcode: ", result, " - ", TradeManager.ResultRetcodeDescription()); if(result == TRADE_RETCODE_DONE || result == TRADE_RETCODE_DONE_PARTIAL) { Print("Trade executed successfully. Ticket: ", TradeManager.ResultOrder()); inTrade = true; tradeTicket = TradeManager.ResultOrder(); } else if(result == TRADE_RETCODE_REQUOTE || result == TRADE_RETCODE_TIMEOUT || result == TRADE_RETCODE_PRICE_CHANGED) { Print("Trade failed due to price change. Retcode: ", TradeManager.ResultRetcodeDescription()); } else { Print("Trade execution failed. Retcode: ", TradeManager.ResultRetcodeDescription()); } } else { Print("Failed to send trade request. Last Error: ", GetLastError()); } }
Die Funktion IsTargetReached() dient dazu, anhand der jüngsten Kurshochs und -tiefs festzustellen, ob ein Strukturknoten ein potenzielles Marktziel erreicht hat. Zunächst stellt die Funktion sicher, dass genügend Bars für die Analyse zur Verfügung stehen, und ermittelt dann das Zielniveau in Abhängigkeit von der aktuellen Marktrichtung. Bei einem bullischen Szenario ermittelt der Algorithmus das höchste Hoch aus den zuletzt gebildeten Bars, während er bei einem bärischen Szenario das tiefste Tief ermittelt. Diese Werte stellen nahegelegene Liquiditäts- oder Ausdehnungsziele dar, die der Kurs möglicherweise anzustreben versucht. Nach der Ermittlung des Zielniveaus berechnet die Funktion die Differenz zwischen dem Kurs des Knotens und dem ermittelten Ziel. Liegt dieser Abstand innerhalb der definierten TargetTolerance, gibt die Funktion true zurück, was bedeutet, dass die strukturelle Bewegung ihren Zielbereich tatsächlich erreicht hat.
Die Funktion ExecuteTrade() übernimmt den gesamten Prozess der Handelsausführung, sobald die strukturellen Bedingungen des DFS eine gültige Gelegenheit signalisieren. Zunächst wird die angemessene Positionsgröße ermittelt, entweder durch eine risikobasierte Berechnung oder – falls das Risikomanagement deaktiviert ist – durch Verwendung einer festen Lotgröße. Nach dem Abruf der aktuellen Markt-Tick-Daten berechnet die Funktion die Stop-Loss- und Take-Profit-Niveaus auf der Grundlage der konfigurierten Punktabstände und normiert diese entsprechend der Genauigkeit des Symbols.
Vor dem Absenden der Order überprüft das System die Stop-Levels, um sicherzustellen, dass sie den Vorgaben des Brokers entsprechen, und stellt zudem sicher, dass für das Symbol derzeit keine Position offen ist. Sind alle Bedingungen erfüllt, sendet die Funktion über die Klasse CTrade einen Kauf- oder Verkaufsauftrag und wertet anschließend den Antwortcode des Servers aus. Wird der Trade erfolgreich ausgeführt, erfasst der EA die Ticketnummer und aktualisiert den Handelsstatus, sodass das System die aktive Position verfolgen und verwalten kann.
//+------------------------------------------------------------------+ //| Visualization | //+------------------------------------------------------------------+ void DrawSwings() { //--- Don't draw if disabled if(!EnableVisual) return; string prefix = "SwingGraph_"; ObjectsDeleteAll(0, prefix); for(int i = 0; i < nodeCount; i++) { string name = prefix + "Node_" + IntegerToString(i); color clr = (nodes[i].type == 1) ? clrBlue : clrRed; if(!ObjectCreate(0, name, OBJ_ARROW, 0, nodes[i].time, nodes[i].price)) Print("Failed to create object ", name, ". Error: ", GetLastError()); else { ObjectSetInteger(0, name, OBJPROP_ARROWCODE, (nodes[i].type == 1) ? 241 : 242); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_WIDTH, 2); } } //--- Draw edges connecting consecutive nodes for(int i = 1; i < nodeCount; i++) { string name = prefix + "Edge_" + IntegerToString(i-1) + "_" + IntegerToString(i); if(!ObjectCreate(0, name, OBJ_TREND, 0, nodes[i-1].time, nodes[i-1].price, nodes[i].time, nodes[i].price)) Print("Failed to create edge ", name, ". Error: ", GetLastError()); else { ObjectSetInteger(0, name, OBJPROP_COLOR, clrGray); ObjectSetInteger(0, name, OBJPROP_WIDTH, 1); ObjectSetInteger(0, name, OBJPROP_RAY, false); } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void DrawActivePath() { //--- Don't draw if disabled if(!EnableVisual) return; if(pathDepth < 2) return; string prefix = "SwingGraph_Path_"; for(int i = 1; i < pathDepth; i++) { string name = prefix + IntegerToString(i-1) + "_" + IntegerToString(i); int from = currentPath[i-1]; int to = currentPath[i]; if(from >= 0 && from < nodeCount && to >= 0 && to < nodeCount) { if(!ObjectCreate(0, name, OBJ_TREND, 0, nodes[from].time, nodes[from].price, nodes[to].time, nodes[to].price)) Print("Failed to create path segment ", name, ". Error: ", GetLastError()); else { ObjectSetInteger(0, name, OBJPROP_COLOR, (currentDirection == 1) ? clrGreen : clrOrange); ObjectSetInteger(0, name, OBJPROP_WIDTH, 3); ObjectSetInteger(0, name, OBJPROP_RAY, false); } } } } //+------------------------------------------------------------------+
Die Funktionen DrawSwings() und DrawActivePath() dienen dazu, die Graphstruktur und den aktiven DFS-Pfad direkt im Handelschart darzustellen. Die Funktion DrawSwings() löscht zunächst alle zuvor gezeichneten Objekte mit dem Präfix SwingGraph_, um Doppelzeichnungen zu vermeiden, und durchläuft anschließend alle gespeicherten Swing-Knoten, um diese als Pfeilobjekte an ihren jeweiligen Zeit- und Kurskoordinaten darzustellen. Swing-Hochs werden durch blaue Pfeile angezeigt, während Swing-Tiefs rot dargestellt werden, wodurch sich strukturelle Wendepunkte leicht erkennen lassen. Zudem zeichnet die Funktion graue Trendlinien zwischen aufeinanderfolgenden Knoten und erstellt so eine visuelle Darstellung der erkannten Marktstruktur.
Die Funktion DrawActivePath() hebt den aktuellen, durch DFS ermittelten Strukturpfad hervor, dem der Algorithmus folgt. Wenn die Visualisierung aktiviert ist und der Pfad mindestens zwei Knoten enthält, durchläuft die Funktion das Array currentPath und zeichnet dickere Trendlinien zwischen jedem Knotenpaar im Pfad. Diese Pfadsegmente sind bei bullischen Strukturen grün und bei bärischen Strukturen orange eingefärbt, sodass Händler klar erkennen können, welche Richtungsfolge der Algorithmus derzeit als die wahrscheinlichste Marktentwicklung ansieht. Gemeinsam wandeln diese Visualisierungstools die zugrunde liegende graphbasierte Analyse in eine intuitive Chartdarstellung um, die den Nutzern hilft, zu verstehen, wie der DFS-Algorithmus die Marktstruktur in Echtzeit interpretiert.
Backtest-Ergebnisse
Das vorrangige Ziel des Systems bestand darin, die subjektive Struktur der Kursentwicklung in eine deterministische, regelbasierte Abfolge von Kurs-Swings umzuwandeln, bei der Trades erst dann ausgeführt werden, wenn ein gültiger struktureller Verlauf eine ausreichende Tiefe erreicht und die Marktrichtungstendenz bestätigt. Das Backtesting wurde für das Währungspaar XAUUSD im M15-Zeitrahmen über einen Testzeitraum von zwei Monaten (1. Dezember 2025 bis 30. Januar 2026) mit den Standardeinstellungen durchgeführt:


Die Ergebnisse deuten darauf hin, dass dieses Ziel weitgehend erreicht wurde, was darauf hindeutet, dass der DFS-Ansatz Trades erfolgreich filtert, indem er vor dem Einstieg bestätigte strukturelle Sequenzen verlangt.
Schlussfolgerung
Zusammenfassend lässt sich sagen, dass wir das Konzept der Tiefensuche aus der Graphentheorie auf den Handel übertragen haben, indem wir die Marktstruktur in ein graphbasiertes Modell miteinander verbundener Swing-Punkte umgewandelt haben, bei dem jedes Swing-Hoch bzw. jedes Swing-Tief einen Knoten darstellt und die Beziehungen zwischen ihnen Kanten bilden. In diesem Rahmen untersucht der Algorithmus mögliche Kursverläufe, indem er zunächst einem strukturellen Zweig – beispielsweise einer Abfolge von höheren Hochs und höheren Tiefs – tief folgt, bevor er alternative Richtungen in Betracht zieht. Um dies in die Praxis umzusetzen, haben wir einen funktionsfähigen MQL5-Expert Advisor implementiert, der eine Swing-Erkennung, die Speicherung von Knoten über die SwingNode-Struktur, eine DFS-Durchquerung für bullische und bärische Pfade sowie eine Pfadauswahl auf Basis der strukturellen Tiefe unter Verwendung des MinDepth-Kriteriums umfasst. Das System ermittelt zudem Schlüsselwerte für die Ungültigkeitsstufen (lastHigherLow und lastLowerHigh) mit einer konfigurierbaren Toleranz (TargetTolerance), führt Trades mit definierten SL/TP-Werten und optionaler risikobasierter Positionsgröße aus und visualisiert die resultierenden Knoten sowie den aktiven Strukturpfad direkt im Chart.
Zusammenfassend lässt sich sagen, dass die Integration von DFS in den Handel die ungefilterten Kursbewegungen in einen strukturierten und navigierbaren Suchraum verwandelt, den Händler programmgesteuert analysieren und testen können. Mit diesem Framework können Sie die Marktstruktur als Graph modellieren, mögliche Bewegungspfade zwischen Swing-Niveaus nachverfolgen und objektiv feststellen, wann sich eine Tendenz bestätigt oder widerlegt. Die daraus resultierende EA-Vorlage bietet einen reproduzierbaren und testbaren Arbeitsablauf, mit dem Sie das System kompilieren, im Strategietester ausführen und mit Parametern wie SwingPeriod, MinDepth und den Risikoeinstellungen experimentieren können, während Sie beobachten, wie sich pathDepth, currentDirection und die strukturellen Ebenen entwickeln. Durch die Formalisierung der Swing-Logik in deterministische Regeln wandelt dieser Ansatz die subjektive Chartinterpretation in ein überprüfbares Suchproblem um und ermöglicht so systematisches Experimentieren, die Verfeinerung der Handelslogik sowie die Entwicklung fortgeschrittener algorithmischer Strategien.
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/21590
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
MetaTrader 5 – Leitfaden für maschinelles Lernen (Teil 8): Bayes’sche Hyperparameter-Optimierung mit Purged Cross-Validation und Trial Pruning
Paketbasierter Ansatz mit KnitPkg für die MQL5-Entwicklung
Eine alternative Log-datei mit der Verwendung der HTML und CSS
Entwicklung eines dynamischen Multi-Pair-EA (Teil 7): Paarübergreifende Korrelationsabbildung für die Handelsfilterung in Echtzeit
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.
Das sieht nicht nach einem Graphen aus – sondern lediglich nach einer Suche nach zwei Arten von Extremwerten in einem chronologisch angeordneten Array. Das erinnert mich an einen Zickzack, der mit verschachtelten Schleifen untersucht wird.
Es wäre graforientierter, auf der Historie aufzubauen und statistisch aussagekräftige Gewichte für Kanten zwischen den wahrscheinlichsten Schwingungssequenzen zu ermitteln, wenn die Wahrscheinlichkeit zukünftiger Verläufe berechnet wird.