English 日本語
preview
Entwicklung des Price Action Analysis Toolkit (Teil 35): Training und Einsatz von Vorhersagemodellen

Entwicklung des Price Action Analysis Toolkit (Teil 35): Training und Einsatz von Vorhersagemodellen

MetaTrader 5Handelssysteme |
111 1
Christian Benjamin
Christian Benjamin

Einführung

Im vorangegangenen Artikel haben wir eine zuverlässige Pipeline für das Streaming historischer Daten aus einem MQL5-Skript in Python und deren Speicherung auf der Festplatte eingerichtet. Wir haben bewiesen, dass die Balken des Marktes erfasst, serialisiert und neu geladen werden können, aber wir haben nicht mit der Modellanpassung begonnen.

Diese Folge macht genau da weiter, wo wir aufgehört haben. Wir gehen über die Lagerung hinaus und zeigen, wie es geht:

  • Vorhersagemodelle anhand der eingegebenen Daten zu trainieren,
  • diese Modelle pro Symbol verpacken und zwischenlagern und
  • sie als Hintergrund einer leichtgewichtigen REST-API einsetzen, die ein MQL5 Expert Advisor in Echtzeit abfragen kann.

Um dies zu erreichen, kombinieren wir die Stärken des Python-Ökosystems für maschinelles Lernen mit der Ausführungsgeschwindigkeit des MetaTrader 5. Der EA kümmert sich um die Interaktion mit dem Markt, während der Python-Dienst das Feature-Engineering, die Modellinferenz und – optional – das periodische Retraining übernimmt.

Ein „trainierbares Modell“ ist in diesem Zusammenhang jeder Algorithmus, dessen interne Parameter anhand von Daten optimiert werden können. Klassische Techniken (über scikit-learn) wie Gradient Boosting oder Support-Vector Machines sind für tabellarische Merkmalsätze geeignet, während Deep-Learning-Frameworks (TensorFlow, PyTorch) bei Bedarf komplexere Architekturen unterstützen. Die umfangreiche Bibliotheksunterstützung, die klare Syntax und die aktive Community machen Python zur Sprache der Wahl für diese Phase der Pipeline.

In der nachstehenden Tabelle sind die jeweiligen Zuständigkeiten im fertigen System zusammengefasst:

Komponente Rolle im Arbeitsablauf
MQL5 Expert Advisor Sammelt Live-Balken und den Kontostatus; sendet Funktionsanfragen; führt Handelssignale aus, die von der API zurückgegeben werden.
Python-Ingestion-Skript Empfängt Stücke (chunks) der Historie von MetaTrader 5, bereinigt und speichert sie (Parquet).
Modul für das Feature-Engineering
Konvertiert OHLC-Rohdaten in technische und statistische Merkmale.
Modultraining Passt Modelle pro Symbol an oder aktualisiert sie; serialisiert sie mit joblib.
Flask-REST-Dienste Bedient /predict, /upload_history usw.; verwaltet einen speicherinternen Modell-Cache für Antworten auf Millisekundenebene.

Dieser Artikel ist wie folgt gegliedert:

Lassen Sie uns eintauchen.


Rekapitulation der Ingestion Pipeline

Wie im vorangegangenen Artikel beschrieben, rationalisiert unser „history ingestion“ Skript zum Abrufen der Historie den gesamten MetaTrader 5-Python-Workflow: Es zieht zunächst die gewünschten historischen Balken über CopyRates, parst dann Zeitstempel, Höchst- und Tiefstwerte sowie Schlussstände in Arrays, bevor es jeden Teil mit BuildJSON zu einer JSON-Nutzlast zusammensetzt. Um die Größenbeschränkungen von MetaTrader 5 für WebRequests einzuhalten, werden die Daten automatisch in überschaubare Stücke (chunks) aufgeteilt – wobei die Stückgrößen je nach Bedarf auf ein definiertes Minimum reduziert werden – und jedes Teil mit PostChunk an unseren Python-Endpunkt gesendet, komplett mit Wiederholungslogik und Timeout-Steuerung. Auf dem Weg dorthin protokolliert es jeden Schritt und jeden Fehler auf der Registerkarte „Experten“ und beendet sich bei Fehlern oder bestätigt die Fertigstellung, sobald alle Daten hochgeladen sind, und legt damit eine solide Grundlage für unsere Pipeline zur Spike-Erkennung.

Schauen wir uns das folgende Diagramm an, um jede Funktion innerhalb des MQL5-Skripts zu untersuchen.

Auf der Python-Seite besteht die Ingestion-Pipeline aus vier Schlüsselkomponenten: Der HTTP-Empfänger (upload_history) analysiert jeden JSON-Block, der vom MQL5-Skript gesendet wird, und extrahiert Symbole, Zeitstempel und Preisdaten; der Feature Enricher (prophet_delta, unterstützt durch _compile_prophet) verwaltet ein zwischengespeichertes Prophet-Modell pro Symbol, um Vorhersagedeltas im laufenden Betrieb zu generieren; der Feature Calculator (innerhalb von upload_history) berechnet eine Reihe technischer Metriken – Spike-Größe, MACD, RSI, ATR, Envelope-Bänder, Kalman-Steigung usw. – und ordnet diese mit den Kennzeichnungen „BUY/SELL/WAIT“ zu.und weist diese zu; und schließlich schreibt der Data Persister (append_rows) diese angereicherten, beschrifteten Zeilen in die Datei training_set.csv, wobei er die Datei und die Kopfzeile erstellt, falls sie noch nicht vorhanden sind.

Jedes Mal, wenn das MQL5-Skript ein Stück historischer Balken sendet, berechnet der upload_history-Handler des Python-Endpunkts alle technischen Merkmale und Beschriftungen und ruft dann append_rows auf, um diese Datensätze in training_set.csv zu schreiben – wobei die Datei und die Kopfzeile erstellt werden, falls sie noch nicht existieren. Mit jedem weiteren Upload erstellen Sie einen vollständigen Datensatz mit Zeitstempel, der für das Modelltraining bereit ist. Mit dieser training_set.csv werden wir zweifellos unser Modell zur Spike-Erkennung trainieren.


MQL5 und Python-Implementierung

In diesem Artikel gehen wir von der Verwendung eines einfachen Skripts zur Entwicklung eines vollständigen Expert Advisors (EA) über, um eine kontinuierliche Überwachung und Echtzeitkommunikation mit einem Python-Backend zu ermöglichen – etwas, das ein eigenständiges Skript nicht effizient bewältigen kann. Der Spike Detector EA auf MetaTrader 5 arbeitet in einem Client-Server-Setup, bei dem er als Client und ein Python Flask Server als Backend fungiert. Der EA beobachtet kontinuierlich die Bildung neuer Kerzen. In festgelegten Intervallen sammelt es eine konfigurierte Anzahl historischer Kerzen (OHLCV-Daten und Zeitstempel), serialisiert diese im JSON-Format und sendet sie über eine HTTP-POST-Anfrage an den Python-Server.

