English
preview
Integration von MQL5 mit Datenverarbeitungspaketen (Teil 8): Einsatz von Graph Neural Networks zur Erkennung von Liquiditätszonen

Integration von MQL5 mit Datenverarbeitungspaketen (Teil 8): Einsatz von Graph Neural Networks zur Erkennung von Liquiditätszonen

MetaTrader 5Beispiele |
16 1
Hlomohang John Borotho
Hlomohang John Borotho

Inhaltsverzeichnis

  1. Einführung
  2. Modell- und Systemübersicht
  3. Historische Daten abrufen
  4. Die ersten Schritte
  5. Alles zusammen auf MQL5
  6. Backtest
  7. Schlussfolgerung


Einführung

Eine der ständigen Herausforderungen für Trader besteht darin, Liquiditätszonen zu identifizieren, bevor der Kurs auf sie reagiert. In zahlreichen Fällen werden Liquiditätspools, wie z. B. Ansammlungen von Stop-Orders um Swing-Highs, Swing-Lows oder Konsolidierungsbereiche, erst erkannt, nachdem sich der Markt bereits bewegt hat. Herkömmliche Indikatoren analysieren den Kurs oft in einer streng sequenziellen Weise – wobei jede Bar unabhängig behandelt wird –, ohne die tieferen strukturellen Beziehungen zwischen verschiedenen Kurspunkten im Markt zu erfassen. Für systematische Trader, die algorithmische Strategien entwickeln, erschwert diese Einschränkung die programmgesteuerte Erkennung, wie sich Liquidität um strukturelle Niveaus bildet, was zu verzögerten Einstiegen, verpassten Gelegenheiten oder Trades führt, die erst eingegangen werden, nachdem die Liquidität bereits verbraucht wurde.

Um diese Einschränkung zu beheben, können wir die Marktstruktur als Graph darstellen, bei dem Swing-Highs, Swing-Lows und strukturelle Wendepunkte zu Knoten werden, während die Beziehungen zwischen ihnen Kanten bilden, die beschreiben, wie sich der Kurs durch den Markt bewegt. Durch die Anwendung von Graph Neural Networks (GNNs) auf diese Struktur kann das Modell Interaktionsmuster zwischen diesen Knoten lernen, wodurch es erkennen kann, wo sich Liquidität wahrscheinlich ansammelt oder als Nächstes angesteuert wird. Durch die Integration dieses Ansatzes mit MQL5 können Trader fortschrittliche Datenverarbeitungs- und Machine-Learning-Techniken mit automatisierten Handelssystemen kombinieren und damit eine intelligentere Erkennung von Liquiditätszonen sowie fundiertere algorithmische Entscheidungen erreichen.


Modell- und Systemübersicht

Das Modell und der Expert Advisor (EA) arbeiten zusammen, indem sie die Verantwortlichkeiten zwischen Datenvorbereitung, Machine-Learning-Inferenz und Handelsausführung aufteilen. Der EA fungiert als Echtzeit-Marktschnittstelle innerhalb von MetaTrader 5 und sammelt kontinuierlich Kursdaten wie Hochs, Tiefs, Schlusskurse und Tick-Volumina. Aus diesen Daten konstruiert der EA eine strukturelle Darstellung des Marktes, indem er Swing-Highs und Swing-Lows erkennt, die dann in einem Graphen organisiert werden, in dem jeder Swing-Punkt zu einem Knoten wird und ihre strukturellen Beziehungen Kanten bilden. Diese Knoten werden mit Merkmalen wie Kursniveau, Swing-Typ und relativem Abstand zu benachbarten Swings angereichert. Die vorbereiteten Knotenmerkmale und Kantenverbindungen werden dann an das trainierte Graph Neural Network (GNN)-Modell übergeben, das den Graphen verarbeitet und Vorhersagen zurückgibt, die die Wahrscheinlichkeit bzw. Stärke einer Liquiditätskonzentration um jedes strukturelle Niveau herum angeben.

Sobald das Modell seine Inferenz abgeschlossen hat, interpretiert der EA die zurückgegebenen Vorhersagen, um handelbare Signale zu bestimmen. Wenn das Modell eine hohe Wahrscheinlichkeit für Liquidität oberhalb der aktuellen Marktstruktur identifiziert, kann das System mit einer bullischen Bewegung rechnen und eine Kaufposition vorbereiten, während vorhergesagte Liquidität unterhalb der Struktur eine potenzielle Verkaufsmöglichkeit signalisieren kann. Der EA wendet dann grundlegende Handelsmanagementregeln an – wie das Überprüfen von Spread-Bedingungen, das Bestätigen der strukturellen Ausrichtung und das Berechnen von Positionsparametern –, bevor eine Position eröffnet wird. Durch diesen Arbeitsablauf konzentriert sich das Machine-Learning-Modell auf das Erkennen komplexer Liquiditätsmuster, während der EA die operativen Aspekte des Handels übernimmt, wodurch das System graphbasierte Liquiditätsvorhersagen in automatisierte Kauf- oder Verkaufsausführungen innerhalb der Handelsplattform umwandeln kann.


Historische Daten abrufen

from datetime import datetime
import MetaTrader5 as mt5
import pandas as pd
import pytz

# Display MetaTrader5 package information
print("MetaTrader5 package author:", mt5.__author__)
print("MetaTrader5 package version:", mt5.__version__)

# Pandas display settings
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1500)

# Initialize MT5 connection
if not mt5.initialize():
    print("initialize() failed, error code =", mt5.last_error())
    quit()

# Define symbol
symbol = "XAUUSD.m"

# Ensure the symbol is available
if not mt5.symbol_select(symbol, True):
    print("Failed to select symbol:", symbol)
    mt5.shutdown()
    quit()

# Set timezone to UTC
timezone = pytz.timezone("Etc/UTC")

# Define date range
utc_from = datetime(2025, 11, 1, tzinfo=timezone)
utc_to   = datetime(2026, 2, 28, tzinfo=timezone)

# Get historical rates
rates = mt5.copy_rates_range(symbol, mt5.TIMEFRAME_H1, utc_from, utc_to)

# Shutdown MT5 connection
mt5.shutdown()

# Validate data
if rates is None or len(rates) == 0:
    print("No data retrieved. Check symbol or date range.")
else:
    
    print("First 10 bars:")
    for rate in rates[:10]:
        print(rate)

    # Convert to DataFrame
    rates_frame = pd.DataFrame(rates)

    # Convert time
    rates_frame['time'] = pd.to_datetime(rates_frame['time'], unit='s')

    # Save to CSV
    filename = "XAUUSD_H1.csv"
    rates_frame.to_csv(filename, index=False)

    print("\nData saved to:", filename)

Ausgabe:

