English Русский 日本語
preview
Graphentheorie: Dijkstras Algorithmus angewandt im Handel

Graphentheorie: Dijkstras Algorithmus angewandt im Handel

MetaTrader 5Beispiele |
139 0
Hlomohang John Borotho
Hlomohang John Borotho


Einführung

In diesem Artikel werden wir die Implementierung des Dijkstra-Algorithmus untersuchen, ein grundlegendes Konzept der Graphentheorie, das für seine Effizienz bei der Lösung von Problemen mit kürzesten Wegen bekannt ist. Wir werden diesen Algorithmus, der traditionell in der Routing- und Netzwerkoptimierung eingesetzt wird, auf die Finanzmärkte übertragen, indem wir die Preisbewegungen als gewichteten Graphen modellieren. Dabei stellen die Knoten Preisniveaus oder Zeitintervalle dar, während die Kanten die Kosten (oder die Wahrscheinlichkeit) des Übergangs zwischen ihnen widerspiegeln.

Unser Ziel ist es, die Dijkstra-Methode zur Vorhersage des nächsten wahrscheinlichen Preisdatenfeldes zu nutzen und so den „kürzesten Weg“ zu bestimmen, den der Preis von seiner aktuellen Position bis zu einem zukünftigen Wert nehmen könnte. Indem wir die Marktdynamik als einen Graphen behandeln, versuchen wir, die wahrscheinlichste Trajektorie zu identifizieren und die Handelsentscheidungen auf der Grundlage minimaler Widerstände oder Kosten zu optimieren.

Die Graphentheorie bietet einen leistungsfähigen Rahmen für die Analyse komplexer Marktstrukturen, und der Dijkstra-Algorithmus bietet einen systematischen Weg, sich in ihnen zurechtzufinden. Indem wir Preisbewegungen als Kanten mit Gewichten wie der Volatilität interpretieren, können wir den optimalen Pfad berechnen, der das Risiko minimiert oder die Effizienz maximiert.

Das prognostizierte Preisfeld stellt im Wesentlichen die kürzeste Entfernung zwischen dem aktuellen Preis und den zukünftigen Niveaus dar und bietet Händlern eine datengestützte Methode zur Vorwegnahme von Trends. Dieser Ansatz schlägt eine Brücke zwischen algorithmischem Handel und Computermathematik und zeigt, wie klassische Graphenalgorithmen verborgene Chancen in finanziellen Zeitreihendaten aufdecken können.


Dijkstras Grundlagen

Terminologie Unsere Interpretation  
Graph: Eine Sammlung von Knoten und Kanten. Graph: Die Struktur des Charts besteht aus hohen und tiefen Umkehrpunkten. Jeder Umkehrpunkt wird zu einem Knotenpunkt und jeder Preispfad dazwischen zu einer Kante.
Weight: Die Kosten für die Reise von einem Knotenpunkt zu einem anderen. Weight: Die Kosten (oder der Aufwand), die es braucht, damit sich der Preis zwischen zwei Umkehrpunkten bewegt. Dies könnte der absolute Preisabstand sein.
Source Node: Der Startpunkt des Algorithmus. Source Node: Der letzte gültige Umkehrpunkt (der letzte Hoch-/Tiefpunkt, den der Kurs noch nicht durchbrochen hat). Das ist unser Ausgangspunkt, von dem aus wir die kürzesten Wege berechnen können.
Visited Set: Knoten, deren Verarbeitung abgeschlossen ist. Visited Set: Alle Umkehrpunkte, die der Algorithmus bereits bewertet hat und nicht noch einmal überprüfen wird. In Bezug auf den Handel handelt es sich um Umkehrpunkte, die der Kurs bereits durchbrochen hat oder auf die er bereits zugegangen ist.
Distance Table: Verfolgt die kürzeste Entfernung zu jedem Knoten. Distance Table: Eine Zuordnung von jedem Knotens zu seinem kürzesten „cost-to-reach“-Wert vom Ursprungsknoten. Beim Handel gibt sie an, wie billig (oder leicht) sich der Preis vom aktuellen Punkt zu einem anderen Umkehrpunkt bewegen kann.

Schritt-für-Schritt-Verfahren:

1. Initialisierung:

  • Setze den Abstand zum Quellknoten auf 0.
  • Setze den Abstand zu allen anderen Knoten auf unendlich.
  • Erstelle eine Prioritäts-Warteschlange (oder Min-Haufen), um immer den Knoten mit der kleinsten bekannten Entfernung auszuwählen.

2. Besuche den nächstgelegenen nicht besuchten Knoten:

  • Beginne mit dem Quellknoten.
  • Berechne für jeden Nachbarn:
    new_distance = distance_to_current + edge_weight
    Wenn diese „neue Entfernung“ kleiner ist als die zuvor bekannte Entfernung, wird sie aktualisiert.

3. Markiere den aktuellen Knoten als besucht:

  • Nach der Bearbeitung werden wir sie nicht wieder aufgreifen.