Das Python-Backend, das in der Regel entweder ein maschinelles Lernmodell oder eine regelbasierte Logik enthält, analysiert die eingehenden Marktdaten und liefert ein Signal: BUY, SELL, CLOSE, oder WAIT. Nach Erhalt dieser Antwort interpretiert der EA das Signal und reagiert entsprechend – er zeichnet Pfeile auf dem Chart, eröffnet Handelsgeschäfte oder schließt bestehende Positionen, je nach den Einstellungen des Nutzers. Diese Rückkopplungsschleife ermöglicht es MetaTrader, seine nativen Fähigkeiten mit externer analytischer Intelligenz in Echtzeit zu erweitern, indem die Ausführungsengine von MetaTrader 5 mit der Verarbeitungsleistung von Python kombiniert wird.

MQL5-Implementierung

Skript-Metadaten und strikter Modus

Ganz oben in Ihrer MQL5-Datei deklarieren Sie Metadaten-Eigenschaften wie #property copyright, #property link und #property version, um Informationen zur Urheberschaft und Version direkt in den kompilierten EA einzubetten. Die Aktivierung von #property strict erzwingt die strengsten Überprüfungen zur Kompilierzeit und hilft Ihnen, Syntax- oder Typfehler frühzeitig zu erkennen und sicherzustellen, dass Ihr Code die Best Practices einhält.

#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/en/users/lynnchris"
#property version   "1.0"
#property strict

Importieren der Handelsbibliothek

Indem wir <Trade.mqh> einbinden und ein CTrade-Objekt instanziieren, erhalten wir Zugriff auf die systemeigene Handelsmanagement-API von MetaTrader. Diese integrierte Bibliothek stellt Methoden für Market Orders, Stop Orders, Positionsschließungen und andere wichtige Operationen zur Verfügung, sodass Sie Handelsgeschäfte als Reaktion auf die Signale Ihres Servers programmatisch eröffnen, ändern und schließen können.

#include <Trade\Trade.mqh>
static CTrade trade;

Definieren von Eingabeparametern

Alle vom Nutzer konfigurierbaren Einstellungen – von der REST-Endpunkt-URL (InpServerURL) und der Anzahl der zu sendenden Balken (InpBufferBars) bis hin zu den Optionen für die Chart-Erstellung und den Flaggen für die Handelsausführung – werden im Voraus mit Eingabeanweisungen deklariert. Jeder Parameter enthält einen Inline-Kommentar, der seinen Zweck erklärt. Dadurch ist der EA selbstdokumentierend und ermöglicht Händlern die Feinabstimmung des Verhaltens direkt in der MetaTrader 5-GUI, ohne den Code zu berühren.

// REST endpoint & polling
input string InpServerURL      = "http://127.0.0.1:5000/analyze";
input int    InpBufferBars     = 200;
input int    MinSecsBetweenReq = 10;

// Visual & trading options
input color  ColorBuy          = clrLime;
input color  ColorSell         = clrRed;
input bool   DrawSLTPLines     = true;
input bool   EnableTrading     = true;
input double FixedLots         = 0.10;

// Debug & retry controls
input int    MaxRetry          = 3;
input bool   DebugPrintJSON    = true;
input bool   DebugPrintReply   = true;

Globale Zustandsvariablen

Sie pflegen mehrere Globals, z. B. lastBarTime und lastReqTime zur Drosselung von Anfragen, retryCount für Ihre HTTP-Wiederholungslogik und _digits plus tickSize für die präzise Preisformatierung. Eine objPrefix-Zeichenkette, die mit der ID des aktuellen Charts versehen ist, gibt allen von diesem EA erstellten Chart-Objekten (Pfeile und Linien) einen Namen, sodass sie später eindeutig identifiziert und entfernt werden können.

datetime lastBarTime = 0;
datetime lastReqTime = 0;
int      retryCount  = 0;
int      _digits;
double   tickSize;
string   objPrefix;

Initialisierung in OnInit

Wenn der EA startet, wird OnInit() einmal ausgeführt, um Eingaben zu validieren (z. B. um sicherzustellen, dass mindestens zwei Balken angefordert werden), Symboleigenschaften (SYMBOL_DIGITS und SYMBOL_POINT) zwischenzuspeichern und einen eindeutigen Objekt-Präfix zu erzeugen. Eine Startmeldung protokolliert die Anzahl der zu sendenden Balken und die URL des Zielservers und bestätigt, dass der EA bereit ist, seinen Abfragezyklus zu beginnen.

int OnInit()
{
   if(InpBufferBars < 2)
      return INIT_FAILED;

   _digits   = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
   tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
   objPrefix = StringFormat("SpikeEA_%I64d_", ChartID());

   PrintFormat("[SpikeEA] Initialized: posting %d bars → %s",
               InpBufferBars, InpServerURL);
   return INIT_SUCCEEDED;
}

Aufräumen bei OnDeinit

Beim Entfernen oder Beenden iteriert OnDeinit() rückwärts durch alle Chart-Objekte und löscht diejenigen, deren Namen mit Ihrem objPrefix beginnen. Dadurch wird sichergestellt, dass nach der Deaktivierung des EA keine verirrten Pfeile oder SL/TP-Linien auf dem Chart verbleiben und Ihr Arbeitsbereich sauber bleibt.

void OnDeinit(const int reason)
{
   for(int i = ObjectsTotal(0) - 1; i >= 0; --i)
   {
      string name = ObjectName(0, i);
      if(StringFind(name, objPrefix) == 0)
         ObjectDelete(0, name);
   }
}

Polling und Payload-Konstruktion in OnTick

Bei jedem Tick prüft der EA, ob sich ein neuer Balken gebildet hat (wenn PollOnNewBarOnly aktiviert ist) und stellt sicher, dass seit der letzten Anfrage ein Mindestintervall (MinSecsBetweenReq) verstrichen ist. Anschließend werden die letzten InpBufferBars über CopyRates gezogen, in eine Reihenfolge gebracht und BuildJSON() aufgerufen, um Closures und Zeitstempel in die JSON-Nutzdaten zu serialisieren. Wenn das Debugging aktiviert ist, wird das rohe JSON in das Expertenprotokoll gedruckt, bevor es gesendet wird.

void OnTick()
{
   datetime barTime = iTime(_Symbol, _Period, 0);
   if(barTime == lastBarTime) return;
   lastBarTime = barTime;

   if(TimeCurrent() - lastReqTime < MinSecsBetweenReq) return;

   MqlRates rates[];
   if(CopyRates(_Symbol, _Period, 0, InpBufferBars, rates) != InpBufferBars)
      return;
   ArraySetAsSeries(rates, true);

   string payload = BuildJSON(rates);
   if(DebugPrintJSON) PrintFormat("[SpikeEA] >>> %s", payload);

   SServerMsg msg;
   if(CallServer(payload, msg))
      ActOnSignal(msg);

   lastReqTime = TimeCurrent();
}

Erstellung von JSON in BuildJSON

Die Hilfsfunktion BuildJSON() nimmt das Array von MqlRates und konstruiert einen kompakten JSON-String, der den Namen Ihres Symbols, ein Array von Schlusskursen (mit der richtigen Anzahl von Dezimalstellen formatiert) und ein paralleles Array von Zeitstempeln im UNIX-Stil enthält. Die Zeichenkettenumwandlung wird angewendet, um alle Sonderzeichen im Symbolnamen zu behandeln und eine gültige JSON-Ausgabe zu gewährleisten.