Dieses Skript stellt eine Verbindung zwischen Python und dem MetaTrader 5-Terminal her, um historische Marktdaten für die Analyse und das Modelltraining abzurufen. Es beginnt mit dem Importieren der notwendigen Bibliotheken, der Anzeige der MetaTrader 5-Paketinformationen und der Konfiguration der Pandas-Anzeigeeinstellungen für eine übersichtlichere Datenansicht. Nach der Initialisierung der MetaTrader 5-Verbindung wählt das Skript das Symbol XAUUSD.m aus und definiert einen spezifischen historischen Datumsbereich unter Verwendung der UTC-Zeit. Anschließend fordert es stündliche Kursdaten innerhalb dieses Zeitraums unter Verwendung der Funktion copy_rates_range an, die strukturierte Informationen wie Eröffnungs-, Hoch-, Tief-, Schlusskurs und das Tick-Volumen zurückgibt.


Die ersten Schritte

import pandas as pd
import numpy as np
import torch
import torch.nn.functional as F

from torch_geometric.data import Data
from torch_geometric.nn import GCNConv

from sklearn.preprocessing import StandardScaler

df = pd.read_csv("XAUUSD_H1.csv")
df['time'] = pd.to_datetime(df['time'])
df.head()

Ausgabe:

Zunächst importieren wir die notwendigen Bibliotheken für die Datenverarbeitung, numerische Berechnungen und den Aufbau unseres Graph Neural Network-Modells. Anschließend laden wir den historischen XAUUSD H1-Datensatz aus der zuvor erstellten CSV-Datei und konvertieren die Zeitspalte in ein geeignetes Zeitformat für eine einfachere zeitbasierte Analyse. Abschließend zeigen wir eine Vorschau der ersten Zeilen des Datensatzes an, um zu überprüfen, ob die Daten korrekt geladen wurden und für die weitere Vorverarbeitung bereit sind.

def detect_swings(df, window=3):

    swing_highs = []
    swing_lows = []

    for i in range(window, len(df)-window):

        high = df['high'][i]
        low = df['low'][i]

        if high == max(df['high'][i-window:i+window]):
            swing_highs.append(i)

        if low == min(df['low'][i-window:i+window]):
            swing_lows.append(i)

    return swing_highs, swing_lows

highs, lows = detect_swings(df)

nodes = []
node_prices = []

for i in highs:

    price = df['close'][i]
    volume = df['tick_volume'][i]

    nodes.append([price, volume, 1])
    node_prices.append(price)

for i in lows:

    price = df['close'][i]
    volume = df['tick_volume'][i]

    nodes.append([price, volume, -1])
    node_prices.append(price)

nodes = np.array(nodes)
node_prices = np.array(node_prices)

In diesem Abschnitt definieren wir eine Funktion namens detect_swings, die für die Identifizierung von Swing-Highs und Swing-Lows innerhalb der Kursdaten verantwortlich ist. Die Funktion durchsucht den Datensatz unter Verwendung einer festgelegten Fenstergröße und vergleicht die Hoch- und Tiefstwerte jeder Kerze mit den umliegenden Kerzen. Wenn das Hoch einer Kerze das Maximum innerhalb des definierten Bereichs ist, wird es als Swing-High klassifiziert, während es als Swing-Low identifiziert wird, wenn sein Tief das Minimum innerhalb desselben Bereichs ist. Die Indizes dieser erkannten Swings werden dann in separaten Listen gespeichert, wodurch wir wichtige Wendepunkte in der Marktstruktur lokalisieren können.

Nach der Identifizierung der Swing-Punkte konstruieren wir die Knoten, die später vom Graph Neural Network verwendet werden. Für jeden erkannten Swing-High und Swing-Low extrahieren wir relevante Merkmale wie den Schlusskurs und das Tick-Volumen aus dem Datensatz. Diese Werte werden mit einem strukturellen Label kombiniert – 1 für Swing-Highs und -1 für Swing-Lows –, um die Art der Marktstruktur zu beschreiben, die durch jeden Knoten repräsentiert wird. Alle Knotenmerkmale werden dann in einem NumPy-Array gesammelt, während die entsprechenden Kursniveaus separat gespeichert werden, um die Daten für die nächste Stufe vorzubereiten, in der die Graphdarstellung erstellt und vom Modell verarbeitet wird.

scaler = StandardScaler()
nodes = scaler.fit_transform(nodes)
edges = []

for i in range(len(nodes)-1):

    edges.append([i, i+1])
    edges.append([i+1, i])

edge_index = torch.tensor(edges, dtype=torch.long).t().contiguous()

labels = []

for i in range(len(node_prices)):

    price = node_prices[i]

    # count nearby swing prices
    touches = np.sum(np.abs(node_prices - price) < 1.0)

    if touches >= 3:
        labels.append(1)
    else:
        labels.append(0)

labels = torch.tensor(labels)

unique, counts = np.unique(labels.numpy(), return_counts=True)

print(dict(zip(unique, counts)))

Ausgabe:

{np.int64(0): np.int64(311), np.int64(1): np.int64(103)}

Hier bereiten wir die Daten so vor, dass sie vom Graph Neural Network verarbeitet werden können. Wir beginnen damit, StandardScaler zu verwenden, um die Knotenmerkmale zu normalisieren, wodurch sichergestellt wird, dass Werte wie Kurs und Volumen für ein stabileres Modelltraining konsistent skaliert werden. Als Nächstes konstruieren wir die Kanten, indem wir jeden Knoten mit seinem benachbarten Knoten in beide Richtungen verbinden, wodurch ein einfacher struktureller Graph gebildet wird, der die Abfolge der Swing-Punkte im Markt darstellt.

Wir generieren dann Labels für jeden Knoten, indem wir messen, wie viele andere Swing-Kurse in der Nähe desselben Kursniveaus auftreten; wenn ein Kursniveau innerhalb eines kleinen Bereichs mehrfach berührt wird, wird es als potenzielle Liquiditätszone markiert; andernfalls wird es als normales Niveau markiert. Schließlich werden diese Labels in Tensoren umgewandelt und ihre Verteilung wird ausgegeben, was es uns ermöglicht, das Gleichgewicht zwischen Liquiditäts- und Nicht-Liquiditätsknoten vor dem Training des Modells schnell zu überprüfen.

x = torch.tensor(nodes, dtype=torch.float)

data = Data(
    x=x,
    edge_index=edge_index,
    y=labels
)

print(data)

Ausgabe:

Data(x=[414, 3], edge_index=[2, 826], y=[414])

In diesem Schritt konvertieren wir die verarbeiteten Knotenmerkmal-Daten in einen PyTorch-Tensor, sodass sie vom Graph Neural Network verarbeitet werden können. Wir konstruieren dann ein Datenobjekt für den Graphen unter Verwendung der Datenstruktur von PyTorch Geometric, wobei wir die Knotenmerkmale, Kantenverbindungen und Ziel-Labels in einer einzigen Graph-Repräsentation kombinieren. Abschließend geben wir das Graph-Objekt aus, um zu überprüfen, ob der Datensatz korrekt strukturiert wurde und für das Modelltraining bereit ist.