4. Wiederholungen:

  • Fahre mit dem Besuch des nächstgelegenen, nicht besuchten Knotens fort.
  • Wiederhole diesen Vorgang, bis alle Knoten besucht sind oder der kürzeste Weg gefunden wurde.
  • while unvisited nodes remain:
        select node with the smallest tentative distance
        for each neighbor:
            if new path to neighbor is shorter:
                update the shortest distance
        mark current node as visited


Erste Schritte

//+------------------------------------------------------------------+
//|                                               Dijkstars Algo.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict
#include <Trade/Trade.mqh>
CTrade trade;

Wir beginnen mit der Einbindung der Datei „trade.mqh“, die uns über die Klasse „CTrade“ den Zugang zu den eingebauten MQL5-Handelsfunktionen ermöglicht. Diese Klasse bietet Methoden zum programmgesteuerten Platzieren, Ändern und Schließen von Handelsgeschäften. Nach dem Einbinden der Datei erstellen wir eine Instanz von „CTrade“ mit dem Namen „trade“, die wir im gesamten Expert Advisor verwenden werden, um Handelsbefehle wie „Buy()“, „Sell()“ und „PositionOpen()“ zu senden. Diese Einrichtung ist für die Automatisierung der Auftragsausführung im EA unerlässlich.

// Input Parameters
input int    TakeProfit   = 1000;     
input int   StopLoss = 385;
input double  In_Lot = 0.01;
input int    LeftBars    = 3;
input int    RightBars   = 3;
input int    MaxSwings   = 50;
input double Lots        = 0.1;
input double PointBuffer = 10;
input int    Slippage    = 5;
ENUM_TIMEFRAMES TimeFrame;

In diesem Abschnitt werden die Eingabeparameter für den Expert Advisor definiert, sodass der Händler die wichtigsten Einstellungen direkt über die EA-Oberfläche anpassen kann. „TakeProfit“ und „StopLoss“ legen das Ziel und das Risiko in Punkten fest, während „InLot“ und „Lots“ die Handelsgröße definieren. „LeftBars“ und „RighBars“ werden verwendet, um Hochs und Tiefs zu erkennen, indem die Balken mit ihren Nachbarn verglichen werden. „MaxSwings“ begrenzt, wie viele Umkehrpunkte verfolgt werden, und „PointBuffer“ fügt aus Sicherheitsgründen einen zusätzlichen Abstand zu SL/TP hinzu. „Slippage“ legt die maximal zulässige Preisabweichung während der Orderausführung fest, und „TimeFrame“ spezifiziert die Chartperiode, die der EA analysiert.

// Node Structure
struct SwingPoint {
   int index;
   datetime time;
   double price;
   bool isHigh;
   bool visited;
   double distance;
   bool   used;
   int previous;
};

Diese Struktur definiert den Bauplan für einen SwingPoint, der einen Knoten im Dijkstra-basierten Handelssystem darstellt. Jeder Umkehrpunkt enthält wichtige Informationen:

  • Index ist die Nummer des Balkens, in dem der Umkehrpunkt gefunden wurde.
  • Time ist der genaue Zeitstempel dieses Balkens.
  • Price ist der Höchst- oder Tiefstwert bei diesem Swing.
  • IsHigh gibt an, ob es sich um ein hohen (true) oder tiefen (false) Umkehrpunkt handelt.
  • Visited dient zur Feststellung, welche Knoten vom Algorithmus bearbeitet wurden.
  • Distance speichert die berechneten Kosten vom Quellknoten in Dijkstras Pfadfindung.
  • Used sind Flags, wenn dieser Umkehrpunkt bereits für Handelsentscheidungen verwendet wurde.
  • Previous merkt sich den vorherigen Knoten in der Kette des kürzesten Weges.
SwingPoint swingPoints[];

//+------------------------------------------------------------------+
//| OnInit                                                           |
//+------------------------------------------------------------------+
int OnInit() {
   Print("Dijkstra Swing EA initialized");
   return INIT_SUCCEEDED;
}

Hier wird ein dynamisches Array „swingPoints[]“ deklariert, um alle erkannten hohen und tiefen Umkehrpunkte (als Strukturen „SwingPoint“) im Chart zu speichern. Dieses Array wird im gesamten EA zur Darstellung von Knoten im Preis-Aktions-Chart verwendet und aufgefüllt. In der Funktion „OnInit()“ druckt der EA einfach eine Nachricht auf dem Terminal aus, die bestätigt, dass der „Dijkstra Swing EA“ erfolgreich initialisiert wurde und gibt „INIT_SUCCEEDED“ zurück, um einen ordnungsgemäßen Start zu signalisieren.