string BuildJSON(const MqlRates &r[])
{
   string j = StringFormat("{\"symbol\":\"%s\",\"prices\":[", _Symbol);
   for(int i = 0; i < InpBufferBars; i++)
      j += DoubleToString(r[i].close, _digits) + (i+1<InpBufferBars?",":"");
   j += "],\"timestamps\":[";
   for(int i = 0; i < InpBufferBars; i++)
      j += IntegerToString(r[i].time) + (i+1<InpBufferBars?",":"");
   j += "]}";
   return j;
}

Server-Kommunikation im CallServer

CallServer() konvertiert die JSON-Zeichenfolge in einen uchar[]-Puffer und führt dann mit WebRequest() einen HTTP-POST an InpServerURL durch. Es behandelt Timeouts und Nicht-200-Statuscodes mit einer Wiederholungslogik bis zu MaxRetry und gibt Fehler aus, wenn Anfragen fehlschlagen. Bei Erfolg wird die Rohtextantwort erfasst – optional protokolliert – und zur Interpretation an ParseJSONLite() übergeben.

bool CallServer(const string &payload, SServerMsg &out)
{
   uchar body[];
   int len = StringToCharArray(payload, body, 0, WHOLE_ARRAY, CP_UTF8);
   ArrayResize(body, len);

   string hdr = "Content-Type: application/json\r\n";
   uchar reply[]; string resp_hdr;
   int status = WebRequest("POST", InpServerURL, hdr,
                           InpTimeoutMs, body, reply, resp_hdr);

   if(status <= 0)
   {
      PrintFormat("WebRequest error %d (retry %d/%d)",
                  GetLastError(), retryCount+1, MaxRetry);
      ResetLastError();
      if(++retryCount >= MaxRetry) retryCount = 0;
      return false;
   }
   retryCount = 0;

   string resp = CharArrayToString(reply);
   if(DebugPrintReply)
      PrintFormat("[SpikeEA] <<< HTTP %d – %s", status, resp);
   if(status != 200) return false;

   return ParseJSONLite(resp, out);
}

Leichtes JSON-Parsing in ParseJSONLite

Anstelle einer vollständigen JSON-Bibliothek verwendet ParseJSONLite() eine einfache Zeichenkettensuche (StringFind), um Schlüsselwörter wie „signal“: „BUY“ und numerische Schlüssel wie „conf“:, „sl“: und „tp“: zu erkennen.

bool ParseJSONLite(const string &txt, SServerMsg &o)
{
   o.code = SIG_WAIT; o.conf = o.sl = o.tp = 0.0;

   if(StringFind(txt, "\"signal\":\"BUY\"")   >= 0) o.code = SIG_BUY;
   if(StringFind(txt, "\"signal\":\"SELL\"")  >= 0) o.code = SIG_SELL;
   if(StringFind(txt, "\"signal\":\"CLOSE\"") >= 0) o.code = SIG_CLOSE;

   // extract numeric values
   ParseJSONDouble(txt, "\"conf\":", o.conf);
   ParseJSONDouble(txt, "\"sl\":",   o.sl);
   ParseJSONDouble(txt, "\"tp\":",   o.tp);

   return true;
}
Er extrahiert und konvertiert diese Teilstrings in die Struktur SServerMsg und setzt den Signalcode des EA, den Konfidenzwert, den Stop-Loss und das Take-Profit-Niveau.
void ParseJSONDouble(const string &txt, const string &key, double &out)
{
   int p = StringFind(txt, key);
   if(p >= 0)
      out = StringToDouble(StringSubstr(txt, p + StringLen(key)));
}

Einwirkung auf Signale in ActOnSignal

Wenn ein neues Signal eintrifft, löscht ActOnSignal() zunächst alle vorherigen Pfeile oder Linien, indem es Ihren objPrefix abgleicht. Anschließend wird ein neuer Pfeil am aktuellen Geldkurs gezeichnet – je nach Signaltyp werden Symbolcode, Farbe und Größe ausgewählt – und, falls aktiviert, werden horizontale SL- und TP-Linien mit Beschriftungen hinzugefügt. Wenn der Live-Handel aktiviert ist, wird das Handelsobjekt verwendet, um Positionen entsprechend dem Signal zu öffnen oder zu schließen: Buy(), Sell(), oder PositionClose().

void ActOnSignal(const SServerMsg &m)
{
   static ESignal last = SIG_WAIT;
   if(m.code == SIG_WAIT || m.code == last) return;
   last = m.code;

   // remove old objects
   for(int i=ObjectsTotal(0)-1;i>=0;--i)
      if(StringFind(ObjectName(0,i),objPrefix)==0)
         ObjectDelete(0,ObjectName(0,i));

   // draw arrow
   int    arrow = (m.code==SIG_BUY ? 233 : m.code==SIG_SELL ? 234 : 158);
   color  clr   = (m.code==SIG_BUY ? ColorBuy : m.code==SIG_SELL ? ColorSell : ColorClose);
   string id    = objPrefix + "Arr_" + TimeToString(TimeCurrent(),TIME_SECONDS);
   double y     = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   if(ObjectCreate(0,id,OBJ_ARROW,0,TimeCurrent(),y))
   {
      ObjectSetInteger(0,id,OBJPROP_ARROWCODE,arrow);
      ObjectSetInteger(0,id,OBJPROP_COLOR,clr);
      ObjectSetInteger(0,id,OBJPROP_WIDTH,ArrowSize);
      PlaySound("alert.wav");
   }

   // draw SL/TP lines
   if(DrawSLTPLines && m.sl>0)
      ObjectCreate(0,objPrefix+"SL_"+id,OBJ_HLINE,0,0,m.sl);
   if(DrawSLTPLines && m.tp>0)
      ObjectCreate(0,objPrefix+"TP_"+id,OBJ_HLINE,0,0,m.tp);

   // execute trade
   if(EnableTrading)
   {
      bool hasPos = PositionSelect(_Symbol);
      if(m.code==SIG_BUY  && !hasPos) trade.Buy(FixedLots,_Symbol,0,m.sl,m.tp);
      if(m.code==SIG_SELL && !hasPos) trade.Sell(FixedLots,_Symbol,0,m.sl,m.tp);
      if(m.code==SIG_CLOSE&&  hasPos) trade.PositionClose(_Symbol,SlippagePoints);
   }
}

Kompilieren und Einsetzen

Zum Abschließen fügen Sie den EA-Code in MetaEditor ein, speichern ihn unter Experten und drücken F7. Nachdem Sie „0 Fehler, 0 Warnungen“ bestätigt haben, wechseln Sie zurück zu MetaTrader 5, suchen Ihren EA im Navigator, ziehen ihn auf einen Chart und konfigurieren die Eingaben im Popup-Dialog. Auf den Registerkarten Experten und Journal werden dann Echtzeitprotokolle von JSON POSTs, geparsten Signalen, gezeichneten Objekten und Handelsausführungen angezeigt.

Python-Implementierung

Datei-Header & Anforderungen