class LiquidityGNN(torch.nn.Module):

    def __init__(self):

        super().__init__()

        self.conv1 = GCNConv(3, 32)
        self.conv2 = GCNConv(32, 32)
        self.conv3 = GCNConv(32, 2)

    def forward(self, x, edge_index):

        x = self.conv1(x, edge_index)
        x = F.relu(x)

        x = self.conv2(x, edge_index)
        x = F.relu(x)

        x = self.conv3(x, edge_index)

        return x

model = LiquidityGNN()

optimizer = torch.optim.Adam(model.parameters(), lr=0.005)

weights = torch.tensor([1.0, 3.0])  # give liquidity higher importance

loss_fn = torch.nn.CrossEntropyLoss(weight=weights)

for epoch in range(800):

    model.train()
    optimizer.zero_grad()
    out = model(data.x, data.edge_index)
    loss = loss_fn(out, data.y)
    loss.backward()
    optimizer.step()

    if epoch % 50 == 0:
        print("Epoch:", epoch, "Loss:", loss.item())

Ausgabe:

In diesem Abschnitt definieren wir das LiquidityGNN-Modell, ein Graph Neural Network, das dazu entwickelt wurde, Beziehungen zwischen Swing-Punkten im Markt zu analysieren. Das Modell ist mit drei Graph Convolutional Network (GCN)-Schichten aufgebaut, wobei die erste Schicht die Eingabe-Knotenmerkmale in einen größeren Merkmalsraum transformiert, die zweite Schicht die gelernten strukturellen Informationen verfeinert und die letzte Schicht Vorhersagen für jeden Knoten erzeugt. Während des Forward-Passes verwenden die Graph-Faltungsoperationen sowohl die Knotenmerkmale als auch die Kantenverbindungen, um Informationen zwischen benachbarten Knoten zu verbreiten, während die ReLU-Aktivierungsfunktion Nichtlinearität einführt, was es dem Netzwerk ermöglicht, komplexere strukturelle Muster zu lernen, die mit Liquiditätszonen assoziiert sind.

Nach der Definition des Modells bereiten wir es für das Training vor, indem wir einen Adam-Optimierer und eine Cross-Entropy-Verlustfunktion mit gewichteten Klassen erstellen, wodurch Liquiditätsknoten eine höhere Bedeutung beigemessen wird, damit das Modell lernt, diese zu priorisieren. Die Trainingsschleife läuft dann über mehrere Epochen, in denen das Modell wiederholt die Graphdaten verarbeitet, den Vorhersageverlust im Vergleich zu den wahren Labels berechnet und seine Parameter durch Backpropagation aktualisiert. Alle paar Iterationen wird der Trainingsverlust ausgegeben, um den Lernfortschritt des Modells zu überwachen und sicherzustellen, dass es seine Fähigkeit, potenzielle Liquiditätszonen innerhalb der Graphdarstellung zu identifizieren, schrittweise verbessert.

model.eval()

pred = model(data.x, data.edge_index).argmax(dim=1)

print(pred)

Ausgabe:

Wir schalten das LiquidityGNN-Modell in den Evaluierungsmodus, um trainingsspezifische Verhaltensweisen zu deaktivieren, und generieren dann Vorhersagen für jeden Knoten, indem wir die Klasse mit der höchsten Ausgabewahrscheinlichkeit auswählen. Anschließend geben wir diese vorhergesagten Labels aus und zeigen, welche Knoten das Modell als potenzielle Liquiditätszonen identifiziert.

liquidity_nodes = (pred == 1).nonzero(as_tuple=True)[0]

print("Liquidity nodes:", liquidity_nodes)

Ausgabe:

Hier setzen wir das Modell in den Evaluierungsmodus und geben die vorhergesagte Klasse für jeden Knoten aus, was auf potenzielle Liquiditätszonen hinweist.

import os

mt5_path = r"C:\Users\...\AppData\Roaming\MetaQuotes\Terminal\...\MQL5\Files"

model_path = os.path.join(mt5_path, "liquidity_gnn.onnx")

dummy_x = data.x
dummy_edge = data.edge_index
model.eval()

torch.onnx.export(
    model,
    (dummy_x, dummy_edge),
    model_path,
    input_names=["node_features","edge_index"],
    output_names=["output"],
    opset_version=17,
    do_constant_folding=True,
    external_data=False,
    dynamic_axes={
        "node_features":{0:"nodes"},
        "edge_index":{1:"edges"},
        "output":{0:"nodes"}
    }
)
print("Model saved to:", model_path)

Ausgabe:

Wir definieren den Pfad zum Files-Verzeichnis von MetaTrader 5 und geben den Speicherort der ONNX-Modelldatei an, setzen dann das trainierte GNN-Modell in den Evaluierungsmodus und exportieren es mithilfe von torch.onnx.export in das ONNX-Format, wobei wir dynamische Achsen für Knoten und Kanten einbeziehen, um unterschiedliche Graphgrößen zu handhaben, und bestätigen schließlich, dass das Modell unter dem angegebenen Pfad gespeichert wurde.


Alles zusammen auf MQL5

//+------------------------------------------------------------------+
//|                                                     Graph NN.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

#resource "\\Files\\liquidity_gnn.onnx" as uchar ExtModel[]

#include <Trade/Trade.mqh>
#include <ONNXRuntime/ONNXRuntime.mqh>

//--- trading
CTrade trade;

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input          string            ModelFile="liquidity_gnn.onnx";
input          int               SwingWindow=3;
input          int               BarsToProcess=500;
input          int               MaxNodes=50;
input          int               MaxEdges=100;
input          double            MinLiquidityScore=0.7;
input          double            LotSize=0.1;
input          int               MagicNumber=777;
input          bool              UseStopLoss=true;
input          int               StopLossPoints=200;
input          bool              UseTakeProfit=true;
input          int               TakeProfitPoints=400;

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+

double               node_features[];
long                 edge_index[];
double               predictions[];
double               nodePrices[];
int                  swingHighs[];
int                  swingLows[];
int                  nodeCount=0;
int                  actualEdgeCount=0;
datetime             lastBarTime=0;
//--- scaler values
double               scalerMean[3]= {1950,500,0};
double               scalerStd[3]= {100,200,1};
long                 onnx_handle;
//--- Model dimensions from ONNX
long                 model_nodes = 0;
long                 model_features = 0;
long                 model_edges = 0;
long                 model_classes = 0;