//+------------------------------------------------------------------+
//| Detect swing highs and lows                                      |
//+------------------------------------------------------------------+
void DetectSwings(int left, int right) {
   ArrayResize(swingPoints, 0);

   int totalBars = Bars(_Symbol, PERIOD_CURRENT) - right;
   for (int i = left; i < totalBars; i++) {
      bool isHigh = true, isLow = true;
      double high = High(i), low = Low(i);

      for (int j = 1; j <= left; j++) {
         if (High(i - j) >= high) isHigh = false;
         if (Low(i - j) <= low) isLow = false;
      }
      for (int j = 1; j <= right; j++) {
         if (High(i + j) >= high) isHigh = false;
         if (Low(i + j) <= low) isLow = false;
      }

      if (isHigh || isLow) {
         int idx = ArraySize(swingPoints);
         ArrayResize(swingPoints, idx + 1);
         swingPoints[idx].index = i;
         swingPoints[idx].time = Time(i);
         swingPoints[idx].price = isHigh ? high : low;
         swingPoints[idx].isHigh = isHigh;
         swingPoints[idx].visited = false;
         swingPoints[idx].distance = DBL_MAX;
         swingPoints[idx].previous = -1;

         if (idx >= MaxSwings) break;
      }
   }
}

Diese Funktion, „DetectSwings()“, identifiziert hohe und tiefe Umkehrpunkte auf dem Preisdiagramm, indem sie jede Kerze mit ihren benachbarten Balken vergleicht. Zunächst wird das vorhandene Array „SwingPoints“ mit Hilfe von „ArrayResize“ geleert, sodass bei jedem Aufruf eine neue Erkennung erfolgt. Es wird jeder Balken auf dem Chart durchlaufen, beginnend mit dem Index „left“ bis „totalBars - right“, und prüft, ob der aktuelle Balken (i) als hoher und tiefer Umkehrpunkt eingestuft werden kann.

Um festzustellen, ob es sich bei einem Balken um ein hohen Umkehrpunkt handelt, wird geprüft, ob sein Hoch höher ist als die Hochs der vorhergehenden „linken“ und der nächsten „rechten“ Balken. In ähnlicher Weise wird ein tiefer Umkehrpunkt bestätigt, wenn sein Tief sowohl unter dem vorherigen als auch unter dem nächsten benachbarten Tief liegt. Wenn eine der beiden Bedingungen erfüllt ist, wird der Balken als gültiger Umkehrpunkt betrachtet. Dieser lokale Vergleich stellt sicher, dass nur signifikante Preisspitzen und -täler als Umkehrpunkt-Nodes erfasst werden.

Wenn ein Umkehrpunkt erkannt wird, wird er im Array „SwingPoints[]“ mit allen relevanten Details gespeichert: sein Index, die Zeit, der Preis, ob es sich um ein Hoch oder ein Tief handelt, und Standardwerte für die Pfadfindung (z.B. „visited“, „distance“ und „previous“). Diese Struktur unterstützt die spätere Analyse mit Hilfe des Dijkstra-Algorithmus, um die Pfadwahrscheinlichkeiten zwischen den Umkehrpunkten zu bewerten. Die Schleife bricht vorzeitig ab, wenn die Anzahl der erkannten Umkehrpunkte „MaxSwings“ erreicht, um übermäßigen Speicherverbrauch oder Leistungsprobleme zu vermeiden.

//+------------------------------------------------------------------+
//| Apply Dijkstra's algorithm                                       |
//+------------------------------------------------------------------+
void ApplyDijkstra() {
   if (ArraySize(swingPoints) == 0) return;

   swingPoints[0].distance = 0;

   for (int i = 0; i < ArraySize(swingPoints); i++) {
      int u = -1;
      double minDist = DBL_MAX;

      for (int j = 0; j < ArraySize(swingPoints); j++) {
         if (!swingPoints[j].visited && swingPoints[j].distance < minDist) {
            minDist = swingPoints[j].distance;
            u = j;
         }
      }

      if (u == -1) break;

      swingPoints[u].visited = true;

      for (int v = 0; v < ArraySize(swingPoints); v++) {
         if (!swingPoints[v].visited) {
            double cost = MathAbs(swingPoints[u].price - swingPoints[v].price);
            if (swingPoints[u].distance + cost < swingPoints[v].distance) {
               swingPoints[v].distance = swingPoints[u].distance + cost;
               swingPoints[v].previous = u;
            }
         }
      }
   }
}

In dieser Funktion implementieren wir den Dijkstra-Algorithmus, um den kürzesten Weg vom ersten Umkehrpunkt zu allen anderen Umkehrpunkten zu berechnen, wobei wir die Preisbewegung zwischen den hohen und tiefen Umkehrpunkt als gewichteten Graphen behandeln. Zunächst wird geprüft, ob es Umkehrpunkte gibt; ist das Feld leer, wird die Funktion sofort beendet. Es setzt dann „distance“ des ersten Knotens (des Startpunkts) auf „0“, was den Ausgangsknoten im Pfadfindungsprozess kennzeichnet.

Der Algorithmus tritt in eine Schleife ein, in der er bei jeder Iteration den nicht besuchten Umkehrpunkt mit dem kleinsten bekannten „distance“ auswählt. Dieser Knoten „u“ wird als besucht markiert, und der Algorithmus wertet alle seine nicht besuchten Nachbarn aus. Für jeden dieser Nachbarn „v“ berechnet es die „cost“ oder das Gewicht, um von Knoten „u“ zu „v“ zu gelangen, basierend auf dem absoluten Preisunterschied zwischen ihnen. Wenn die kumulativen Kosten, um „v“ über „u“ zu erreichen, geringer sind als die aktuell aufgezeichnete Entfernung, wird die „Entfernung“ von „v“ aktualisiert und „u“ als sein „vorheriger“ Knoten aufgezeichnet.