Ganz oben in engine.py fügen wir einen Unix-Shebang (#!/usr/bin/env python3) und einen beschreibenden Kommentarblock ein, der die Fähigkeiten des Back-Ends zusammenfasst – vektorisierte History-Ingestion, CSV-Normalisierung, Prophet-Caching, Training, Backtesting und CLI-Modi – sowie den Pip-Install-Befehl für alle erforderlichen Abhängigkeiten. Diese Kopfzeile dokumentiert nicht nur auf einen Blick, was das Skript tut, sondern liefert jedem Entwickler auch die genaue Liste der Bibliotheken, die für den Betrieb des Systems erforderlich sind.

#!/usr/bin/env python3
# engine.py – Boom/Crash/Vol-75 ML back-end
# • vectorised /upload_history
# • /upload_spike_csv
# • Prophet cache (1h)
# • robust CSV writer
# • train() drops bad rows
# • SL/TP with ATR or fallback
# • backtest defaults to 30 days
# • CLI: collect · history · train · backtest · serve · info
#
# REQS: pip install numpy pandas ta prophet cmdstanpy pykalman \
#            scikit-learn flask MetaTrader5 joblib pytz

Nutzerkonfigurierbare Einstellungen

Unmittelbar nach dem Header definieren wir Konstanten für die Anmeldedaten des Terminals (TERM_PATH, LOGIN, PASSWORD, SERVER) und ein Array von SYMBOLEN, die unser System verarbeiten wird. Wir legen auch Parameter fest, die die Vorausschau für die Kennzeichnung (LOOKAHEAD, THRESH_LABEL), die Abfrageintervalle (STEP_SECONDS), die Schwellenwerte für das Öffnen und Schließen von Handelsgeschäften (THR_BC_OPEN, THR_O_OPEN, THR_O_CLOSE) und die ATR-basierten Stop-Loss/Take-Profit-Multiplikatoren (ATR_PERIOD, SL_MULT, TP_MULT, ATR_FALLBACK_P) steuern. Durch die Zentralisierung dieser Werte können die Nutzer die Risikoparameter, Datenfenster und die Symbolliste der Strategie schnell anpassen, ohne in die Codelogik einzutauchen.

TERM_PATH  = r""
LOGIN      = 123456
PASSWORD   = "passwd"
SERVER     = "DemoServer"

SYMBOLS = [
    "Boom 900 Index",  "Crash 1000 Index",
    "Volatility 75 (1s) Index"
]

LOOKAHEAD    = 10       # minutes
THRESH_LABEL = 0.0015   # 0.15 %
STEP_SECONDS = 60       # live collect interval

ATR_PERIOD     = 14
SL_MULT        = 1.0
TP_MULT        = 2.0
ATR_FALLBACK_P = 0.002

Dateipfade & CSV-Kopfzeile

Als Nächstes legen wir Konstanten für das Dateisystem fest: BASE_DIR als Stammordner für die Analyse, CSV_FILE, das auf unseren aggregierten Trainingsdatensatz verweist, MODEL_DIR für die Artefakte des Modells für jedes einzelne Symbol und GLOBAL_PKL für das übergreifende Modell. Wir definieren auch CSV_HEADER, eine feste Liste von Spaltennamen, die sicherstellen, dass jede geschriebene Zeile dieselben 12 Felder enthält. In diesem Abschnitt wird der Speicherort der Daten standardisiert und die Konsistenz der gespeicherten CSV-Dateien sichergestellt, was für eine nahtlose nachfolgende Training und Analyse entscheidend ist.

BASE_DIR   = r"C:\Analysis EA"
CSV_FILE   = rf"{BASE_DIR}\training_set.csv"
MODEL_DIR  = rf"{BASE_DIR}\models"
GLOBAL_PKL = rf"{MODEL_DIR}\_global.pkl"

CSV_HEADER = [
    "timestamp","symbol","price","spike_mag","macd","rsi",
    "atr","slope","env_low","env_up","delta","label"
]

Einrichtung von Importen und Protokollierung

Wir importieren Standardbibliotheken (os, sys, time, threading, etc.), datenwissenschaftliche Pakete (numpy, pandas, ta, joblib), die Prophet- und Kalman-Filter-Module, Flask für unsere API und den MetaTrader 5 Python-Wrapper. Warnungen werden aus Gründen der Sauberkeit unterdrückt, und die Protokollierung ist so konfiguriert, dass Zeitstempel, Protokollstufen und Meldungen in einem für Menschen lesbaren Format ausgegeben werden. Schließlich stellen wir sicher, dass das Modellverzeichnis existiert und ändern das Arbeitsverzeichnis in BASE_DIRum, damit alle relativen Dateioperationen an einem bekannten Ort stattfinden.

import os,sys,time,logging,warnings,argparse,threading,io
import datetime as dt
from pathlib import Path
import numpy as np, pandas as pd, ta, joblib, pytz
from flask import Flask, request, jsonify, abort
from prophet import Prophet
from pykalman import KalmanFilter
import MetaTrader5 as mt5

warnings.filterwarnings("ignore")
logging.basicConfig(level=logging.INFO,
                    format="%(asctime)s %(levelname)-7s %(message)s",
                    datefmt="%H:%M:%S")
Path(MODEL_DIR).mkdir(parents=True, exist_ok=True)
os.chdir(BASE_DIR)

MetaTrader 5 Initialisierungshilfen

Die Funktion init_mt5() initialisiert die MetaTrader 5-Verbindung auf sichere Weise, indem sie zunächst einen Standardaufruf versucht und bei Bedarf auf die Anmeldeinformationen zurückgreift; ein Fehlschlag löst einen sauberen Abbruch mit einer Fehlermeldung aus. sicher_symbol(sym) wickelt einfach mt5.symbol_select um sicherzustellen, dass jedes Instrument aktiv ist, bevor Datenanforderungen gestellt werden. Eine Threading-Sperre (_mt5_lock) schützt alle Multi-Thread-Aufrufe an MetaTrader 5, um die Thread-Sicherheit zu gewährleisten, wenn unser Server Hintergrundaufgaben ausführt.

_mt5_lock = threading.Lock()
def init_mt5():
    if mt5.initialize(): return
    if not mt5.initialize(path=TERM_PATH, login=LOGIN,
                          password=PASSWORD, server=SERVER):
        sys.exit(f"MT5 init failed {mt5.last_error()}")

def ensure_symbol(sym):
    return mt5.symbol_select(sym, True)

Prophet Cache & Vorhersage Delta

Um zu vermeiden, dass das Prophet-Modell bei jeder Anfrage neu kompiliert wird, führen wir ein thread-sicheres Wörterbuch _PROP, das Symbole entweder auf None (Kompilierung ausstehend) oder auf ein Tupel (Modell, Zeitstempel) abbildet. _compile_prophet(df, sym) trainiert ein neues Prophet-Modell auf historischen Daten und zeichnet die Zeit auf. prophet_delta(preise, zeiten, sym) prüft den Cache: wenn er fehlt oder veraltet ist (mehr als eine Stunde), wird eine Hintergrundkompilierung gestartet; wenn er bereits verfügbar ist, wird eine Sekunde vorausgesagt und das vorhergesagte Delta zurückgegeben. Dieses Design sorgt dafür, dass die Vorhersage schnell reagiert und eingehende Anfragen nicht blockiert werden.

_PROP_LOCK = threading.Lock()
_PROP = {}  # sym -> (model, timestamp) or None

def _compile_prophet(df, sym):
    mdl = Prophet(daily_seasonality=False, weekly_seasonality=False)
    mdl.fit(df)
    with _PROP_LOCK:
        _PROP[sym] = (mdl, time.time())

def prophet_delta(prices, times, sym):
    if len(prices) < 20: return 0.0
    with _PROP_LOCK:
        entry = _PROP.get(sym)
        if entry is None:
            _PROP[sym] = None
            df = pd.DataFrame({"ds": pd.to_datetime(times, unit='s'),
                               "y": prices})
            threading.Thread(target=_compile_prophet, args=(df, sym), daemon=True).start()
            return 0.0
        mdl, ts = entry
    if time.time() - ts > 3600:
        with _PROP_LOCK: _PROP[sym] = None
        return 0.0
    fut = mdl.make_future_dataframe(periods=1, freq='s')
    return float(mdl.predict(fut).iloc[-1]["yhat"] - prices[-1])

Hilfsfunktionen für Merkmale

Wir definieren eine Reihe von kleinen Funktionen – z_spike, macd_div, rsi_val, combo_spike und andere – die einzelne technische Signale berechnen, z. B. Standard-Score-Spikes, MACD-Divergenz, RSI und einen kombinierten „Spike-Score“. Jeder Helfer prüft vor der Berechnung, ob genügend Daten vorhanden sind, und gibt einen Standardwert zurück, wenn die Daten nicht ausreichen. Indem wir diese Berechnungen isolieren, halten wir unsere Hauptlogik für die Aufnahme sauber und erleichtern die Einheitstests der einzelnen Indikatoren.

def z_spike(prices, win=20):
    if len(prices) < win: return False, 0.0
    r = np.diff(prices[-win:])
    z = (r[-1] - r.mean())/(r.std()+1e-6)
    return abs(z) > 2.5, float(z)

def macd_div(prices):
    if len(prices) < 35: return 0.0
    return float(ta.trend.macd_diff(pd.Series(prices)).iloc[-1])

def rsi_val(prices, l=14):
    if len(prices) < l+1: return 50.0
    return float(ta.momentum.rsi(pd.Series(prices), l).iloc[-1])

def combo_spike(prices):
    _, z = z_spike(prices)
    m = macd_div(prices)
    v = prices[-1] - prices[-4] if len(prices) >= 4 else 0.0
    s = abs(z) + abs(m) + abs(v)/(np.std(prices[-20:])+1e-6)
    return s > 3.0, s

CSV Append Helper & gen_row

append_rows(rows) nimmt eine Liste von 12-Element-Listen und schreibt sie in training_set.csv, wobei die Datei beim ersten Schreiben mit Kopfzeilen erstellt und danach angehängt wird. gen_row(i, closes, times, sym, highs=None, lows=None) erstellt eine einzelne Trainingszeile: Sie berechnet die Merkmale aus der Kurshistorie bis zum Index i (einschließlich ATR und Envelope-Bänder, wenn High/Low-Arrays angegeben sind), ruft prophet_delta für die Vorhersage auf und weist eine der Kennzeichnungen „BUY/SELL/WAIT“ auf der Grundlage der zukünftigen Preisbewegung zu. Durch die Trennung von Zeilengenerierung und Ingestion verwenden wir erneut gen_row sowohl bei Live- als auch bei historischen Importen.

def append_rows(rows):
    if not rows: return
    pd.DataFrame(rows, columns=CSV_HEADER)\
      .to_csv(CSV_FILE, mode="a", index=False,
              header=not Path(CSV_FILE).exists())

def gen_row(i, closes, times, sym, highs=None, lows=None):
    if i < LOOKAHEAD or i+LOOKAHEAD >= len(closes): return None
    seq = closes[:i]
    _, mag = combo_spike(seq)
    atr = ta.volatility.average_true_range(pd.Series(highs[:i+1]),
                                           pd.Series(lows[:i+1]),
                                           pd.Series(seq)).iloc[-1] if highs else 0.0
    row = [
        times[i], sym, closes[i], mag,
        macd_div(seq), rsi_val(seq),
        atr, 0.0, 0.0, 0.0,
        prophet_delta(seq, times[:i], sym)
    ]
    ch = (closes[i+LOOKAHEAD] - closes[i]) / closes[i]
    row.append("BUY" if ch > THRESH_LABEL else "SELL" if ch < -THRESH_LABEL else "WAIT")
    return row

Schleife sammeln (Live-Daten)

In collect_loop() stellen wir sicher, dass die CSV-Datei existiert, und starten dann eine Endlosschleife, die für jedes Symbol die letzten LOOKAHEAD+1-Balken über mt5.copy_rates_from_pos anfordert, Duplikate nach Zeitstempel überspringt und gen_row aufruft, um eine neue beschriftete Beobachtung zu erstellen und anzuhängen. Ein Sleep von STEP_SECONDS erzwingt eine kontrollierte Abfragerate. Diese Live-Daten-Schleife erweitert unsere Trainingsmenge kontinuierlich um neue Beobachtungen, bis der Nutzer sie unterbricht.

def collect_loop():
    if not Path(CSV_FILE).exists(): append_rows([])
    last = {}
    print("Collecting… CTRL-C to stop")
    init_mt5()
    while True:
        for sym in SYMBOLS:
            if not ensure_symbol(sym): continue
            bars = mt5.copy_rates_from_pos(sym, mt5.TIMEFRAME_M1, 0, LOOKAHEAD+1)
            if bars is None or len(bars) < LOOKAHEAD+1: continue
            if last.get(sym) == bars[-1]['time']: continue
            last[sym] = bars[-1]['time']
            closes = bars['close'].tolist()
            times  = bars['time'].tolist()
            row = gen_row(len(closes)-LOOKAHEAD-1, closes, times, sym)
            if row: append_rows([row])
        time.sleep(STEP_SECONDS)

Historie importieren (MetaTrader 5 & Datei)

history_from_mt5(sym, start, end) und history_from_file(sym, path) ermöglichen es, die CSV-Datei entweder aus der gespeicherten Historie von MetaTrader 5 oder aus einer lokalen Datei auszufüllen. Beide Funktionen durchlaufen eine Schleife durch jeden mit einem Zeitstempel versehenen Balken, rufen gen_row auf, um Merkmale und eine Beschriftung zu erzeugen, stapeln die Zeilen in Stücken (z. B. 5.000 auf einmal) und fügen sie mit append_rows an. Der Wrapper history_cli(args) analysiert Kommandozeilenargumente (--days, --from, --to oder --file), um die Aufnahme des gesamten Datensatzes für bestimmte Symbole und Datumsbereiche zu automatisieren.

def history_from_mt5(sym, start, end):
    init_mt5()
    r = mt5.copy_rates_range(sym, mt5.TIMEFRAME_M1,
                              start.replace(tzinfo=UTC),
                              end.replace(tzinfo=UTC))
    if r is None or len(r)==0: return
    closes, times = r['close'].tolist(), r['time'].tolist()
    highs, lows   = r['high'].tolist(), r['low'].tolist()
    rows = [gen_row(i, closes, times, sym, highs, lows)
            for i in range(len(closes)-LOOKAHEAD) if gen_row(i, closes, times, sym, highs, lows)]
    append_rows([rw for rw in rows if rw])
    print(sym, "imported", len(rows), "rows")

Training der Modelle

train_models() liest training_set.csv ein, wandelt die Merkmalsspalten in numerische Werte um (wobei alle fehlerhaften Zeilen entfernt werden) und durchläuft dann die Teilmenge jedes Symbols: Wenn mindestens 400 Zeilen vorhanden sind, wird eine scikit‑learn-Pipeline erstellt (Standard-Skalierung + Gradient Boosting), an die gekennzeichneten Daten angepasst und das Modell als .pkl gespeichert. Es trainiert und speichert auch ein globales Modell für alle Symbole. Das Ergebnis ist ein Verzeichnis mit einsatzbereiten Klassifikatoren.

def build_pipe(X, y):
    pipe = Pipeline([
        ("sc", StandardScaler()),
        ("gb", GradientBoostingClassifier(n_estimators=400,
                                          learning_rate=0.05,
                                          max_depth=3,
                                          random_state=42))
    ])
    return pipe.fit(X, y)

def train_models():
    df = pd.read_csv(CSV_FILE)
    df = df.dropna(subset=FEATURES)
    for sym in SYMBOLS:
        d = df[df.symbol == sym]
        if len(d) < 400: continue
        model = build_pipe(d[FEATURES], d.label.map({"WAIT":0,"BUY":1,"SELL":2}))
        joblib.dump(model, Path(MODEL_DIR)/f"{sym.replace(' ','_')}.pkl")
    global_model = build_pipe(df[FEATURES], df.label.map({"WAIT":0,"BUY":1,"SELL":2}))
    joblib.dump(global_model, GLOBAL_PKL)

Flask-Server-Endpunkte

Wir erstellen eine Flask-App mit drei primären Routen:

/upload_history parst JSON-Balkenstücke, berechnet die gleichen Merkmale wie gen_row, beschriftet jede Zeile und ruft append_rows auf.

/upload_spike_csv akzeptiert rohe EA-Protokolle (entweder CSV-Text oder JSON-Arrays), überträgt sie in unser 12-Spalten-Format und hängt sie an.

/analyze lädt das entsprechende Modell über load_model(), berechnet Live-Merkmale aus geposteten Kursen und Zeitstempeln, prognostiziert Klassenwahrscheinlichkeiten, wendet Open/Close-Schwellenwerte an und gibt ein JSON-Objekt zurück, das das Signal, die Konfidenz, SL/TP und die Positionsstärke enthält.

app = Flask(__name__)
app.config["MAX_CONTENT_LENGTH"] = 32*1024*1024

@app.route("/upload_history", methods=["POST"])
def upload_history():
    j = request.get_json(force=True)
    close, ts = np.array(j["close"]), np.array(j["time"],dtype=int)
    high = np.array(j.get("high", close))
    low  = np.array(j.get("low", close))
    df = pd.DataFrame({"timestamp": ts, "price": close})
    # compute features as in gen_row…
    append_rows(df.assign(symbol=j["symbol"]).values.tolist())
    return jsonify(status="ok", rows_written=len(df))

@app.route("/upload_spike_csv", methods=["POST"])
def upload_spike_csv():
    j = request.get_json(force=True)
    df_ea = pd.read_csv(io.StringIO(j.get("csv","")), sep=",")
    # map EA columns → CSV_HEADER
    append_rows(mapped_rows)
    return jsonify(status="ok", rows_written=len(mapped_rows))

@app.route("/analyze", methods=["POST"])
def api_analyze():
    j = request.get_json(force=True)
    mdl = load_model(j["symbol"])
    feats = [...]  # compute from j["prices"], j["timestamps"]
    proba = mdl.predict_proba([feats])[0]
    signal = decide_open(proba[1], proba[2], j["symbol"])
    # build sl, tp, manage _trades…
    return jsonify(signal=signal, sl=sl, tp=tp, strength=max(proba))

Diese Endpunkte ermöglichen Ingestion, Backfill und Entscheidungsfindung in Echtzeit für den MQL5 EA.

Backtest & Info Utilities

backtest_one(sym, df) verwendet die Offline-Feature-Helfer und die Modellinferenzlogik, um Handelsgeschäfte über den historischen DataFrame df zu simulieren und die P&L aufzuzeichnen, wenn Stop-Loss-, Take-Profit- oder Early-Close-Bedingungen erfüllt sind. backtest_cli(args) aggregiert die Ergebnisse über alle Symbole und druckt eine Zusammenfassung der P&L. Die Funktion info() meldet einfach die Anzahl der CSV-Zeilen, die Verteilungen der Bezeichnungen und die Anzahl der Merkmale jedes Modells – praktisch für eine schnelle Überprüfung des Datenzustands.

def backtest_one(sym, df):
    mdl = load_model(sym)
    for i in range(len(df)):
        feats = [...]  # offline feature calcs
        pr = mdl.predict_proba([feats])[0]
        # open/close logic identical to /analyze
    return trades

def info():
    df = pd.read_csv(CSV_FILE)
    print("Rows:", len(df), "Labels:", df.label.value_counts())
    for pkl in Path(MODEL_DIR).glob("*.pkl"):
        mdl = joblib.load(pkl)
        print(pkl.name, "features", mdl.named_steps["sc"].n_features_in_)

Befehlszeilenschnittstelle

Schließlich definiert der Block if __name__ == "__main__": eine „argparse-CLI“ mit sechs Unterbefehlen – collect, history, train, backtest, serve und info – die jeweils die entsprechende Funktion aufrufen. Dieses Muster liefert ein einziges, zusammenhängendes Skript, in dem Sie zum Beispiel python engine.py history --days 180 ausführen können, um sechs Monate an Daten zurückzufüllen, oder python engine.py serve, um die Live-API für Ihren EA zu starten.

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    subs = parser.add_subparsers(dest="mode", required=True)
    subs.add_parser("collect")
    subs.add_parser("history")
    subs.add_parser("train")
    subs.add_parser("backtest")
    subs.add_parser("serve")
    subs.add_parser("info")
    args = parser.parse_args()
    if args.mode == "collect": init_mt5(); collect_loop()
    elif args.mode == "history": history_cli(args)
    elif args.mode == "train": train_models()
    elif args.mode == "backtest": backtest_cli(args)
    elif args.mode == "serve": init_mt5(); app.run("0.0.0.0", 5000, threaded=True)
    elif args.mode == "info": info()


Modelltraining in Python

Da wir nun genügend historische Daten in unserer CSV-Datei gesammelt haben (die über unsere MQL5-Historienabfrage-Routine und den Python-Empfänger gefüllt wurde), ist der nächste Schritt das Modelltraining. Stellen Sie sicher, dass wir Daten für alle in unseren MQL5- und Python-Skripten definierten Symbole aufgenommen haben. Sobald die Einnahme abgeschlossen ist, können wir fortfahren:
  • Trainieren der Machine-Learning-Modelle
  • Backtesting ihrer Leistung über einen historischen Zeitraum
  • Einsatz der resultierenden Modelle für Live-Inferenz

In diesem Schritt trainieren wir einen Gradient-Boosting-Klassifikator für jedes Symbol (und ein globales Modell), um vorherzusagen, ob der Preis nach unserem Vorausschauzeitraum BUY, SELL, oder WAIT wird. Gradient Boosting baut nacheinander ein Ensemble von Entscheidungsbäumen auf, wobei jeder neue Baum die Fehler der vorangegangenen korrigiert. Dadurch ist es robust gegenüber verrauschten Finanzdaten und in der Lage, nichtlineare Muster in unserer Merkmalsgruppe zu erfassen. Wir verpacken es in eine scikit‑learn-Pipeline mit einem StandardScaler zur Normalisierung der Merkmale vor dem Training.

# 3) TRAIN MODELS
def build_pipe(X, y):
    """
    Construct and fit a pipeline: StandardScaler → GradientBoostingClassifier.
    """
    pipe = Pipeline([
        ("sc", StandardScaler()),
        ("gb", GradientBoostingClassifier(
            n_estimators=400,      # number of boosting rounds
            learning_rate=0.05,    # shrinkage factor per tree
            max_depth=3,           # depth of each tree
            random_state=42        # reproducibility
        ))
    ])
    pipe.fit(X, y)
    return pipe

def train_models():
    """
    Load the CSV, clean it, train per-symbol and global Gradient Boosting models, and save to disk.
    """
    if not Path(CSV_FILE).exists():
        sys.exit("No training_set.csv")

    # Read and sanitize
    df = pd.read_csv(CSV_FILE)
    if "symbol" not in df.columns:
        sys.exit("CSV missing 'symbol' column")

    # Ensure numeric features
    for col in FEATURES:
        df[col] = pd.to_numeric(df[col], errors="coerce")
    bad = df[FEATURES].isna().any(axis=1).sum()
    if bad:
        print(f"Discarding {bad} malformed rows")
        df = df.dropna(subset=FEATURES)

    # Train a Gradient Boosting model for each symbol
    for sym in SYMBOLS:
        d = df[df.symbol == sym]
        if len(d) < 400:
            print("Skip", sym, "(few rows)")
            continue
        model = build_pipe(
            d[FEATURES],
            d.label.map({"WAIT": 0, "BUY": 1, "SELL": 2})
        )
        joblib.dump(model, Path(MODEL_DIR) / f"{sym.replace(' ', '_')}.pkl")
        print("model", sym, "saved")

    # Train and save a global Gradient Boosting model
    global_model = build_pipe(
        df[FEATURES],
        df.label.map({"WAIT": 0, "BUY": 1, "SELL": 2})
    )
    joblib.dump(global_model, GLOBAL_PKL)
    print("global model saved")
Rufen wir auch das Training auf:
python engine.py train
Nach dem Aufrufen unserer Trainingsroutine haben wir diese Konsolenausgabe beobachtet:
C:\Users\hp\Pictures\Saved Pictures\Analysis EA>python engine.py train
Discarding 1152650 malformed rows
model Boom 900 Index saved
model Boom 1000 Index saved
model Boom 500 Index saved
model Crash 500 Index saved
model Boom 300 Index saved
....................................
....................................
All models saved

Sobald das Training abgeschlossen ist und alle Modelle gespeichert wurden – was angesichts der Datenmenge eine Weile dauern kann – sind wir bereit für die nächsten Schritte. Wir können die neu trainierten Modelle entweder anhand historischer Daten einem Backtest unterziehen oder direkt mit dem Einsatz beginnen. In meinem Fall bin ich direkt zur Modellbereitstellung übergegangen, die ich im nächsten Abschnitt behandeln werde.


Modellbereitstellung und Echtzeit-Interferenz

Um den Trainingsprozess zu beenden, muss nur Strg+C. Dann starten Sie den Echtzeit-Inferenzserver mit:
python engine.py serve

Dieser Befehl setzt die trainierten Modelle ein und beginnt mit der Ausgabe von Live-Handelssignalen.

In MetaTrader 5 fügen Sie den Expert Advisor jedem Symbol zu, für das Sie ein Modell trainiert haben. Dann, in MetaTrader 5 Extras → Optionen → Expert Advisors aktivieren Sie WebRequest zulassen für die aufgelistete URL, und fügen Sie die Adresse Ihres Servers zur Whitelist hinzu.


Der HTTP-Statuscode 200 bedeutet „OK“ – die Anfrage wurde empfangen, verstanden und erfolgreich verarbeitet.

Während unserer Live-Server-Tests erreichte jede EA-Instanz erfolgreich das Python-Backend (HTTP 200) und gab ihre Handelsempfehlung in weniger als 50 ms zurück. Das sagen uns die Protokolle:

Crash 1000 Index (M1)

Um 00:31:59.717 Uhr meldete das Modell eine KAUF-Wahrscheinlichkeit von 0 % und eine VERKAUFS-Wahrscheinlichkeit von 2,6 %, was eine kombinierte Konfidenz (Stärke) von nur 3 % ergibt. Da keiner der beiden Schwellenwerte überschritten wurde, entschied sich der EA korrekt für das WAIT-Signal.

Boom 1000 Index (M1)

Nur 37 ms später (um 00:31:59.754) ergab das Modell dieses Symbols eine KAUF-Wahrscheinlichkeit von 99,4 % und einen VERKAUF von 0 %. Dieses hohe Vertrauen löste sofort ein OPEN BUY-Signal aus. 

Diese Protokolle bestätigen, dass unsere Bereitstellungs-Pipeline durchgängig funktioniert.

2025.07.30 00:31:59.717 Spike DETECTOR (Crash 1000 Index,M1)    [SpikeEA] <<< HTTP 200 – {"Pbuy":0.0,"Psell":0.026,"scale_in":null
,"side":"NONE","signal":"WAIT","strength":0.03
2025.07.30 00:31:59.754 Spike DETECTOR (Boom 1000 Index,M1)     [SpikeEA] <<< HTTP 200 – {"Pbuy":0.994,"Psell":0.0,"scale_in":null
,"side":"BUY","signal":"OPEN"
, "strength":0.99

Hier ist ein früherer Testlauf des Systems. Gelegentlich signalisiert der EA einen Einstieg – diese „OPEN“-Anweisungen erscheinen in den Protokollen des MetaTrader 5, obwohl kein Pfeil auf dem Chart angezeigt wird. Ob eine visuelle Markierung erscheint, hängt von der Stärke des Signals ab.

MetaTrader 5 Protokolle

2025.07.25 19:55:01.445 Spike DETECTOR (Boom 1000 Index,M1)     [SpikeEA] <<< HTTP 200 – {"Pbuy":0.999,"Psell":0.0,"scale_in":null
,"side":"BUY","signal":"OPEN","strength":1.0

MetaTrader 5 Chart


Schlussfolgerung

Durch die Verbindung von MQL5 und Python haben wir einen leistungsstarken, flexiblen Handelsrahmen geschaffen, der das Beste aus beiden Welten nutzt. Auf der MQL5-Seite erfasst unser EA nahtlos Spikes, MACD-Divergenz, RSI, ATR, Kalman-gefilterte Steigungen und Prophet-Deltas und leitet diese Metriken dann direkt in Python weiter. Auf der Python-Seite übernimmt ein einziges engine.py-Skript (mit den Befehlen collect, history, train, backtest, serve) die schwere Arbeit des Modelltrainings und des Live-Serving. In unserem Setup stützten wir uns auf den EA von MQL5, um alle erforderlichen Merkmalsdaten zu liefern, sodass wir ihn nur einmal ausführen mussten:

python engine.py train
python engine.py serve

Wir überspringen „collect“ und „history“, weil unser EA bereits den gesamten Datensatz für uns verwaltet und bereitstellt.

Das Ergebnis? Unsere Gradient-Boosting-Modelle liefern innerhalb weniger Augenblicke nach dem Aufschlagen von Serve Echtzeit-Kauf-/Verkaufs-/WAIT-Signale in weniger als 50 ms pro Balken an den MetaTrader 5 zurück – bereit, von der Auftragslogik unseres EAs verarbeitet zu werden. Egal, ob Sie gerade erst mit der reichhaltigen Bibliothek der MQL5-Website und den Community-Beispielen beginnen oder ob Sie ein erfahrener Quant sind, der neue Feature-Generatoren oder Algorithmen einfügen möchte, diese End-to-End-Pipeline lässt sich mühelos über Symbole und Strategien hinweg skalieren.

Vielen Dank an die MQL5-Gemeinschaft und die Fülle an Codebeispielen und Foreneinblicken, die auf mql5.com zur Verfügung stehen, Ihre Ressourcen haben diese Integration unkompliziert gemacht. Ich möchte jeden ermutigen, weiter zu forschen: Hyperparameter zu optimieren, neue Indikatoren hinzuzufügen oder sogar Ihren Python-Server für die Produktion zu containerisieren. Am wichtigsten ist jedoch, dass Sie Ihre Erkenntnisse mit der Community teilen, damit wir alle gemeinsam den datengesteuerten algorithmischen Handel weiter vorantreiben können.




   
Chart Projector
Analytical Comment
Analytics Master
Analytics Forecaster 
Volatility Navigator
Der Mean Reversion Signal Reaper
Signal Pulse 
Metrics Board 
External Flow
VWAP
Heikin Ashi   FibVWAP  
RSI DIVERGENCE
Parabolic Stop and Reverse (PSAR) 
Quarters Drawer Script
Intrusion Detector
TrendLoom Tool  Quarters Board 
ZigZag Analyzer  Correlation Pathfinder  Market Structure Flip Detector Tool
Correlation Dashboard   Währungsstärkemesser 
PAQ-Analyse-Tool 
Dual EMA Fractal Breaker
Pin-Bar, Engulfing und RSI-Divergenz
Liquidity Sweep Werkzeug zum Ausbrechen aus dem Eröffnungsbereich Ausleger und Crash Interceptor CCI Zero-Line EA
Erkennen von Kerzenmuster Kerzenerkennung mit Ta-Lib Candle Range Tool MetaTrader 5 Datenabruf Modelltraining und -einführung  

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

Beigefügte Dateien |
Spike_DETECTOR.mq5 (20.63 KB)
engine.py (23.34 KB)
Letzte Kommentare | Zur Diskussion im Händlerforum (1)
Helga Gustana Argita
Helga Gustana Argita | 12 Aug. 2025 in 13:56
das ist erstaunlich
aber warum sehe ich nicht, dass Objekte erstellt werden, wenn es ein Signal gibt, und nicht einmal einen Handel machen
Selbstoptimierende Expert Advisors in MQL5 (Teil 10): Matrix-Faktorisierung Selbstoptimierende Expert Advisors in MQL5 (Teil 10): Matrix-Faktorisierung
Die Faktorisierung ist ein mathematischer Prozess, der dazu dient, Erkenntnisse über die Eigenschaften von Daten zu gewinnen. Wenn wir die Faktorisierung auf große Mengen von Marktdaten anwenden – die in Zeilen und Spalten organisiert sind – können wir Muster und Merkmale des Marktes aufdecken. Die Faktorisierung ist ein mächtiges Werkzeug, und dieser Artikel zeigt Ihnen, wie Sie es im MetaTrader 5-Terminal über die MQL5-API nutzen können, um tiefere Einblicke in Ihre Marktdaten zu gewinnen.
MQL5-Handelswerkzeuge (Teil 7): Informatives Dashboard für Multi-Symbol-Positionen und Kontoüberwachung MQL5-Handelswerkzeuge (Teil 7): Informatives Dashboard für Multi-Symbol-Positionen und Kontoüberwachung
In diesem Artikel entwickeln wir ein Informations-Dashboard in MQL5 zur Überwachung von Multi-Symbol-Positionen und Kontometriken wie Kontostand, Kapital und freie Marge. Wir implementieren ein sortierbares Raster mit Echtzeit-Updates, CSV-Export und einen leuchtenden Header-Effekt, um die Nutzerfreundlichkeit und den visuellen Reiz zu verbessern.
Vom Neuling zum Experten: Animierte Nachrichtenschlagzeile mit MQL5 (VII) – Post-Impact-Strategie für den Nachrichtenhandel Vom Neuling zum Experten: Animierte Nachrichtenschlagzeile mit MQL5 (VII) – Post-Impact-Strategie für den Nachrichtenhandel
In den ersten Minuten nach der Veröffentlichung einer wichtigen Wirtschaftsnachricht ist das Risiko eines „Whipsaw“ extrem hoch. In diesem kurzen Zeitfenster können Kursbewegungen unberechenbar und volatil sein und oft beide Seiten von schwebenden Aufträgen auslösen. Kurz nach der Veröffentlichung – in der Regel innerhalb einer Minute – stabilisiert sich der Markt in der Regel und nimmt den vorherrschenden Trend wieder auf oder korrigiert ihn mit der üblichen Volatilität. In diesem Abschnitt werden wir einen alternativen Ansatz für den Nachrichtenhandel untersuchen, um seine Wirksamkeit als wertvolle Ergänzung zum Instrumentarium eines Händlers zu bewerten. Lesen Sie weiter, um weitere Einblicke und Details zu dieser Diskussion zu erhalten.
Klassische Strategien neu interpretieren (Teil 14): Analyse mehrerer Strategien Klassische Strategien neu interpretieren (Teil 14): Analyse mehrerer Strategien
In diesem Artikel setzen wir unsere Erforschung der Erstellung eines Ensembles von Handelsstrategien und der Verwendung des MT5 genetischen Optimierers zur Abstimmung der Strategieparameter fort. Heute haben wir die Daten in Python analysiert. Dabei hat sich gezeigt, dass unser Modell besser vorhersagen kann, welche Strategie besser abschneiden wird, und eine höhere Genauigkeit erreicht als die direkte Vorhersage der Marktrenditen. Als wir unsere Anwendung jedoch mit ihren statistischen Modellen testeten, fielen unsere Leistungswerte drastisch ab. In der Folge stellten wir fest, dass der genetische Optimierer leider stark korrelierte Strategien bevorzugte, was uns dazu veranlasste, unsere Methode zu überarbeiten, um die Stimmgewichte fest zu halten und die Optimierung stattdessen auf Indikatoreinstellungen zu konzentrieren.