In diesem Codeabschnitt betten wir zunächst das trainierte ONNX-GNN-Modell mithilfe von #resource als Ressource ein, was es dem Expert Advisor ermöglicht, direkt aus dem Files-Ordner darauf zuzugreifen, ohne auf externe Dateipfade angewiesen zu sein. Wir binden dann die notwendigen MQL5-Bibliotheken für den Handel (CTrade) und die ONNX-Runtime-Funktionalität ein und definieren Eingabeparameter, die Schlüsselaspekte des Systems steuern, wie das Swing-Erkennungsfenster, die Anzahl der zu verarbeitenden Bars, die maximale Anzahl an Knoten und Kanten für den Graphen, den Mindest-Liquiditätswert für die Handelsausführung, die Berechnung der Positionsparameter, Stop-Loss/Take-Profit-Einstellungen sowie eine eindeutige MagicNumber zur Identifizierung von Trades.

Wir deklarieren außerdem globale Variablen, um Graphdatenstrukturen und Modellausgaben zu speichern, einschließlich Arrays für Knotenmerkmale, Kantenindizes, Vorhersagen und den Knoten zugeordnete Kurse sowie Swing-Hochs und -Tiefs. Zusätzliche globale Variablen verfolgen die aktuelle Anzahl der Knoten, die tatsächlichen Kanten und den Zeitstempel der zuletzt verarbeiteten Bar. Um die Daten zu normalisieren, werden vorberechnete Mittelwert- und Standardabweichungswerte in Arrays (scalerMean und scalerStd) gespeichert, und Platzhalter für die ONNX-Modelldimensionen (Knoten, Merkmale, Kanten, Klassen) sowie das Handle der ONNX-Sitzung werden initialisiert, um sicherzustellen, dass der EA bereit ist, das Modell zu laden, eingehende Kursdaten zu verarbeiten und Handelsentscheidungen zu treffen.

//+------------------------------------------------------------------+
//| Expert initialization                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   trade.SetExpertMagicNumber(MagicNumber);

   onnx_handle = OnnxCreateFromBuffer(ExtModel, ONNX_DEFAULT);

   if(onnx_handle == INVALID_HANDLE)
     {
      Print("ONNX create failed: ", GetLastError());
      return INIT_FAILED;
     }

   Print("ONNX model loaded");

   OnnxTypeInfo type_info;

   long input_count = OnnxGetInputCount(onnx_handle);
   Print("Model inputs: ", input_count);

   for(long i = 0; i < input_count; i++)
     {
      string name = OnnxGetInputName(onnx_handle, i);
      Print("Input ", i, " name: ", name);

      if(OnnxGetInputTypeInfo(onnx_handle, i, type_info))
        {
         Print("Input ", i, " dimensions:");
         ArrayPrint(type_info.tensor.dimensions);

         //--- Store model dimensions
         if(name == "node_features")
           {
            if(ArraySize(type_info.tensor.dimensions) >= 2)
              {
               model_nodes = type_info.tensor.dimensions[0];
               model_features = type_info.tensor.dimensions[1];
               Print("Model expects node_features: [", model_nodes, ", ", model_features, "]");
              }
           }
         else
            if(name == "edge_index")
              {
               if(ArraySize(type_info.tensor.dimensions) >= 2)
                 {
                  model_edges = type_info.tensor.dimensions[1];
                  Print("Model expects edge_index: [2, ", model_edges, "]");
                 }
              }
        }
     }

   long output_count = OnnxGetOutputCount(onnx_handle);
   Print("Model outputs: ", output_count);

   for(long i = 0; i < output_count; i++)
     {
      string name = OnnxGetOutputName(onnx_handle, i);
      Print("Output ", i, " name: ", name);

      if(OnnxGetOutputTypeInfo(onnx_handle, i, type_info))
        {
         Print("Output ", i, " dimensions:");
         ArrayPrint(type_info.tensor.dimensions);

         if(ArraySize(type_info.tensor.dimensions) >= 2)
           {
            model_classes = type_info.tensor.dimensions[1];
            Print("Model output classes: ", model_classes);
           }
        }
     }

//--- Don't set fixed shapes - the model has dynamic axes
//--- Instead, we'll use the actual node count from detection

   Print("ONNX initialization complete");
   Print("Model expects: nodes=", model_nodes, ", features=", model_features,
         ", edges=", model_edges, ", classes=", model_classes);

   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Print("EA deinitializing. Reason code: ", reason);

//--- Release ONNX model session
   if(onnx_handle != INVALID_HANDLE)
     {
      OnnxRelease(onnx_handle);
      onnx_handle = INVALID_HANDLE;
      Print("ONNX session released");
     }

//--- Delete all objects created by this EA
//--- Delete liquidity objects on the chart
   int deleted = 0;
   for(int i = ObjectsTotal(0, -1, OBJ_RECTANGLE) - 1; i >= 0; i--)
     {
      string name = ObjectName(0, i);
      if(StringFind(name, "Liq_") == 0) // Objects starting with "Liq_"
        {
         ObjectDelete(0, name);
         deleted++;
        }
     }

   if(deleted > 0)
      Print("Deleted ", deleted, " rectangle objects");

//--- Optionally close all open positions (if you want this behavior)
   bool closePositions = false; // Set to true if you want to close all positions on deinit

   if(closePositions)
     {
      int closed = 0;
      for(int i = PositionsTotal() - 1; i >= 0; i--)
        {
         ulong ticket = PositionGetTicket(i);
         if(PositionSelectByTicket(ticket))
           {
            if(PositionGetInteger(POSITION_MAGIC) == MagicNumber)
              {
               trade.PositionClose(ticket);
               closed++;
              }
           }
        }

      if(closed > 0)
         Print("Closed ", closed, " positions");
     }

//--- Clear global arrays to free memory
   ArrayFree(node_features);
   ArrayFree(edge_index);
   ArrayFree(predictions);
   ArrayFree(nodePrices);
   ArrayFree(swingHighs);
   ArrayFree(swingLows);

   Print("EA deinitialization complete");
  }

In der Funktion OnInit() initialisieren wir den Expert Advisor, indem wir dem CTrade-Objekt zunächst die MagicNumber zuweisen, um sicherzustellen, dass die von diesem EA eröffneten Trades eindeutig identifiziert werden. Anschließend laden wir das eingebettete ONNX-Modell aus dem uchar-Array ExtModel mithilfe von OnnxCreateFromBuffer und prüfen, ob die Erstellung erfolgreich war. Sobald die Modellsitzung aktiv ist, fragen wir ihre Eingabe- und Ausgabespezifikationen mithilfe von OnnxGetInputCount, OnnxGetInputName, OnnxGetInputTypeInfo und ähnlichen Ausgabefunktionen ab. Dies ermöglicht es uns, die erwartete Anzahl an Knoten, Merkmalen, Kanten und Ausgabeklassen dynamisch zu speichern, die der EA später verwendet, um die Graphdaten korrekt für die Inferenz zu formatieren.