Dieser Vorgang wird so lange fortgesetzt, bis alle erreichbaren Drehpunkte besucht wurden oder keine unbesuchten Knoten mehr erreichbar sind. Am Ende der Funktion enthält jeder Umkehrpunkt die kürzesten kumulativen Kosten von der Quelle und einen Zeiger auf den vorherigen Knoten entlang dieses optimalen Pfades. Anhand dieser Informationen kann der EA den effizientesten Weg durch die jüngste Marktstruktur nachverfolgen und bestimmen, welche Umkehrpunkte der Kurs als Nächstes wahrscheinlich wieder aufsuchen wird, was die Grundlage für die Erzeugung intelligenter Handelssignale bildet.

//+------------------------------------------------------------------+
//| Visualize Swing Points and Connections                           |
//+------------------------------------------------------------------+
void VisualizeSwings() {
   for (int i = 0; i < ArraySize(swingPoints); i++) {
      string objName = "Swing_" + IntegerToString(i);
      ObjectDelete(0, objName);

      ObjectCreate(0, objName, OBJ_ARROW, 0, swingPoints[i].time, swingPoints[i].price);
      ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, swingPoints[i].isHigh ? 233 : 234);
      ObjectSetInteger(0, objName, OBJPROP_COLOR, swingPoints[i].isHigh ? clrRed : clrBlue);
   }

   for (int i = 1; i < ArraySize(swingPoints); i++) {
      int prev = swingPoints[i].previous;
      if (prev != -1) {
         string lineName = "Line_" + IntegerToString(i);
         ObjectDelete(0, lineName);

         ObjectCreate(0, lineName, OBJ_TREND, 0,
                      swingPoints[prev].time, swingPoints[prev].price,
                      swingPoints[i].time, swingPoints[i].price);
         ObjectSetInteger(0, lineName, OBJPROP_COLOR, clrGray);
         ObjectSetInteger(0, lineName, OBJPROP_WIDTH, 1);
      }
   }
}

Die Funktion „VisualizeSwings()“ ist dafür verantwortlich, die erkannten Umkehrpunkte und die Verbindungen zwischen ihnen direkt auf dem Chart zu zeichnen, was den Händlern hilft, die vom EA verwendete Struktur und Logik visuell zu bestätigen. In der ersten „for“-Schleife wird das Array „swingPoints[]“ durchlaufen und für jeden Umkehrpunkt ein Pfeilobjekt erstellt. Bevor ein neues Objekt erstellt wird, werden alle vorhandenen Objekte mit demselben Namen gelöscht, um Unordnung zu vermeiden. Jeder Pfeil ist mit einem spezifischen Symbol versehen: rot für Hochs (Pfeilcode 233) und blau für Tiefs (Pfeilcode 234), sodass sie visuell unterscheidbar sind.

In der zweiten „for“-Schleife zieht die Funktion Linien zwischen jedem Schwingungspunkt und dem entsprechenden „vorhergehenden“ Knoten (wie durch den Dijkstra-Algorithmus bestimmt). Diese Linien stellen die Verbindungen mit dem kürzesten Weg dar, die zur Bewertung potenzieller Handelswege verwendet werden. Auch hier wird jedes vorhandene Linienobjekt mit demselben Namen gelöscht, bevor eine neue Linie gezeichnet wird. Die Linien werden mit „OBJ_TREND“ erstellt und in Grau mit einer Standardbreite gezeichnet, um eine saubere und klare visuelle Struktur zu erhalten.

Diese Visualisierung hilft bei der Validierung der EA-Entscheidungen, da Sie sehen können, welche Umkehrpunkte identifiziert wurden, wie sie miteinander verbunden sind und welcher Pfad auf der Grundlage des Dijkstra-Algorithmus gewählt wurde. Es ist besonders nützlich beim Backtesting oder Live-Handel, um zu überprüfen, ob der EA die Marktstruktur wie beabsichtigt analysiert.

double High(int index){return (iHigh(_Symbol, _Period, index));}
double Low(int index){return (iLow(_Symbol, _Period, index));}
datetime Time(int index){return (iTime(_Symbol, _Period, index));}

Diese drei Hilfsfunktionen, „High()“, „Low()“ und „Time()“ sind einfache Umhüllungen der eingebauten MQL5-Funktionen „iHigh()“, „iLow()“ und „iTime()“. Sie ermöglichen einen einfachen Zugriff auf den Hochs und Tiefs sowie die Eröffnungszeit eines bestimmten Balkens (basierend auf dem angegebenen Index) für das aktuelle Symbol und den aktuellen Zeitrahmen. Durch die Verwendung dieser Kurz-Funktionen wird der Code sauberer und lesbarer, insbesondere beim wiederholten Zugriff auf die Daten der Balken während der Erkennung oder Visualisierung eines Umkehrpunktes.