Die Funktion OnDeinit() sorgt für ein sauberes Herunterfahren und eine ordnungsgemäße Ressourcenverwaltung, wenn der EA entfernt oder das Terminal beendet wird. Sie gibt zunächst die ONNX-Sitzung frei, um Speicher freizugeben, und iteriert dann durch alle Chartobjekte, um alle vom EA erstellten Liquiditätsobjekte zu entfernen. Optional kann sie alle offenen Positionen schließen, die mit der MagicNumber des EA verknüpft sind. Abschließend leert sie die globalen Arrays für Knotenmerkmale, Kantenindizes, Vorhersagen, Kurse und Swings, um Speicher freizugeben.

//+------------------------------------------------------------------+
//| Expert tick                                                      |
//+------------------------------------------------------------------+
void OnTick()
  {
   datetime currentBar = iTime(_Symbol, _Period, 0);

   if(currentBar == lastBarTime)
      return;

   lastBarTime = currentBar;

   if(!DetectSwings())
      return;

   if(!BuildGraph())
      return;

   if(!RunInference())
      return;

   ExecuteTrades();
  }

//+------------------------------------------------------------------+
//| Detect swing highs/lows                                          |
//+------------------------------------------------------------------+
bool DetectSwings()
  {
   ArrayResize(swingHighs, 0);
   ArrayResize(swingLows, 0);

   double high[], low[];

   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);

   if(CopyHigh(_Symbol, _Period, 0, BarsToProcess, high) <= 0)
     {
      Print("Failed to copy high data");
      return false;
     }

   if(CopyLow(_Symbol, _Period, 0, BarsToProcess, low) <= 0)
     {
      Print("Failed to copy low data");
      return false;
     }

   for(int i = SwingWindow; i < BarsToProcess - SwingWindow; i++)
     {
      bool swingHigh = true;
      bool swingLow = true;

      for(int j = -SwingWindow; j <= SwingWindow; j++)
        {
         if(j == 0)
            continue;

         if(high[i] <= high[i + j])
            swingHigh = false;

         if(low[i] >= low[i + j])
            swingLow = false;
        }

      if(swingHigh)
        {
         int s = ArraySize(swingHighs);
         ArrayResize(swingHighs, s + 1);
         swingHighs[s] = i;
        }

      if(swingLow)
        {
         int s = ArraySize(swingLows);
         ArrayResize(swingLows, s + 1);
         swingLows[s] = i;
        }
     }

   Print("Detected ", ArraySize(swingHighs), " swing highs and ",
         ArraySize(swingLows), " swing lows");

   return true;
  }

In der Funktion OnTick() behandeln wir die Kernlogik des EA bei jedem eingehenden Tick. Die Funktion prüft zunächst, ob sich eine neue Bar gebildet hat, indem sie den Zeitstempel der aktuellen Bar mit lastBarTime vergleicht; falls nicht, wird die Funktion vorzeitig beendet, um redundante Berechnungen zu vermeiden. Sobald eine neue Bar erkannt wird, ruft der EA nacheinander DetectSwings() auf, um aktuelle Swing-Hochs und -Tiefs zu identifizieren, BuildGraph(), um die Graphdarstellung für das ONNX-Modell zu erstellen, RunInference(), um Liquiditätsvorhersagen vom GNN zu erhalten, und schließlich ExecuteTrades(), um Kauf- oder Verkaufsaufträge basierend auf den vorhergesagten Signalen zu platzieren.

Die Funktion DetectSwings() durchsucht aktuelle Bars, um lokale Maxima und Minima zu identifizieren, die Swing-Hochs und -Tiefs darstellen. Unter Verwendung eines konfigurierbaren SwingWindow iteriert sie durch die Arrays der Hochs und Tiefs und vergleicht jede Bar mit ihren umliegenden Nachbar-Bars, um festzustellen, ob sie sich als Swing-Punkt qualifiziert. Erkannte Swings werden in den dynamischen Arrays swingHighs und swingLows gespeichert. Die Funktion behandelt auch Fehler beim Kopieren von Kursdaten und gibt eine Zusammenfassung der Anzahl der erkannten Swings aus, was für Transparenz und Einblicke bei der Fehlersuche sorgt. Dieser Schritt ist entscheidend, da er die grundlegenden Knoten für den Aufbau des Graphen bildet, den das GNN für die Liquiditätsanalyse verwendet.

//+------------------------------------------------------------------+
//| Build graph                                                      |
//+------------------------------------------------------------------+
bool BuildGraph()
  {
   double close[];
   long volume[];

   ArraySetAsSeries(close, true);
   ArraySetAsSeries(volume, true);

   if(CopyClose(_Symbol, _Period, 0, BarsToProcess, close) <= 0)
     {
      Print("Failed to copy close data");
      return false;
     }

   if(CopyTickVolume(_Symbol, _Period, 0, BarsToProcess, volume) <= 0)
     {
      Print("Failed to copy volume data");
      return false;
     }

   nodeCount = 0;
   ArrayResize(nodePrices, MaxNodes);

//--- swing highs
   for(int i = 0; i < ArraySize(swingHighs) && nodeCount < MaxNodes; i++)
     {
      int bar = swingHighs[i];
      if(bar < BarsToProcess)
        {
         nodePrices[nodeCount] = close[bar];
         nodeCount++;
        }
     }

//--- swing lows
   for(int i = 0; i < ArraySize(swingLows) && nodeCount < MaxNodes; i++)
     {
      int bar = swingLows[i];
      if(bar < BarsToProcess)
        {
         nodePrices[nodeCount] = close[bar];
         nodeCount++;
        }
     }

   if(nodeCount < 2)
     {
      Print("Not enough nodes: ", nodeCount);
      return false;
     }

   Print("Building graph with ", nodeCount, " nodes");

//--- Build node features [nodes, 3]
   ArrayResize(node_features, nodeCount * 3);
   ArrayInitialize(node_features, 0);

   int f = 0;
   for(int i = 0; i < nodeCount; i++)
     {
      double price = nodePrices[i];
      double vol = 0;

      //--- Get volume for this bar
      if(i < BarsToProcess)
         vol = (double)volume[i];

      //--- Determine swing type (1 for highs, -1 for lows)
      double swingType = (i < ArraySize(swingHighs)) ? 1.0 : -1.0;

      //--- Normalize features using scaler
      node_features[f++] = (price - scalerMean[0]) / scalerStd[0];  // price
      node_features[f++] = (vol - scalerMean[1]) / scalerStd[1];    // volume
      node_features[f++] = (swingType - scalerMean[2]) / scalerStd[2]; // swing type
     }

//--- Build edges
//--- For a chain graph with bidirectional connections between consecutive nodes:
//--- Number of edges = (nodeCount - 1) * 2
   actualEdgeCount = (nodeCount - 1) * 2;

//--- Edge_index should be a flattened array of shape [2, actualEdgeCount]
//--- That means: [row0_edge0, row1_edge0, row0_edge1, row1_edge1, ...]
//--- So total array size = 2 * actualEdgeCount
   int edgeArraySize = 2 * actualEdgeCount;
   ArrayResize(edge_index, edgeArraySize);
   ArrayInitialize(edge_index, 0);

   Print("Creating ", actualEdgeCount, " edges, array size: ", edgeArraySize);

   int e = 0;
   for(int i = 0; i < nodeCount - 1; i++)
     {
      //--- Forward edge: from i to i+1
      if(e < edgeArraySize - 1)
        {
         edge_index[e] = 0;     // row 0 (source row)
         e++;
         edge_index[e] = i;      // source node index
         e++;
        }

      //--- Forward edge: target
      if(e < edgeArraySize - 1)
        {
         edge_index[e] = 1;      // row 1 (target row)
         e++;
         edge_index[e] = i + 1;  // target node index
         e++;
        }

      //--- Backward edge: from i+1 to i (source)
      if(e < edgeArraySize - 1)
        {
         edge_index[e] = 0;      // row 0 (source row)
         e++;
         edge_index[e] = i + 1;  // source node index
         e++;
        }

      //--- Backward edge: target
      if(e < edgeArraySize - 1)
        {
         edge_index[e] = 1;      // row 1 (target row)
         e++;
         edge_index[e] = i;      // target node index
         e++;
        }
     }

   Print("Added ", e/2, " edge entries (", e, " array elements used)");

//--- Verify we used the correct number of elements
   if(e != edgeArraySize)
     {
      Print("Warning: Expected ", edgeArraySize, " elements but used ", e);
     }

   return true;
  }

//+------------------------------------------------------------------+
//| Run ONNX inference                                               |
//+------------------------------------------------------------------+
bool RunInference()
  {
//--- Set dynamic shapes before inference
   long input0_shape[] = {nodeCount, 3};
   if(!OnnxSetInputShape(onnx_handle, 0, input0_shape))
     {
      Print("Failed to set input shape for node_features: ", GetLastError());
      return false;
     }

//--- actualEdgeCount is the number of edges (not the array size)
//--- Edge_index shape is [2, actualEdgeCount]
   long input1_shape[] = {2, actualEdgeCount};
   if(!OnnxSetInputShape(onnx_handle, 1, input1_shape))
     {
      Print("Failed to set input shape for edge_index: ", GetLastError());
      return false;
     }

   long output_shape[] = {nodeCount, 2};
   if(!OnnxSetOutputShape(onnx_handle, 0, output_shape))
     {
      Print("Failed to set output shape: ", GetLastError());
      return false;
     }

//--- Debug: Print array sizes
   Print("Running inference with: nodes=", nodeCount,
         ", edges=", actualEdgeCount,
         ", features array size=", ArraySize(node_features),
         ", edges array size=", ArraySize(edge_index));

//--- Run inference
   ArrayResize(predictions, nodeCount * 2);

   if(!OnnxRun(onnx_handle, ONNX_NO_CONVERSION, node_features, edge_index, predictions))
     {
      Print("ONNX inference failed: ", GetLastError());
      return false;
     }

   ProcessPredictions();

   return true;
  }

Die Funktion BuildGraph() konstruiert die Graphdarstellung der jüngsten Swing-Punkte im Markt, die in das ONNX-GNN-Modell eingespeist wird. Sie sammelt zunächst die neuesten Schlusskurse und Tick-Volumina für die angegebene Anzahl von Bars und kombiniert dann die erkannten Swing-Hochs und -Tiefs zu einem Knoten-Array. Jedem Knoten werden drei Merkmale zugewiesen – normalisierter Kurs, Volumen und Swing-Typ (hoch=1, tief=-1) – unter Verwendung vordefinierter Skalierungswerte. Die Funktion erstellt außerdem bidirektionale Kanten zwischen aufeinanderfolgenden Knoten, um einen Kettengraphen zu bilden, speichert diese in einem flachen edge_index-Array und verfolgt gleichzeitig die Gesamtzahl der Knoten und Kanten für eine dynamische ONNX-Eingabegrößenanpassung.

Die Funktion RunInference() bereitet dann die dynamischen Eingabe- und Ausgabeformen basierend auf der tatsächlichen Anzahl der in BuildGraph() erstellten Knoten und Kanten vor. Sie ändert die Größe der ONNX-Eingabetensoren (node_features und edge_index) sowie des Ausgabetensors, gibt Debug-Informationen zur Überprüfung aus und führt das ONNX-Modell mithilfe von OnnxRun() aus. Vorhersagen werden im predictions-Array gespeichert und von ProcessPredictions() verarbeitet. Dieser Schritt übersetzt die Graphdarstellung der Markt-Swings direkt in nutzbare Handelssignale, die der EA später zum Auslösen von Trades verwenden kann, wodurch sichergestellt wird, dass die Echtzeit-Inferenz mit der dynamisch erkannten Marktstruktur übereinstimmt.

//+------------------------------------------------------------------+
//| Process predictions                                              |
//+------------------------------------------------------------------+
void ProcessPredictions()
  {
   int liquidityCount = 0;

   for(int i = 0; i < nodeCount; i++)
     {
      double p0 = predictions[i * 2];
      double p1 = predictions[i * 2 + 1];

      //--- Apply softmax manually
      double maxv = MathMax(p0, p1);
      double e0 = MathExp(p0 - maxv);
      double e1 = MathExp(p1 - maxv);
      double prob = e1 / (e0 + e1);  // Probability of class 1 (liquidity)

      if(prob > MinLiquidityScore)
        {
         liquidityCount++;
         DrawLiquidityZone(i, nodePrices[i], prob);
        }
     }

   if(liquidityCount > 0)
      Print("Found ", liquidityCount, " liquidity zones");
  }

//+------------------------------------------------------------------+
//| Execute trades                                                   |
//+------------------------------------------------------------------+
void ExecuteTrades()
  {
   if(CountPositions() > 0)
      return;

   double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   for(int i = 0; i < nodeCount; i++)
     {
      if(MathAbs(nodePrices[i] - ask) < 10 * _Point)
        {
         if(ask < nodePrices[i])
            OpenTrade(ORDER_TYPE_BUY);
         else
            OpenTrade(ORDER_TYPE_SELL);
         break;
        }
     }
  }