//+------------------------------------------------------------------+
//|                          Filter and mark                         |
//+------------------------------------------------------------------+
void FilterAndMarkValidSwings(SwingPoint &points[]) {
   int count = ArraySize(points);
   if(count < 2) return;

   for(int i = 0; i < count; i++) {
      if(points[i].used) continue;

      bool isValid = true;
      double swingPrice = points[i].price;
      int swingIndex = points[i].index;

      // Scan forward in time from the swing point
      for(int j = swingIndex - 1; j >= 0; j--) {
         double high = iHigh(_Symbol, TimeFrame, j);
         double low  = iLow(_Symbol, TimeFrame, j);

         // Invalidate swing high if price went higher later
         if(points[i].isHigh && high > swingPrice) {
            isValid = false;
            break;
         }

         // Invalidate swing low if price went lower later
         if(!points[i].isHigh && low < swingPrice) {
            isValid = false;
            break;
         }
      }

      if(isValid) {
         points[i].used = true;

         // Draw object on chart
         string objName = points[i].isHigh ? 
            StringFormat("SwingHigh_%d", TimeToString(iTime(_Symbol, TimeFrame, swingIndex))) :
            StringFormat("SwingLow_%d", TimeToString(iTime(_Symbol, TimeFrame, swingIndex)));

         color swingColor = points[i].isHigh ? clrRed : clrBlue;

         ObjectCreate(0, objName, OBJ_HLINE, 0, 0, swingPrice);
         ObjectSetInteger(0, objName, OBJPROP_COLOR, swingColor);
         ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DASH);
         ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1);
      }
   }
}

Die Funktion „FilterAndMarkValidSwings()“ verfeinert die Liste der Umkehrpunkte, indem sie feststellt, welche Punkte noch gültig sind und nicht durch zukünftige Kursbewegungen ungültig gemacht wurden. Sie nimmt ein Array von „SwingPoints“-Referenzen und durchläuft sie, wobei sie alle überspringt, die bereits als „used“ markiert sind. Für jeden Kandidatenumkehr wird angenommen, dass der Punkt gültig ist, und dann eine Validierungsprüfung auf der Grundlage historischer Kursbewegungen durchgeführt, um zu bestätigen, ob sich der Kurs über diesen Umkehrpunkt hinaus bewegt hat, nachdem er gebildet wurde.

Um die Gültigkeit festzustellen, durchsucht die Funktion die vergangenen Takte ab dem Index des Umkehrpunktes rückwärts. Bei einem hohen Umkehrpunkt wird geprüft, ob eine zukünftige Kerze ein Hoch hatte, das dieses überstieg, und bei einem tiefen Umkehrpunkt wird geprüft, ob eine Kerze ein tieferes Tief hatte. Wird eine solche Bedingung festgestellt, wird der Umkehrpunkt als ungültig betrachtet, da der Preis diesen Umkehrpunkt effektiv „durchlaufen“ hat, sodass er nicht markiert oder in weiteren Berechnungen verwendet wird. Wird keine solche Bedingung gefunden, ist der Schwingungspunkt gültig und wird als „verwendet“ gekennzeichnet.

Für jeden gültigen Umkehrpunkt zeichnet die Funktion dann eine horizontale Linie auf dem Chart, um ihn visuell zu markieren. Die Linie ist gestrichelt und rot für Höchststände und blau für Tiefststände eingefärbt. Das Objekt wird anhand des Typs des Umkehrpunkts und der Uhrzeit des Balkens, an dem es erkannt wurde, benannt. Dieses visuelle Feedback hilft Händlern, sofort zu erkennen, welche Umkehrpunkte der EA als stark und vom Preis unberührt ansieht, was es einfacher macht, der Logik während der Analyse oder des Handels zu vertrauen und sie zu debuggen.

//+------------------------------------------------------------------+
//|                        Cleaning up old swings                    |
//+------------------------------------------------------------------+
void CleanOldSwingObjects(int keepBars = 100) {
   datetime oldestDate = iTime(_Symbol, TimeFrame, keepBars);
   int total = ObjectsTotal(0);

   for(int i = total - 1; i >= 0; i--) {
      string name = ObjectName(0, i);
      if(StringFind(name, "SwingHigh_") == 0 || StringFind(name, "SwingLow_") == 0) {
         datetime swingTime = (datetime)ObjectGetInteger(0, name, OBJPROP_TIME);
         if(swingTime < oldestDate) {
            ObjectDelete(0, name);
         }
      }
   }
}

In diesem Fall ist die Funktion dafür verantwortlich, veraltete, umkehrbezogene visuelle Elemente aus dem Chart zu entfernen, um die Übersichtlichkeit und Leistung zu erhalten. Er bestimmt den Schwellenwert für „alte“ Objekte mit Hilfe des Parameters „keepBars“, der den Zeitstempel des Balkens abruft, der vor „keepBars“ Kerzen ist. Alle Umkehr-Objekte, die vor diesem Zeitstempel erstellt wurden, gelten als veraltet. Die Funktion durchläuft dann alle grafischen Objekte im Chart in umgekehrter Reihenfolge und prüft, ob ihre Namen mit „SwingHigh_“ oder „SwingLow_“ beginnen, wodurch sie als Marker der Umkehrpunkte identifiziert werden.

Für jedes dieser Swing-Objekte wird der Erstellungszeitpunkt ermittelt und mit dem Zeitstempel für die Sperrfrist (oldestDate) verglichen. Wenn die Zeit des Objekts älter ist, wird es mit „objectDelete()“ aus dem Chart gelöscht. Diese Routine sorgt dafür, dass das Chart übersichtlich bleibt, indem nur die jüngsten und relevanten Umkehrpunkte angezeigt werden. Es hilft auch, einen Leistungsabfall im Laufe der Zeit zu verhindern, insbesondere wenn der EA auf längeren Historien oder in Live-Märkten läuft, wo sich viele Schwankungen ansammeln.

//+------------------------------------------------------------------+
//| Generate Signal & Trade                                          |
//+------------------------------------------------------------------+
void GenerateSignalAndTrade() {
   if (ArraySize(swingPoints) < 2) return;

   int last = ArraySize(swingPoints) - 1;
   int prev = swingPoints[last].previous;
   if (prev == -1) return;

   double entry = swingPoints[last].price;
   double reference = swingPoints[prev].price;
   double sl, tp;
   bool isBuy = entry > reference, isSell = entry < reference;

   SetSLTP(entry, reference, isBuy, sl, tp);

   if (PositionSelect(_Symbol)) return;

   if (isBuy)
      ExecuteTrade(ORDER_TYPE_BUY);
   else if(isSell)
      ExecuteTrade(ORDER_TYPE_SELL); 
}

Die Funktion „GenerateSignalAndTrade()“ ist für die Erzeugung eines Handelssignals auf der Grundlage der Richtung des letzten vom Dijkstra-Algorithmus gefundenen Pfades durch die Umkehrpunkte verantwortlich. Zunächst wird sichergestellt, dass es mindestens zwei zu vergleichende Schwingungspunkte gibt und dass der letzte Schwingungspunkt einen gültigen „vorherigen“ Knoten hat. Es extrahiert dann den Preis des letzten Umkehrpunkts und des damit verbundenen vorangegangenen Umkehrpunkts und verwendet die Preisbeziehung zwischen ihnen, um die Handelsrichtung zu bestimmen: Wenn der jüngste Preis höher ist als der vorherige, signalisiert er einen Kauf; wenn er niedriger ist, einen Verkauf.

Sobald die Richtung festgelegt ist, berechnet die Funktion Stop-Loss und Take-Profit mit Hilfe der Funktion „SetSLTP()“, die auf dem Abstand zwischen den Umkehrpunkten basiert. Bevor ein Handelsgeschäft eröffnet wird, wird geprüft, ob für das Symbol bereits eine Position offen ist, um Duplikate zu vermeiden. Abschließend wird der Handel mit der Funktion „ExecuteTrade()“ platziert, wobei die entsprechende Auftragsart übergeben wird. Diese Logik stellt sicher, dass Handelsgeschäfte nur dann ausgeführt werden, wenn ein klarer, strukturell unterstützter Richtungspfad zwischen gültigen Umkehrpunkten identifiziert wird.

//+------------------------------------------------------------------+
//| Calculate SL and TP based on distance to previous node           |
//+------------------------------------------------------------------+
void SetSLTP(double entry, double ref, bool isBuy, double &sl, double &tp) {
   double distance = MathAbs(entry - ref) + PointBuffer * _Point;
   if (isBuy) {
      sl = entry - distance;
      tp = entry + distance;
   } else {
      sl = entry + distance;
      tp = entry - distance;
   }
}

Die Funktion „SetSLTP()“ berechnet die Stop-Loss (sl) und Take-Profit (tp) für einen Handelsgeschäft auf der Grundlage des Abstands zwischen dem aktuellen Einstiegskurs und einem Referenzkurs (in der Regel der vorherige Umkehrpunkt). Es berechnet zunächst die absolute Preisdifferenz zwischen diesen beiden Punkten und fügt zur Sicherheit einen kleinen Puffer (in Punkten) hinzu. Bei einem Kauf wird der Stop-Loss unterhalb des Einstiegs und der Take-Profit oberhalb davon platziert; bei einem Verkauf liegt der Stop-Loss oberhalb des Einstiegs und der Take-Profit unterhalb. Dadurch wird sichergestellt, dass Risiko und Ertrag symmetrisch um die Struktur der Umkehrpunkte herum ausgerichtet sind, was dem EA hilft, der Preisbewegung mit sinnvollen, strukturbasierten SL- und TP-Levels zu folgen.