//+------------------------------------------------------------------+
//| Open trade                                                       |
//+------------------------------------------------------------------+
void OpenTrade(ENUM_ORDER_TYPE type)
  {
   double price, sl = 0, tp = 0;

   if(type == ORDER_TYPE_BUY)
      price = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   else
      price = SymbolInfoDouble(_Symbol, SYMBOL_BID);

   if(UseStopLoss)
     {
      if(type == ORDER_TYPE_BUY)
         sl = price - StopLossPoints * _Point;
      else
         sl = price + StopLossPoints * _Point;
     }

   if(UseTakeProfit)
     {
      if(type == ORDER_TYPE_BUY)
         tp = price + TakeProfitPoints * _Point;
      else
         tp = price - TakeProfitPoints * _Point;
     }

   if(type == ORDER_TYPE_BUY)
      trade.Buy(LotSize, _Symbol, price, sl, tp);
   else
      trade.Sell(LotSize, _Symbol, price, sl, tp);
  }

//+------------------------------------------------------------------+
//| Draw zone                                                        |
//+------------------------------------------------------------------+
void DrawLiquidityZone(int id, double price, double probability)
  {
   string name = "Liq_" + IntegerToString(id) + "_" + IntegerToString(TimeCurrent());

   datetime t1 = iTime(_Symbol, _Period, 0);
   datetime t2 = t1 + PeriodSeconds(_Period) * 5;

   if(!ObjectCreate(0, name, OBJ_RECTANGLE, 0,
                    t1, price - 5 * _Point,
                    t2, price + 5 * _Point))
     {
      Print("Failed to create rectangle object");
      return;
     }

   ObjectSetInteger(0, name, OBJPROP_COLOR, clrGold);
   ObjectSetInteger(0, name, OBJPROP_FILL, true);
   ObjectSetString(0, name, OBJPROP_TEXT, "Liquidity: " + DoubleToString(probability * 100, 1) + "%");
  }

Die Funktion ProcessPredictions() nimmt die Rohausgaben des ONNX-GNN-Modells entgegen und wandelt sie für jeden erkannten Knoten in umsetzbare Liquiditätswahrscheinlichkeiten um. Für jeden Knoten wendet sie manuell eine Softmax-Berechnung an, um die Wahrscheinlichkeit zu bestimmen, dass der Knoten eine signifikante Liquiditätszone darstellt. Knoten, die den benutzerdefinierten Schwellenwert MinLiquidityScore überschreiten, werden gezählt und mithilfe von DrawLiquidityZone() im Chart visualisiert, wobei rechteckige Zonen um die vorhergesagten Kursniveaus farblich markiert und mit einer Wahrscheinlichkeitsangabe versehen. Dies ermöglicht es Tradern, zu sehen, wo das Modell potenzielle Bereiche hoher Liquidität im Markt identifiziert.

Die nachfolgende Handelslogik nutzt diese identifizierten Liquiditätszonen, um Positionen automatisch auszuführen. ExecuteTrades() prüft zunächst, ob keine offenen Positionen vorhanden sind, und vergleicht dann die aktuellen Geld- und Briefkurse mit den erkannten den Knoten zugeordneten Kursen. Wenn der Kurs innerhalb eines engen Bereichs eines Knotens liegt, eröffnet das System entsprechend eine Kauf- oder Verkaufsposition mithilfe von OpenTrade(), wobei konfigurierbare Stop-Loss- und Take-Profit-Niveaus angewendet werden. Die Funktion DrawLiquidityZone() stellt sicher, dass jede Liquiditätszone im Chart deutlich mit einem Rechteck und einer annotierten Wahrscheinlichkeit markiert ist, was sowohl einen visuellen Kontext als auch automatisierte Ausführungssignale für den EA liefert.

//+------------------------------------------------------------------+
//|                                                  ONNXRuntime.mqh |
//|                        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/"

//--- ONNX flags (keep it in sync with MT5 if changed)
#define ONNX_DEFAULT       0
#define ONNX_DEBUG_LOGS    1
#define ONNX_NO_CONVERSION 2

//--- Simple ONNX model wrapper that calls MT5 built-in Onnx* functions.
//--- This wrapper DOES NOT redeclare OnnxTypeInfo and DOES NOT import any DLL.

//+------------------------------------------------------------------+
//|                     Simple ONNX Runtime Wrapper                  |
//+------------------------------------------------------------------+
class CSimpleONNXModel
  {
private:
   long              session;
   bool              initialized;

public:

                     CSimpleONNXModel()
     {
      session = INVALID_HANDLE;
      initialized = false;
     }

                    ~CSimpleONNXModel()
     {
      Release();
     }

   //--- Initialize model from resource
   bool              InitFromResource(const uchar &model[])
     {
      session = OnnxCreateFromBuffer(model, ONNX_DEFAULT);

      if(session == INVALID_HANDLE)
        {
         Print("ONNX create failed: ", GetLastError());
         return false;
        }

      initialized = true;

      Print("ONNX model loaded successfully");

      return true;
     }

   bool              SetInputShape(int index,long &shape[])
     {
      return OnnxSetInputShape(session,index,shape);
     }

   bool              SetOutputShape(int index,long &shape[])
     {
      return OnnxSetOutputShape(session,index,shape);
     }

   bool              Run(double &input_features[],
            long &edge_index[],
            double &output[])
     {
      if(!initialized)
         return false;

      bool result = OnnxRun(session,
                            ONNX_NO_CONVERSION,
                            input_features,
                            edge_index,
                            output);

      if(!result)
         Print("ONNX run failed: ",GetLastError());

      return result;
     }

   void              Release()
     {
      if(session!=INVALID_HANDLE)
        {
         OnnxRelease(session);
         session=INVALID_HANDLE;
        }
     }
  };
//+------------------------------------------------------------------+

Schließlich haben wir unsere ONNXRuntime-Include-Datei, die als leichtgewichtiger Wrapper fungiert, der es dem Expert Advisor ermöglicht, mit dem ONNX-Modell für maschinelles Lernen innerhalb von MetaTrader 5 zu interagieren. Diese Datei definiert eine kleine Klasse namens CSimpleONNXModel, die die Verwendung der integrierten ONNX-Funktionen von MetaTrader 5 wie OnnxCreateFromBuffer, OnnxSetInputShape, OnnxSetOutputShape und OnnxRun vereinfacht. Anstatt diese Funktionen direkt im gesamten EA aufzurufen, organisiert der Wrapper sie in übersichtlichen Methoden, die die Modellinitialisierung, die Konfiguration der Eingabe-/Ausgabeform, die Ausführung der Inferenz und die Verwaltung der Sitzung übernehmen, wodurch die Integration zwischen dem EA und dem trainierten Modell sauberer und wartungsfreundlicher wird.

Der Wrapper verwaltet zudem den Lebenszyklus der ONNX-Sitzung und stellt sicher, dass das Modell korrekt aus einer Ressource geladen, mit den bereitgestellten Eingabe-Tensoren ausgeführt und sicher freigegeben wird, wenn es nicht mehr benötigt wird. Durch die Kapselung dieser Operationen in einer einzigen wiederverwendbaren Klasse schaffen wir eine strukturierte Schnittstelle zwischen der MQL5-Handelsumgebung und dem exportierten Graph Neural Network, die es dem EA ermöglicht, effizient Inferenz durch maschinelles Lernen durchzuführen und die Vorhersagen des Modells in Handelsentscheidungen umzuwandeln, ohne die ONNX-Runtime-Aufrufe auf niedrigerer Ebene direkt handhaben zu müssen.