//+------------------------------------------------------------------+
//| Execute trade with risk parameters                               |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE tradeType){

   double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) :
                                                  SymbolInfoDouble(_Symbol, SYMBOL_BID);

   // Convert StopLoss and TakeProfit from pips to actual price distances
   double sl_distance = StopLoss * point;
   double tp_distance = TakeProfit * point;
   
   double sl = (tradeType == ORDER_TYPE_BUY) ? price - sl_distance :
                                               price + sl_distance;
   
   double tp = (tradeType == ORDER_TYPE_BUY) ? price + tp_distance :
                                               price - tp_distance;

   trade.PositionOpen(_Symbol, tradeType, In_Lot, price, sl, tp, NULL);
}

Die Funktion „ExecuteTrade()“ ist für die Erteilung eines Handelsauftrags mit vordefinierten Risikoparametern zuständig. Zunächst wird der aktuelle Marktpreis ermittelt: Für einen Kaufauftrag wird der Briefkurs (ask), für einen Verkauf der Geldkurs (bid) verwendet. Anschließend werden Stop-Loss und Take-Profit berechnet, indem die Eingabewerte (stopLoss und TakeProfit) von Punkten in tatsächliche Preisabstände umgewandelt werden, wobei die Punktgröße des Symbols verwendet wird. Je nachdem, ob es sich um einen Kauf oder Verkauf handelt, positioniert es den Stop-Loss und den Take-Profit entsprechend über oder unter dem Einstiegskurs.

Schließlich wird die Methode „PositionOpen()“ der Klasse „Trade“ verwendet, um den Handel mit den berechneten Parametern auszuführen, einschließlich Losgröße, Richtung, Einstiegskurs, SL, TP und kein eigener Kommentar. Dadurch wird sichergestellt, dass die Geschäfte unabhängig von der Marktrichtung einem einheitlichen Risikorahmen folgen.

//+------------------------------------------------------------------+
//| OnTick                                                           |
//+------------------------------------------------------------------+
void OnTick() {
   static datetime lastBarTime = 0;
   datetime currentBarTime = iTime(_Symbol, _Period, 0);

   if (currentBarTime != lastBarTime) {
      lastBarTime = currentBarTime;

      DetectSwings(LeftBars, RightBars);
      ApplyDijkstra();
      VisualizeSwings();
      GenerateSignalAndTrade();
      FilterAndMarkValidSwings(swingPoints);
      CleanOldSwingObjects();

   }
}

Die Funktion „OnTick()“ schließlich ist die Hauptausführungsschleife des EA, die bei jedem neuen Tick ausgelöst wird. Um redundante Verarbeitung zu vermeiden, wird eine statische Variable „lastBarTime“ verwendet, um zu erkennen, ob sich ein neuer Balken gebildet hat, indem sie mit der Eröffnungszeit des aktuellen Balkens verglichen wird. Wenn ein neuer Balken erkannt wird, wird „lastBarTime“ aktualisiert und die Kernlogik ausgeführt: Erkennung neuer Hochs und Tiefs von Umkehrpunkten (DetectSwings), Anwendung des Dijkstras Algorithmus, um den effizientesten Pfad zwischen den Schwüngen zu finden (ApplyDijkstra), die visuelle Darstellung der Schwünge und ihrer Verbindungen auf dem Chart (VisualiseSwings), die Erzeugung und Ausführung von Handelssignalen auf der Grundlage der Pfadrichtung (GenerateSignalAndTrade), das Herausfiltern ungültiger Umkehrpunkte (FilterAndMarkValidSwings) und schließlich das Aufräumen alter Umkehrobjekte, um das Chart übersichtlich zu halten (CleanOldSwingObjects).

Diese Struktur stellt sicher, dass der EA die Marktstruktur auf intelligente Weise verarbeitet.



Backtest-Ergebnisse

Die Backtests wurde für den 1H-Zeitrahmen über ein zweimonatiges Testfenster (01. Mai 2025 bis 20. Juni 2025) mit den folgenden Eingabeeinstellungen bewertet:

  • TP in Punkten = 1000
  • Stop loss = 385
  • Eingabe Lots = 0,01
  • Linke Balken = 3
  • Rechte Balken = 3
  • Max swing = 50
  • Punktpuffer = 10,0
  • Slippage = 5

Schlussfolgerung

Zusammenfassend lässt sich sagen, dass wir einen voll funktionsfähigen MQL5 Expert Advisor entwickelt haben, der die Struktur des Finanzmarktes mit Hilfe des Dijkstra-Algorithmus interpretiert, der auf Swing-Highs und Swing-Lows als Graph-Knoten angewendet wird. Das System erkennt signifikante Umkehrpunkte auf jedem neuen Balken, filtert ungültige Punkte heraus, die der Kurs bereits durchbrochen hat, und behandelt gültige Swings als Eckpunkte in einem Pfadfindungsalgorithmus. Anschließend wird der Preisabstand als Kantengewichtung verwendet, um den effizientesten Weg durch die Marktstruktur zu berechnen und die wahrscheinlichste Richtung der Preisbewegung zu bestimmen.

Basierend auf dieser Analyse generiert der EA direktionale Handelssignale und führt Trades mit korrekt berechneten Stop-Loss- und Take-Profit-Levels aus, die auf dem Abstand zwischen den Swing-Points basieren. Visuelle Hilfsmittel wie Pfeile und Trendlinien werden gezeichnet, um sowohl die erkannten Umkehrpunkte als auch die berechneten Pfade widerzuspiegeln, während Bereinigungsroutinen dafür sorgen, dass das Chart übersichtlich und aktuell bleibt.

Zusammenfassend lässt sich sagen, dass dieser EA über den traditionellen indikatorbasierten Handel hinausgeht, indem er einen grafikbasierten Algorithmus in die Preisaktionsanalyse integriert, was strukturiertere und logischere Handelsentscheidungen ermöglicht. Durch die Ausrichtung der Handelseinträge an der Marktschwingungsgeometrie und die Sicherstellung, dass jeder Knoten nur einmal verwendet wird, wenn er nicht gültig ist, ahmt das System nach, wie der Preis auf natürliche Weise durch Unterstützung und Widerstand fließt. Der modulare Aufbau mit Funktionen zur Erkennung, Validierung, Pfadfindung, Ausführung und Visualisierung macht es außerdem einfach, die Strategie weiter zu verbessern, zu erweitern oder zu testen. Dieses Projekt legt den Grundstein für ein intelligentes, adaptives Handelssystem, das Preisaktionen als ein navigierbares Netzwerk behandelt und Datenstrukturtheorie mit Marktverhalten kombiniert.


Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/18760

Beigefügte Dateien |
Dijkstras_Algo.mq5 (21.01 KB)
Automatisieren von Handelsstrategien in MQL5 (Teil 22): Erstellen eines Zone Recovery Systems für den Trendhandel mit Envelopes Automatisieren von Handelsstrategien in MQL5 (Teil 22): Erstellen eines Zone Recovery Systems für den Trendhandel mit Envelopes
In diesem Artikel entwickeln wir ein Zone Recovery System, das mit einer Envelopes-Trend-Handelsstrategie in MQL5 integriert ist. Wir skizzieren die Architektur für die Verwendung von RSI- und Envelopes-Indikatoren, um Handelsgeschäfte auszulösen und Erholungszonen zu verwalten, um Verluste zu mindern. Durch Implementierung und Backtests zeigen wir, wie man ein effektives automatisches Handelssystem für dynamische Märkte aufbaut.
MQL5-Assistenz-Techniken, die Sie kennen sollten (Teil 75): Verwendung des Awesome Oszillators und des Envelopes MQL5-Assistenz-Techniken, die Sie kennen sollten (Teil 75): Verwendung des Awesome Oszillators und des Envelopes
Der Awesome Oscillator von Bill Williams und der Envelopes-Kanal sind ein Paar, das komplementär in einem MQL5 Expert Advisor verwendet werden kann. Wir verwenden den Awesome Oscillator wegen seiner Fähigkeit, Trends zu erkennen, während der Envelope-Kanal zur Definition unserer Unterstützungs-/Widerstandsniveaus herangezogen wird. Bei der Erkundung dieser Indikatorpaarung verwenden wir den MQL5-Assistenten, um das Potenzial dieser beiden Indikatoren zu ermitteln und zu testen.
Vom Neuling zum Experten: Animierte Nachrichten-Schlagzeile mit MQL5 (IV) – Markteinsichten durch lokal verfügbare KI-Modelle Vom Neuling zum Experten: Animierte Nachrichten-Schlagzeile mit MQL5 (IV) – Markteinsichten durch lokal verfügbare KI-Modelle
In der heutigen Diskussion untersuchen wir, wie man Open-Source-KI-Modelle selbst hosten und zur Gewinnung von Markteinblicken nutzen kann. Dies ist Teil unserer laufenden Bemühungen, den News Headline EA zu erweitern, indem wir einen AI Info-Streifen einführen, die ihn in ein Multi-Integrations-Assistenz-Tool verwandelt. Der aktualisierte EA zielt darauf ab, Händler durch Kalenderereignisse, aktuelle Finanznachrichten, technische Indikatoren und jetzt auch durch KI-generierte Marktperspektiven auf dem Laufenden zu halten - und bietet so zeitnahe, vielfältige und intelligente Unterstützung für Handelsentscheidungen. Seien Sie dabei, wenn wir praktische Integrationsstrategien erforschen und untersuchen, wie MQL5 mit externen Ressourcen zusammenarbeiten kann, um ein leistungsstarkes und intelligentes Arbeitsterminal für den Handel aufzubauen.
Formulierung eines dynamischen Multi-Pair EA (Teil 3): Mean-Reversion- und Momentum-Strategien Formulierung eines dynamischen Multi-Pair EA (Teil 3): Mean-Reversion- und Momentum-Strategien
In diesem Artikel werden wir den dritten Teil unserer Reise zur Formulierung eines dynamischen Multi-Pair Expert Advisors (EA) erkunden und uns dabei speziell auf die Integration von Mean Reversion- und Momentum-Handelsstrategien konzentrieren. Wir werden aufschlüsseln, wie man Kursabweichungen vom Mittelwert (Z-Score) erkennt und darauf reagiert, und wie man das Momentum bei mehreren Devisenpaaren misst, um die Handelsrichtung zu bestimmen.