Backtest-Ergebnisse des Modells

 Betrachtet man die unten stehenden Backtest-Ergebnisse, so lässt sich erkennen, dass die Integration der auf Graph Neural Networks basierenden Liquiditätserkennung in das Handelssystem handelbare Marktstrukturzonen identifizieren kann, was zu einem positiven Nettogewinn und einem Profitfaktor von über 1 führt, was mit unserem Ziel übereinstimmt, liquiditätsgetriebene Handelsgelegenheiten algorithmisch zu erkennen. Das Backtesting wurde für das Währungspaar XAUUSD im H1-Zeitrahmen über einen Testzeitraum von zwei Monaten (1. Dezember 2025 bis 30. Januar 2026) mit den Standardeinstellungen durchgeführt:


Schlussfolgerung

Zusammenfassend haben wir die Herausforderung der Identifizierung von Liquiditätszonen – Bereiche, in denen der Kurs aufgrund gebündelter Aufträge wahrscheinlich reagieren wird – durch die Kombination von graphbasiertem maschinellem Lernen mit einem automatisierten Handelssystem in MQL5 behandelt. Anstatt uns ausschließlich auf traditionelle Indikatoren oder manuelle Chartanalysen zu verlassen, haben wir die Marktstruktur in eine Graphdarstellung umgewandelt, bei der Swing-Hochs und -Tiefs zu Knoten wurden und ihre Beziehungen Kanten bildeten. Unter Verwendung dieser Struktur haben wir ein Graph Neural Network (GNN) trainiert, um Muster zu erkennen, die mit Liquiditätsakkumulation verbunden sind. Das trainierte Modell wurde anschließend in das ONNX-Format exportiert und in einen Expert Advisor integriert, wodurch das System in der Lage ist, Live-Marktdaten zu verarbeiten, Graphen dynamisch zu erstellen, die Modellinferenz auszuführen und potenzielle Liquiditätszonen direkt innerhalb der Handelsplattform zu erkennen.

Dieser Ansatz ermöglicht es Tradern, ihre Systeme mit fortschrittlichen Mustererkennungsfunktionen zu erweitern, die über die herkömmliche technische Analyse hinausgehen. Durch die Integration von maschinellem Lernen mit der Analyse der Marktstruktur können Trader die Erkennung von Liquiditätsbereichen automatisieren und diese nutzen, um fundiertere Handelsentscheidungen zu treffen. Wenn dieses Framework in eine automatisierte Strategie implementiert wird, kann es Tradern helfen, schneller auf strukturelle Gelegenheiten zu reagieren, subjektive Voreingenommenheit bei der Identifizierung von Liquidität zu reduzieren und potenziell die Konsistenz ihrer Handelssysteme zu verbessern, indem KI-gestützte Erkenntnisse mit algorithmischer Ausführung kombiniert werden.

Dateiname
Beschreibung
GNN for LqZs.ipynb Notebook-Datei zum Trainieren und Speichern des Modells.
ONNXRuntime.mqh Datei mit der ONNXRuntime, unserer Include-Datei.
Graph NN.mq5 Datei mit dem MQL5-EA, der das Modell lädt und Trades ausführt.
XAUUSD_H1.csv Datei mit historischen XAUUSD-Kursdaten.

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

Beigefügte Dateien |
GNN_for_LqZs.ipynb (14.45 KB)
ONNXRuntime.mqh (2.7 KB)
Graph_NN.mq5 (18.5 KB)
XAUUSD_H1.csv (117.25 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (1)
Stanislav Korotky
Stanislav Korotky | 19 März 2026 in 11:53
Ein Bild sagt mehr als tausend Worte. Warum sollte man in einem Artikel über einen Graphen nicht einfach eine Darstellung dieses Graphen zeigen?
Die Übertragung der Trading-Signale in einem universalen Expert Advisor. Die Übertragung der Trading-Signale in einem universalen Expert Advisor.
In diesem Artikel wurden die verschiedenen Möglichkeiten beschrieben, um die Trading-Signale von einem Signalmodul des universalen EAs zum Steuermodul der Positionen und Orders zu übertragen. Es wurden die seriellen und parallelen Interfaces betrachtet.
Der MQL5-Standardbibliotheks-Explorer (Teil 9): Verwendung von ALGLIB zur Filterung übermäßiger MA-Crossover-Signale Der MQL5-Standardbibliotheks-Explorer (Teil 9): Verwendung von ALGLIB zur Filterung übermäßiger MA-Crossover-Signale
Bei Seitwärtsbewegungen des Kurses sehen sich Händler mit übermäßigen Signalen aus mehreren Kreuzungen gleitender Durchschnitte konfrontiert. Heute besprechen wir, wie ALGLIB ungefilterte Kursdaten vorverarbeitet, um gefilterte Crossover-Ebenen bzw. -Darstellungen zu erzeugen, die bei ihrem Auftreten auch Alarme oder Benachrichtigungen auslösen können. Nehmen Sie an dieser Diskussion teil, um zu erfahren, wie eine mathematische Bibliothek in MQL5-Programmen genutzt werden kann.
Eine alternative Log-datei mit der Verwendung der HTML und CSS Eine alternative Log-datei mit der Verwendung der HTML und CSS
In diesem Artikel werden wir eine sehr einfache, aber leistungsfähige Bibliothek zur Erstellung der HTML-Dateien schreiben, dabei lernen wir auch, wie man eine ihre Darstellung einstellen kann (nach seinem Geschmack) und sehen wir, wie man es leicht in seinem Expert Advisor oder Skript hinzufügen oder verwenden kann.
Larry Williams’ Marktgeheimnisse (Teil 14): Erkennung versteckter Hidden-Smash-Day-Umkehrmuster mit einem benutzerdefinierten Indikator Larry Williams’ Marktgeheimnisse (Teil 14): Erkennung versteckter Hidden-Smash-Day-Umkehrmuster mit einem benutzerdefinierten Indikator
Dieser Artikel entwickelt einen praktischen MQL5-Indikator, der Hidden-Smash-Day-Bars anhand strenger numerischer Kriterien und einer optionalen Bestätigung in der folgenden Sitzung identifiziert. Wir behandeln Erkennungsroutinen, das Einrichten der Puffer und die Plot-Einstellungen, um Pfeile an gültigen Bars zu platzieren. Der Ansatz liefert stabile, nicht nachträglich neu gezeichnete Signale für historische Tests und Echtzeitüberwachung.