Vom Neuling zum Experten: Detaillierte Handelsberichte mit Reporting EA beherrschen
Inhalt:
- Einführung
- Überblick über das Update von Reporting EA
- Aktualisierung des Codes von Reporting EA
- Aktualisieren des Python-Skripts reports_processor
- Prüfung
- Schlussfolgerung
- Wichtige Lektionen
- Anlagen
Einführung
Die heutige Diskussion befasst sich mit den Herausforderungen, die mit der Bereitstellung von Handelsberichten in MetaTrader 5 verbunden sind. In unserer letzten Veröffentlichung haben wir den allgemeinen Arbeitsablauf und die Anforderungen für ein effizientes Funktionieren des Systems beschrieben. Mit dem Reporting EA haben wir ein Tool eingeführt, das Handelsberichte im PDF-Format generiert und liefert, und zwar in einer Frequenz, die vom Nutzer angepasst werden kann.
Mit dieser Fortsetzung wollen wir auf dieser Grundlage aufbauen, indem wir sowohl die Tiefe der in den Berichten enthaltenen Informationen als auch die Effizienz ihrer Bereitstellung verbessern. Diese Verbesserung wird durch die Integration von MQL5 mit leistungsstarken Python-Bibliotheken ermöglicht, die reichhaltigere Ausgaben und eine reibungslosere Automatisierung ermöglichen. Dabei werden wir wertvolle Erkenntnisse darüber gewinnen, wie die sprachübergreifende Integration genutzt werden kann, um robuste und nahtlose Handelslösungen zu schaffen.
Es lohnt sich, dieses Thema weiterzuverfolgen, wenn Sie erkannt haben, wie stark sich eine detaillierte Berichterstattung auf die Denkweise und den Entscheidungsprozess eines Händlers auswirken kann. In der vorherigen Version enthielt die generierte PDF-Datei nur vier Datenpunkte – eine klare Einschränkung und ein deutlicher Hinweis auf die Notwendigkeit einer umfassenderen Berichterstattung. Künftig werden unsere Berichte erweiterte Metriken, Visualisierungen, Diagramme und mehrere analytische Komponenten enthalten, wie in Absatz 2 des früheren Artikels über das Verständnis der Berichtsfunktionen beschrieben.
Letztlich geht es darum, ein flexibles und informationsreiches Reporting-Tool bereitzustellen, das jeder Analyst oder Händler an seine individuellen Handelsstrategien und Arbeitsabläufe anpassen kann.

Abbildung 1: Bildschirmfoto des Handelsberichts, der von der frühen Version des Reporting EA erstellt wurde
Das obige Bild wurde dem PDF-Bericht entnommen, der von der ersten Version unseres Skripts reports_processor.py erstellt wurde. Der entsprechende Codeschnipsel ist unten zu sehen. Heute konzentrieren wir uns auf die Verfeinerung dieses Codes, damit er detailliertere und aufschlussreichere Berichte liefert.
In dieser Diskussion werden wir zusätzliche Möglichkeiten erkunden, die Python in den Berichtsprozess einbringen kann, und zeigen, wie die Interaktion zwischen MQL5 und Python für mehr Effizienz und Flexibilität optimiert werden kann.
def generate_pdf(report, output_path): pdf = FPDF() pdf.add_page() pdf.set_font("Arial", size=12) pdf.cell(0, 10, f"Report Date: {report['date']}", ln=True) pdf.cell(0, 10, f"Total Trades: {report['trade_count']}", ln=True) pdf.cell(0, 10, f"Net Profit: ${report['net_profit']:.2f}", ln=True) pdf.cell(0, 10, f"Top Symbol: {report['top_symbol']}", ln=True) pdf.output(output_path)
Vergleicht man die in Abbildung 1 und Abbildung 2 dargestellten Berichterstattungsmerkmale, so wird deutlich, dass noch erheblicher Spielraum für Verbesserungen besteht. Unser Ziel ist es nicht, bestehende Lösungen neu zu erfinden, sondern eine anpassbare und flexible Alternative für die Erstellung von Handelsberichten anzubieten.
Das Bild aus dem Berichtsbereich des MetaTrader 5-Terminals veranschaulicht die aktuellen Funktionen, die wir als Grundlage für die geplanten Verbesserungen verwenden werden.

Abbildung 2: Merkmale der MQL5-Handelsberichte
Überblick über das Update von Reporting EA
Abbildung 3 (Flussdiagramm) zeigt den Verarbeitungsprozess, der durch das Update von Reporting EA implementiert wird. Der EA exportiert eine datierte CSV-Datei des Handelsverlaufs und startet den Python-Berichtsgenerator, der die CSV-Datei verarbeitet, um Analysen zu berechnen, Diagramme zu erstellen und den endgültigen Bericht (PDF) sowie ein kompaktes JSON-Ergebnis mit pdf_path und email_sent-Status zu erstellen. Der EA fragt dieses JSON ab, liest und parst es, validiert die gemeldeten Ausgabepfade und Dateigrößen und sendet bei erfolgreicher Validierung Benachrichtigungen und archiviert oder öffnet den Bericht optional, um den automatischen Berichtszyklus abzuschließen.

Abbildung 3: Prozessablaufplan für das Reporting EA
Aktualisierung des Codes von Reporting EA
In diesem Stadium werden wir die MQL5-Seite der Aktualisierung implementieren, um die Ausgabe eines hochwertigen Berichts zu gewährleisten. Wir werden den EA in logische Abschnitte unterteilen und den Code erklären, der unsere Berichtsziele direkt unterstützt. Denken Sie daran, dass MQL5 Expert Advisors nicht auf das Platzieren von Trades beschränkt sind – sie können viele routinemäßige Handelsaufgaben automatisieren, und die Berichterstattung ist ein perfektes Beispiel dafür. Obwohl wir uns derzeit auf ein leichtgewichtiges, dediziertes Reporting EA konzentrieren, kann das gleiche Reporting-Modul später in einen Berater mit mehr Funktionen integriert werden. Die Entwicklung einer spezialisierten, fokussierten EA ermöglicht es uns, ein großes Problem in überschaubare Teile zu zerlegen, die Berichtsfunktion isoliert zu entwickeln und zu verfeinern, sie durch Smoke-Tests und gezieltes Debugging zu validieren und die ausgefeilte Komponente dann mit minimalen Nachteilen in komplexere Systeme einzubauen.
1. Header, Windows-Importe und Nutzereingaben
Dieser Abschnitt deklariert die EA-Metadaten, importiert die minimalen Windows-API-Aufrufe, die wir benötigen (zum Testen der Attribute der absoluten Datei und zum Starten von cmd.exe), und stellt nutzerkonfigurierbare Eingaben zur Verfügung. Achten Sie darauf, dass diese Werte für Ihre Umgebung korrekt sind (Python-Pfad, Skriptpfad, Ausgabeverzeichnis), damit der EA sie beim Start validieren kann. Mit den Eingaben ReportFormats, EnableEmail und TestOnInit können Sie das Verhalten ohne Codeänderungen steuern.
#property copyright "Clemence Benjamin" #property version "1.01" #property description "Exports trading history in CSV and generates reports via Python" #property strict #define SW_HIDE 0 #define INVALID_FILE_ATTRIBUTES 0xFFFFFFFF #import "kernel32.dll" uint GetFileAttributesW(string lpFileName); #import #import "shell32.dll" int ShellExecuteW(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd); #import //--- User inputs input string PythonPath = "C:\\Users\\YOUR_COMPUTER_NAME\\AppData\\Local\\Programs\\Python\\Python312\\python.exe"; input string ScriptPath = "C:\\Users\\YOUR_COMPUTER_NAME\\PATH_TO\\reports_processor.py"; input string ReportFormats= "html,pdf"; input string OutputDir = "C:\\Users\\YOUR_COMPUTER_NAME\\PATH_TO_WHERE_YOUR_Reports_are_ saved"; input bool EnableEmail = true; input bool TestOnInit = true;
2. Initialisierung und Umgebungsvalidierung (OnInit)
OnInit() wärmt den EA auf, initialisiert RNG, bereinigt die konfigurierten Pfade, protokolliert das Verzeichnis der Terminaldateien, verifiziert die Existenz der ausführbaren Python-Datei und des Ausgabeverzeichnisses und bestätigt die Schreibrechte für MQL5-Dateien. Wenn TestOnInit aktiviert ist, wird ein Testlauf ausgelöst – ein hilfreicher Smoke-Check, um sicherzustellen, dass die Ende-zu-Ende-Kette korrekt verdrahtet ist.
int OnInit() { Print(">> Reporting EA initializing…"); MathSrand((uint)TimeLocal()); string cleanPythonPath = CleanPath(PythonPath); string cleanScriptPath = CleanPath(ScriptPath); string cleanOutputDir = CleanPath(OutputDir); Print("PythonPath set to: ", cleanPythonPath); Print("ScriptPath set to: ", cleanScriptPath); Print("OutputDir set to: ", cleanOutputDir); string filesDir = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\"; Print("Terminal Files directory: ", filesDir); if(GetFileAttributesW(cleanPythonPath) == INVALID_FILE_ATTRIBUTES) Print("!! Python executable not found at: ", cleanPythonPath); else Print("✔ Found Python at: ", cleanPythonPath); // Write-permission test (MQL5\\Files) int h = FileOpen("test_perm.txt", FILE_WRITE|FILE_TXT); if(h == INVALID_HANDLE) Print("!! Cannot write to MQL5\\Files directory! Error: ", GetLastError()); else { FileWrite(h, "OK"); FileFlush(h); FileClose(h); FileDelete("test_perm.txt"); Print("✔ Write permission confirmed."); } if(TestOnInit) { Print(">> Test mode: running initial export."); RunDailyExport(); lastRunTime = TimeCurrent(); } return(INIT_SUCCEEDED); }
3. Hilfsprogramme für den Pfad und grundlegende Hilfsmittel
Saubere, normalisierte Pfade und zuverlässige Dateiprüfungen vermeiden eine ganze Reihe von Laufzeitfehlern. CleanPath() normalisiert Schrägstriche und warnt vor Leerzeichen; FileExists() wickelt GetFileAttributesW für absolute Pfade ein und greift auf FileOpen für terminal-lokale Dateien zurück. Bewahren Sie diese Helfer zentral auf und verwenden Sie sie während des gesamten EA wieder.
string CleanPath(string path) { string cleaned = path; StringReplace(cleaned, "/", "\\"); while(StringFind(cleaned,"\\\\")>=0) StringReplace(cleaned, "\\\\", "\\"); StringTrimLeft(cleaned); StringTrimRight(cleaned); return cleaned; } bool FileExists(string path) { if(StringFind(path, "\\", 0) >= 0) { uint attrs = GetFileAttributesW(path); return(attrs != INVALID_FILE_ATTRIBUTES); } else { int fh = FileOpen(path, FILE_READ|FILE_TXT); if(fh == INVALID_HANDLE) return false; FileClose(fh); return true; } }
4. CSV-Erstellung und Exportlogik
ExportHistoryToCSV() verarbeitet die Auswahl der Historie und schreibt eine CSV-Datei mit einer Kopfzeile und Zeilen pro Deal. Es verwendet MakeUniqueFilename(), um Kollisionen zu vermeiden (nützlich für wiederholte Läufe) und gibt den gewählten Dateinamen in der globalen LastExportedCSV zurück. Sie möchten, dass Ihr CSV-Writer deterministisch ist und die Spalten enthält, die das Python-Skript erwartet.
string MakeUniqueFilename(string baseName) { int dot = StringFind(baseName, ".", 0); string name = dot >= 0 ? StringSubstr(baseName, 0, dot) : baseName; string ext = dot >= 0 ? StringSubstr(baseName, dot) : ""; string ts = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS); StringReplace(ts, ".", ""); StringReplace(ts, " ", "_"); StringReplace(ts, ":", ""); int suffix = MathAbs(MathRand()) % 9000 + 1000; return name + "_" + ts + "_" + IntegerToString(suffix) + ext; } bool ExportHistoryToCSV(string filename) { datetime end = TimeCurrent(); datetime start = end - 7776000; // 90 days if(!HistorySelect(start, end)) { Print("!! HistorySelect failed."); return false; } int total = HistoryDealsTotal(); if(total == 0) { Print("!! No trading history found."); return false; } int attempts = 3; string tryName = filename; int fh = INVALID_HANDLE; for(int a=0; a<attempts; a++) { fh = FileOpen(tryName, FILE_WRITE|FILE_CSV|FILE_ANSI, ","); if(fh != INVALID_HANDLE) break; tryName = MakeUniqueFilename(filename); Sleep(200); } if(fh == INVALID_HANDLE) { Print("!! FileOpen failed after retries."); return false; } FileWrite(fh, "Ticket,Time,Type,Symbol,Volume,Price,Profit,Commission,Swap,Balance,Equity"); // ... iterate deals and FileWrite rows ... FileFlush(fh); FileClose(fh); LastExportedCSV = tryName; return true; }
5. Python-Aufruf und -Orchestrierung (RunDailyExport)
RunDailyExport() ist der Orchestrierungs-Hub: Es erstellt den CSV-Pfad, startet Python mit cmd.exe und leitet stdout/stderr in python_run.log in MQL5\Files um, sodass der EA die Skriptausgabe noch überprüfen kann. Die Methode fragt dann nach dem bevorzugten report_result_YYYY-MM-DD.json (von Python erzeugt) und greift auf die PDF-Abfrage zurück, wenn JSON nicht verfügbar ist. Dieser Entwurf legt den Schwerpunkt auf einen strukturierten JSON-Handshake, damit der EA den Erfolg und die Anhänge zuverlässig bestätigen kann.
void RunDailyExport() { string filesDir = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\"; string dateStr = TimeToString(TimeCurrent(), TIME_DATE); StringReplace(dateStr, ".", ""); string csvBase = "History_" + dateStr + ".csv"; LastExportedCSV = ""; if(!ExportHistoryToCSV(csvBase)) { Print("!! CSV export failed"); return; } string csvFull = filesDir + LastExportedCSV; string cleanOutputDir = CleanPath(OutputDir); string pyLogName = "python_run.log"; string pyLogFull = filesDir + pyLogName; string pythonCmd = StringFormat("\"%s\" \"%s\" \"%s\" --formats %s --outdir \"%s\" %s > \"%s\" 2>&1", CleanPath(PythonPath), CleanPath(ScriptPath), csvFull, "pdf", cleanOutputDir, EnableEmail ? "--email" : "", pyLogFull); string fullCmd = "/c " + pythonCmd; PrintFormat("→ Launching: cmd.exe %s", fullCmd); int result = ShellExecute(fullCmd); PrintFormat("← ShellExecute returned: %d", result); // Then poll for JSON or fallback to PDF; see next section for JSON handling... }
6. JSON-Verarbeitung und eine Ausweichlösung für die Lesestrategie
Um die Abhängigkeit von menschenlesbaren Protokollen zu vermeiden, zieht der EA ein maschinenlesbares JSON-Ergebnis vor. ReadFileText() versucht, absolute JSON-Pfade direkt zu lesen; wenn MetaTrader 5 absolutes FileOpen verweigert, verwendet CopyToFilesAndRead() eine cmd-Kopie, um das JSON in MQL5\Files zu kopieren und liest es dann mit FileOpen. JsonExtractString / JsonExtractBool sind kleine, pragmatische Extraktoren, die für den einfachen JSON-Vertrag (pdf_path, email_sent) ausreichen.
string ReadFileText(string fullpath) { if(StringFind(fullpath, "\\", 0) >= 0) { uint attrs = GetFileAttributesW(fullpath); if(attrs == INVALID_FILE_ATTRIBUTES) return(""); int fh = FileOpen(fullpath, FILE_READ|FILE_TXT); if(fh == INVALID_HANDLE) return(""); string txt = ""; while(!FileIsEnding(fh)) { txt += FileReadString(fh); if(!FileIsEnding(fh)) txt += "\n"; } FileClose(fh); return txt; } else { int fh = FileOpen(fullpath, FILE_READ|FILE_TXT); if(fh == INVALID_HANDLE) return(""); // ... read and return ... } } string CopyToFilesAndRead(string absPath, string filesName) { string filesDir = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\"; string destAbs = filesDir + filesName; string cmd = "/c copy /Y \"" + absPath + "\" \"" + destAbs + "\" > nul 2>&1"; int r = ShellExecute(cmd); // wait briefly for copy then read local file return ReadTerminalLogFile(filesName); } string JsonExtractString(string json, string key) { /* tiny extractor: locate "key": "value" */ } bool JsonExtractBool(string json, string key) { /* tiny extractor: locate "key": true/false */ }
7. Überprüfung, Benachrichtigung und zuverlässiger Ausweichlösung
Nach dem Parsen des JSON validiert der EA die Werte für pdf_path und html_path mit FileExists() und benachrichtigt den Händler optional über SendNotification(). Wenn das Lesen oder die Validierung von JSON fehlschlägt, greift der EA auf das Tailing von python_run.log zurück und sucht nach dem erwarteten Report_YYYY-MM-DD.pdf (Größe > 0). Dieser mehrschichtige Ansatz schafft ein Gleichgewicht zwischen Zuverlässigkeit (JSON) und Robustheit (PDF/Protokoll-Ausweichlösung).
if(StringLen(pdfp) > 0) { if(FileExists(pdfp)) { PrintFormat("✔ PDF exists at (from JSON): %s", pdfp); SendNotification("Report PDF created: " + pdfp); } else Print("!! JSON says PDF path but file not found: ", pdfp); } // fallback: if JSON unavailable -> poll expectedPdf in OutputDir and check size
8. Unterstützungen und Zerlegungen
Dienstprogramme wie GetFileSizeByOpen() (Überprüfung der sicheren Dateigröße), ReadTerminalLogFile() (Lesen von Dateien aus MQL5\Files) und der ShellExecute()-Wrapper sind klein, aber wichtig. OnDeinit() gibt beim Aufräumen eine Meldung aus. Halten Sie diese kompakt und gut dokumentiert; sie vereinfachen die Fehlersuche und vermeiden Doppelarbeit.
long GetFileSizeByOpen(string filename) { int fh = FileOpen(filename, FILE_READ|FILE_BIN); if(fh == INVALID_HANDLE) return -1; int pos = FileSeek(fh, 0, SEEK_END); FileClose(fh); return (pos < 0) ? -1 : (long)pos; } string ReadTerminalLogFile(string filename) { int fh = FileOpen(filename, FILE_READ | FILE_TXT); if(fh == INVALID_HANDLE) return(""); string content = ""; while(!FileIsEnding(fh)) { content += FileReadString(fh); if(!FileIsEnding(fh)) content += "\n"; } FileClose(fh); return content; } int ShellExecute(string command) { return ShellExecuteW(0, "open", "cmd.exe", command, NULL, SW_HIDE); }
Aktualisieren des Python-Skripts reports_processor
Der EA arbeitet Hand in Hand mit dem Python-Skript reports_processor.py. Nachdem wir also den EA aktualisiert haben, müssen wir die Python-Seite anpassen, um die Ergebnisse so zu speichern und zu melden, dass der EA sie effizient nutzen kann. In diesem Abschnitt erläutern wir den Skriptcode im Detail und zeigen genau, wie die einzelnen Teile in den EA integriert werden – von der CSV-Eingabe und -Analyse über das PDF-Rendering und die JSON-Ergebnisausgabe – um eine zuverlässige, automatisierbare Reporting-Pipeline zu gewährleisten.
Bevor wir in die Aufschlüsselung unseres Skriptcodes eintauchen, müssen wir zunächst die Python-Umgebung einrichten, um sicherzustellen, dass das System die Funktionalität des Skripts vollständig unterstützen kann. In den folgenden Schritten werden wir den Einrichtungsprozess von der Installation an durchlaufen. Dieser entscheidende Schritt gibt uns die Gewissheit, dass die Umgebung richtig konfiguriert ist, sodass wir während der Entwicklung des Skripts reports_processor nahtlos Tests durchführen können.
Schritt-für-Schritt-Anleitung: Einrichten von Python unter Windows für das Skript reports_processor
1. Installieren Sie Python 3.9+ von python.org und stellen Sie sicher, dass python im PATH steht.
2. Öffnen Sie PowerShell und erstellen + aktivieren Sie venv:
python -m venv venv .\venv\Scripts\Activate.ps1
3. Upgrade pip:
python -m pip install --upgrade pip
4. Installieren Sie die Kernpakete (fpdf ist das Standard-PDF-Backend; fügen Sie andere hinzu, falls gewünscht):
pip install pandas numpy jinja2 matplotlib fpdf yagmail
Optionale Backends:
pip install pdfkit weasyprint
Wenn Sie wkhtmltopdf verwenden, laden Sie den Windows-Installer herunter und fügen Sie die ausführbare Datei zu PATH hinzu oder setzen Sie die Umgebungsvariable WKHTMLTOPDF_PATH.
5. Legen Sie Umgebungsvariablen für E-Mail fest (Beispiel: in Systemeigenschaften → Umgebungsvariablen oder PowerShell):
setx YAGMAIL_USERNAME "you@example.com" setx YAGMAIL_PASSWORD "your-email-password" setx WKHTMLTOPDF_PATH "C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe" # if using wkhtmltopdf
6. Testlauf (aus dem Projektordner, mit aktivem venv):
python reports_generator.py "C:\path\to\MQL5\Files\History_20250821.csv" --formats pdf --outdir "C:\path\to\Reports" --email
7. Überprüfen Sie die Ausgaben in C:\path\to\Reports:
- Report_YYYY-MM-DD.pdf (primär)
- equity_curve_YYYY-MM-DD.png (Chart)
- report_result_YYYY-MM-DD.json (EA Handshake)
Reports_processor.py
1. Startup, Importe und Backend-Erkennung
Beim Start lädt das Skript die Python-Standardbibliotheken (os, sys, logging, datetime, argparse, glob) und die Kerndatenbibliotheken (pandas, numpy). Es versucht dann, optionale Toolchains zu importieren – WeasyPrint, pdfkit (wkhtmltopdf), FPDF und yagmail – und zeichnet Verfügbarkeitskennzeichen für jedes Backend auf. Mit diesen Flags kann das Skript zur Laufzeit einen geeigneten Weg für die PDF-Erzeugung oder den E-Mail-Versand wählen, der auf den installierten Paketen basiert und auch in Umgebungen funktioniert, in denen einige optionale Pakete nicht vorhanden sind. Die Protokollierung wird frühzeitig konfiguriert, sodass das Skript Informations- und Fehlermeldungen mit Zeitstempel ausgibt, die der EA in python_run.log erfassen kann.
#!/usr/bin/env python # reports_processor.py __version__ = "1.0.1" import sys import os import traceback import argparse import glob from datetime import datetime, timedelta import logging # core data libs import pandas as pd import numpy as np # templating & plotting from jinja2 import Environment, FileSystemLoader import matplotlib.pyplot as plt # optional libraries: import safely WEASY_AVAILABLE = False PDFKIT_AVAILABLE = False FPDF_AVAILABLE = False YAGMAIL_AVAILABLE = False try: from weasyprint import HTML # may fail if native libs aren't installed WEASY_AVAILABLE = True except Exception: WEASY_AVAILABLE = False try: import pdfkit # wrapper for wkhtmltopdf PDFKIT_AVAILABLE = True except Exception: PDFKIT_AVAILABLE = False try: from fpdf import FPDF # pure-python PDF writer (no native deps) FPDF_AVAILABLE = True except Exception: FPDF_AVAILABLE = False try: import yagmail YAGMAIL_AVAILABLE = True except Exception: YAGMAIL_AVAILABLE = False # Configure logging logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
2. Parsen von Argumenten und Befehlskontrakt
Das Skript verfügt über eine kleine Befehlszeilenschnittstelle: csv_path akzeptiert entweder einen CSV-Dateipfad oder einen zu durchsuchenden Ordner; --formats steuert, welche Ausgaben erstellt werden sollen (HTML, PDF oder beides); --outdir legt fest, wohin die Artefakte geschrieben werden sollen; --email schaltet den Versand von Berichten ein; und --pdf-backend wählt den zu verwendenden PDF-Pfad aus. Dieser Vertrag entspricht dem EA-Aufrufmuster: Der EA gibt den CSV-Pfad, das Ausgabeverzeichnis und ein optionales E-Mail-Flag an und überwacht dann das Ausgabeverzeichnis auf ein JSON-Ergebnis.
def parse_args(): """Parse command-line arguments.""" parser = argparse.ArgumentParser(description="Generate trading reports (HTML/PDF) from CSV.") parser.add_argument("csv_path", help="Path to the trades CSV file or a folder to search for History_*.csv") parser.add_argument("-f", "--formats", nargs="+", choices=["html", "pdf"], default=["pdf"], help="Report formats to generate (html, pdf, or both)") parser.add_argument("-o", "--outdir", default=".", help="Directory to write report files") parser.add_argument("--email", action="store_true", help="Send report via email") parser.add_argument("--pdf-backend", choices=["weasyprint","wkhtmltopdf","fpdf"], default="fpdf", help="PDF backend to use. 'weasyprint', 'wkhtmltopdf', or 'fpdf' (no native deps). Default: fpdf") return parser.parse_args()
3. CSV-Auflösung (Finden der Eingabedatei)
Wenn ein Ordner oder ein flexibler csv_path angegeben wird, löst das Skript die zu verarbeitende CSV-Datei auf. Wenn csv_path ein Verzeichnis ist, wird nach History_*.csv-Dateien gesucht (wobei auf eine beliebige .csv-Datei zurückgegriffen wird, wenn keine übereinstimmt) und die zuletzt geänderte Datei ausgewählt. Wenn ein vollständiger CSV-Pfad angegeben wird, wird dieser unverändert akzeptiert. Dadurch ist das Skript tolerant und kann sowohl vom EA (der eine bestimmte CSV übergibt) als auch bei manuellen Läufen einfach aufgerufen werden.
def resolve_csv_path(csv_path): """ Ensure csv_path points to an existing file. If not, try to locate the most recent History_*.csv in the same directory or the provided folder. Returns resolved path or raises FileNotFoundError. """ # If csv_path is a directory -> look there if os.path.isdir(csv_path): search_dir = csv_path else: # If path exists as file -> return if os.path.isfile(csv_path): return csv_path # otherwise, take parent directory to search search_dir = os.path.dirname(csv_path) or "." pattern = os.path.join(search_dir, "History_*.csv") candidates = glob.glob(pattern) if not candidates: # also allow any .csv if no History_*.csv found candidates = glob.glob(os.path.join(search_dir, "*.csv")) if not candidates: raise FileNotFoundError(f"No CSV files found in {search_dir} (tried {pattern})") # pick the newest file by modification time candidates.sort(key=os.path.getmtime, reverse=True) chosen = candidates[0] logging.info(f"Resolved CSV path: {chosen} (searched: {search_dir})") return chosen
4. Laden und Normalisieren von Handelsdaten
Die CSV-Datei wird mit Pandas geladen und es wird versucht, eine Zeitspalte in Datumsangaben zu zerlegen. Es stellt sicher, dass die erforderlichen Spalten vorhanden sind (z. B. Gewinn, Symbol, Saldo und Kapital), indem es Standardwerte ausfüllt oder Werte berechnet, wenn dies möglich ist (z. B. kumulierte Summen für Saldo/Kapital, wenn erforderlich). Nach der Normalisierung wird der DataFrame nach Zeit sortiert, wodurch ein konsistenter, zeitlich geordneter Datensatz für Analysen und Diagramme entsteht.
def load_data(csv_path): """Load and validate CSV data, computing missing columns if necessary.""" try: if not os.path.isfile(csv_path): logging.error(f"CSV file not found: {csv_path}") raise FileNotFoundError(f"CSV file not found: {csv_path}") # try parsing Time; if fails, read without parse and try to coerce later try: df = pd.read_csv(csv_path, parse_dates=["Time"]) except Exception: df = pd.read_csv(csv_path) if "Time" in df.columns: df["Time"] = pd.to_datetime(df["Time"], errors="coerce") # Provide minimal defaults if columns missing if "Profit" not in df.columns: df["Profit"] = 0.0 if "Symbol" not in df.columns: df["Symbol"] = "N/A" required_cols = ["Time", "Symbol", "Profit", "Balance", "Equity"] for col in required_cols: if col not in df.columns: logging.warning(f"Missing column: {col}. Attempting to compute.") if col == "Balance": # create cumsum of profit + commission + swap if possible df["Commission"] = df.get("Commission", 0) df["Swap"] = df.get("Swap", 0) df["Balance"] = (df["Profit"] + df["Commission"] + df["Swap"]).cumsum() elif col == "Equity": df["Equity"] = df.get("Balance", df["Profit"].cumsum()) # ensure Time column exists and is sorted if "Time" in df.columns: df = df.sort_values(by="Time") return df except Exception as e: logging.error(f"Error loading CSV: {e}") raise
5. Analytik und Metrik der Datenverarbeitung
Der Schritt compute_stats leitet die wichtigsten Berichtsmetriken ab, die für den EA und den Nutzer von Bedeutung sind: eine Zeichenkette des Berichtsdatums, der Nettogewinn, die Gesamtzahl der Handelsgeschäfte, das Symbol mit der besten Performance (nach aggregiertem Gewinn), der maximale Drawdown, der aus der Kapitalkurve berechnet wird, und eine einfache Sharpe-ähnliche Verhältnisschätzung. Diese Werte werden in ein Statistik-Lexikon verpackt und später sowohl in HTML-Vorlagen als auch in PDF-Seiten eingebettet, um eine schnelle Zusammenfassung zu präsentieren.
def compute_stats(df): """Compute trading statistics from the DataFrame.""" try: top_symbol = None try: top_symbol = df.groupby("Symbol")["Profit"].sum().idxmax() except Exception: top_symbol = "N/A" eq = df.get("Equity") if eq is None: eq = df.get("Balance", df["Profit"].cumsum()) max_dd = float((eq.cummax() - eq).max()) if len(eq) > 0 else 0.0 sharpe = 0.0 if len(df) > 1 and "Profit" in df.columns: dif = df["Profit"].diff().dropna() if dif.std() != 0: sharpe = float((dif.mean() / dif.std()) * np.sqrt(252)) stats = { "date": datetime.now().strftime("%Y-%m-%d"), "net_profit": float(df["Profit"].sum()) if "Profit" in df.columns else 0.0, "trade_count": int(len(df)), "top_symbol": top_symbol, "max_drawdown": max_dd, "sharpe_ratio": sharpe } return stats except Exception as e: logging.error(f"Error computing stats: {e}") raise
6. Diagramme – Kapital- bzw. Saldenkurve
Das Skript erzeugt eine PNG-Kapitalkurve unter Verwendung von Matplotlib. Es bevorzugt die Darstellung von Zeit gegen Saldo und Kapital, wenn Zeitstempel vorhanden sind; andernfalls stellt es den Index gegen Saldo/Kapital dar. Das erzeugte PNG wird im --outdir gespeichert und später von der HTML-Vorlage referenziert oder in die PDF-Datei eingebettet, sodass der Leser eine visuelle Zusammenfassung der Kontoperformance über das Berichtsfenster erhält.
# Generate equity curve plot curve_image = os.path.join(args.outdir, f"equity_curve_{stats['date']}.png") plt.figure() # handle possibility of NaT in Time if "Time" in df.columns and not df["Time"].isnull().all(): plt.plot(df["Time"], df["Balance"], label="Balance") plt.plot(df["Time"], df["Equity"], label="Equity") else: # fallback: plot index vs balance/equity plt.plot(df.index, df["Balance"], label="Balance") plt.plot(df.index, df["Equity"], label="Equity") plt.title("Balance & Equity Curve") plt.legend() plt.tight_layout() plt.savefig(curve_image) plt.close() logging.info(f"Equity curve saved: {curve_image}")
7. HTML-Wiedergabe mit Jinja2
Wenn eine HTML-Ausgabe angefordert (oder als Zwischenprodukt erzeugt) wird, verwendet das Skript Jinja2 zum Rendern von report_template.html. Die Vorlage erhält das Statistik-Lexikon, die Gewinnaggregate pro Symbol und den Dateinamen des Kurvenbildes. Das gerenderte HTML wird in den Ausgabeordner geschrieben und protokolliert. Dieses HTML kann als Endprodukt oder als Quelle für HTML-zu-PDF-Konverter verwendet werden.
def render_html(df, stats, outpath, curve_image): """Render the HTML report using a Jinja2 template.""" try: env = Environment(loader=FileSystemLoader(os.path.dirname(__file__))) tmpl_path = os.path.join(os.path.dirname(__file__), "report_template.html") if not os.path.isfile(tmpl_path): logging.error(f"Template not found: {tmpl_path}") raise FileNotFoundError(f"Missing template: {tmpl_path}") tmpl = env.get_template("report_template.html") html = tmpl.render(stats=stats, symbol_profits=df.groupby("Symbol")["Profit"].sum().to_dict(), curve_image=curve_image) os.makedirs(os.path.dirname(outpath), exist_ok=True) with open(outpath, "w", encoding="utf-8") as f: f.write(html) logging.info(f"HTML written: {outpath}") except Exception as e: logging.error(f"Error rendering HTML: {e}") raise
8. PDF-Erzeugung – mehrere Backends
Das Skript unterstützt drei PDF-Erzeugungsstrategien und wählt eine davon auf der Grundlage des Arguments --pdf-backend und der festgestellten Verfügbarkeit aus:
- WeasyPrint: konvertiert das gerenderte HTML direkt in PDF (HTML → PDF) unter Verwendung von WeasyPrint, falls vorhanden.
- wkhtmltopdf (pdfkit): konvertiert HTML in ähnlicher Weise in PDF über das wkhtmltopdf-Binary, wenn es konfiguriert ist.
- FPDF: ein reiner Python-Pfad, der eine PDF-Datei programmatisch erstellt und Diagramme und Datentabellen einfügt, ohne auf HTML/CSS-Rendering angewiesen zu sein. Dieser Pfad erzeugt ein mehrseitiges A4-PDF mit einem Titelblatt, Kennzahlenblöcken, der Kapitalkurve, Symbol-Gewinntabellen und einem Beispiel für die letzten Transaktionen.
def convert_pdf_weasy(html_path, pdf_path): if not WEASY_AVAILABLE: raise RuntimeError("WeasyPrint backend requested but weasyprint is not available.") os.makedirs(os.path.dirname(pdf_path), exist_ok=True) HTML(html_path).write_pdf(pdf_path) logging.info(f"PDF written: {pdf_path}") def convert_pdf_wkhtml(html_path, pdf_path, wk_path=None): if not PDFKIT_AVAILABLE: raise RuntimeError("wkhtmltopdf/pdfkit backend requested but pdfkit is not available.") config = None if wk_path and os.path.isfile(wk_path): config = pdfkit.configuration(wkhtmltopdf=wk_path) pdfkit.from_file(html_path, pdf_path, configuration=config) logging.info(f"PDF written: {pdf_path}") def convert_pdf_with_fpdf(df, stats, curve_image_path, pdf_path): if not FPDF_AVAILABLE: raise RuntimeError("FPDF backend requested but fpdf is not installed.") pdf = FPDF(orientation='P', unit='mm', format='A4') pdf.set_auto_page_break(auto=True, margin=15) pdf.add_page() pdf.set_font("Arial", "B", 16) pdf.cell(0, 8, f"Trading Report - {stats.get('date','')}", ln=True, align='C') # ... add stats, insert curve image and tables ... if curve_image_path and os.path.isfile(curve_image_path): try: pdf.image(curve_image_path, x=10, y=None, w=190) except Exception as e: logging.warning(f"Could not insert curve image into PDF: {e}") pdf.output(pdf_path) logging.info(f"PDF written: {pdf_path}")
Jedes Backend schreibt die endgültige Datei Report_YYYY-MM-DD.pdf in das konfigurierte Ausgabeverzeichnis und meldet ihre Fertigstellung über die Protokollierung.
9. E-Mail-Versand
Wenn das --email-Flag gesetzt ist, verwendet das Skript eine E-Mail-Hilfsroutine, um die erzeugten Berichtsdateien mit Hilfe der verfügbaren E-Mail-Bibliothek an den konfigurierten Empfänger zu senden. Die Routine protokolliert, ob das Senden erfolgreich war, und das Ergebnis wird später im JSON-Handshake erfasst, sodass der EA feststellen kann, ob während dieses Laufs eine E-Mail versendet wurde.
def send_email(files, stats): """Send the generated reports via email.""" try: if not YAGMAIL_AVAILABLE: logging.error("yagmail not available; cannot send email. Install yagmail or remove --email flag.") return user = os.getenv("Your YAGMAIL ACCOUNT") pw = os.getenv("Your Email password") if not user or not pw: logging.error("Email credentials not set in environment variables (YAGMAIL_USERNAME, YAGMAIL_PASSWORD).") return yag = yagmail.SMTP(user, pw) subject = f"Trading Report {stats['date']}" contents = [f"See attached report for {stats['date']}"] + files yag.send(to=user, subject=subject, contents=contents) logging.info("Email sent.") except Exception as e: logging.error(f"Error sending email: {e}")
10. Ergebnis JSON-Writer – der EA-Handshake
Nach der Erzeugung von Ausgaben kompiliert das Skript ein kompaktes, maschinenlesbares JSON, das pdf_path, html_path, email_sent, Zeitstempel und einen exit_code enthält. Das JSON wird atomar (über eine temporäre Datei + Umbenennung) in das --outdir als report_result_YYYYY-MM-DD.json geschrieben. Da der Schreibvorgang atomar abläuft, kann der EA das Vorhandensein der JSON-Datei als zuverlässiges Signal für die Beendigung der Python-Seite abfragen und dann ihren Inhalt lesen, um die genauen Ausgabeorte zu erfahren und festzustellen, ob der E-Mail-Versand erfolgt ist.
def write_result_json(outdir, pdf_path=None, html_path=None, email_sent=False, exit_code=0): """Write a small JSON file with the report result so MQL5 can poll/verify.""" import json, tempfile try: result = { "pdf_path": pdf_path or "", "html_path": html_path or "", "email_sent": bool(email_sent), "timestamp": datetime.now().isoformat(), "exit_code": int(exit_code) } os.makedirs(outdir, exist_ok=True) fname = os.path.join(outdir, f"report_result_{datetime.now().strftime('%Y-%m-%d')}.json") # atomic write fd, tmp = tempfile.mkstemp(prefix="._report_result_", dir=outdir, text=True) with os.fdopen(fd, "w", encoding="utf-8") as f: json.dump(result, f, indent=2) os.replace(tmp, fname) logging.info(f"Result JSON written: {fname}") return fname except Exception as e: logging.error(f"Failed to write result JSON: {e}") return None
11. Hauptablauf
Die Funktion main() fasst alles zusammen: Sie parst Argumente, löst die CSV auf, lädt und normalisiert die Daten, berechnet Statistiken, generiert die Kapitalkurve, rendert HTML (falls angefordert), generiert PDF(s) über das ausgewählte Backend, versendet optional E-Mails, bereinigt ältere Berichte im Ausgabeverzeichnis, schreibt das Ergebnis in JSON und beendet sich mit einem Erfolgs- oder Fehlercode. Die Sequenz und die Protokollierung stellen sicher, dass der EA, der das Skript gestartet hat, den Fortschritt des Laufs (über das python_run.log) verfolgen und auf das endgültige report_result_...json reagieren kann.
def main(): """Main function to generate reports.""" args = parse_args() try: # Ensure output directory exists os.makedirs(args.outdir, exist_ok=True) # Resolve csv_path in case a folder or variable-name was provided try: csv_path_resolved = resolve_csv_path(args.csv_path) except FileNotFoundError as e: logging.error(str(e)) return 1 # Load data and compute stats df = load_data(csv_path_resolved) stats = compute_stats(df) # Generate equity curve plot curve_image = os.path.join(args.outdir, f"equity_curve_{stats['date']}.png") # ... plotting code ... base = os.path.join(args.outdir, f"Report_{stats['date']}") files_out = [] # Generate HTML report (if requested or needed by PDF backend) html_path = base + ".html" if "html" in args.formats: render_html(df, stats, html_path, os.path.basename(curve_image)) files_out.append(html_path) # Decide PDF backend and generate PDF backend = args.pdf_backend.lower() if hasattr(args, "pdf_backend") else "fpdf" if "pdf" in args.formats: pdf_path = base + ".pdf" if backend == "weasyprint": convert_pdf_weasy(html_path, pdf_path) elif backend == "wkhtmltopdf": convert_pdf_wkhtml(html_path, pdf_path, wk_path=os.getenv("WKHTMLTOPDF_PATH")) elif backend == "fpdf": convert_pdf_with_fpdf(df, stats, curve_image, pdf_path) files_out.append(pdf_path) # Send email if enabled if args.email: send_email(files_out, stats) # write result JSON for MQL5 pdf_file = next((f for f in files_out if f.endswith(".pdf")), "") html_file = next((f for f in files_out if f.endswith(".html")), "") write_result_json(args.outdir, pdf_path=pdf_file, html_path=html_file, email_sent=args.email, exit_code=0) return 0 except Exception as e: logging.error(f"Main execution error: {e}") traceback.print_exc() try: write_result_json(args.outdir, exit_code=1) except: pass return 1 if __name__ == "__main__": sys.exit(main())
12. Fehlerbehandlung und garantiertes JSON bei Fehlern
Während main() werden Ausnahmen auf der obersten Ebene abgefangen, sodass das Skript beim Auftreten eines unbehandelten Fehlers den Traceback protokolliert und dennoch eine report_result_YYYY-MM-DD.json mit einem exit_code schreibt, der den Fehler angibt. Dadurch wird sichergestellt, dass der EA immer ein deterministisches JSON-Objekt zur Überprüfung erhält (Erfolg oder Misserfolg), was die Logik der Verifizierung und der Ausweichlösung des EAs vereinfacht.
except Exception as e: logging.error(f"Main execution error: {e}") traceback.print_exc() # ensure a JSON is still written so MQL5 knows it failed try: write_result_json(args.outdir, exit_code=1) except: pass return 1
13. Bereinigungs- und Aufbewahrungspolitik
Am Ende eines erfolgreichen Laufs führt das Skript einen Retention Sweep des Ausgabeverzeichnisses durch und entfernt ältere Report_-Dateien nach einem konfigurierbaren Zeitfenster (das aktuelle Skript verwendet 30 Tage). Dadurch bleibt der Ausgabeordner aufgeräumt und es wird verhindert, dass die Festplatte auf Systemen, die häufig Berichte ausführen, unkontrolliert anwächst.
# Clean up old reports (older than 30 days) cutoff = datetime.now() - timedelta(days=30) for f in os.listdir(args.outdir): if f.startswith("Report_") and f.endswith(tuple(args.formats)): full = os.path.join(args.outdir, f) if datetime.fromtimestamp(os.path.getmtime(full)) < cutoff: os.remove(full) logging.info(f"Deleted old file: {full}")
Wie EA und Python-Skript zur Laufzeit integriert werden
Der Handshake zur Laufzeit ist einfach: Der EA exportiert eine CSV-Datei und startet das Skript (mit Übergabe des CSV-Pfads, --outdir und beliebigen Flags). Das Python-Skript verarbeitet die CSV-Datei und schreibt Artefakte (PNG, PDF) in den Ausgabeordner und schreibt schließlich report_result_YYYY-MM-DD.json. Der EA fragt nach diesem JSON; sobald es vorhanden ist, liest er das JSON, validiert die gemeldeten Pfade und Dateigrößen und führt Nachbearbeitungen durch (Benachrichtigungen, Archivierung, E-Mail-Bestätigung). Die Protokollierung (python_run.log) und das JSON bilden zusammen einen robusten, maschinenverwertbaren Vertrag zwischen den beiden Komponenten.
Tests
Unsere Tests werden durchgeführt, indem der EA auf der MetaTrader 5-Plattform eingesetzt wird, wo das Python-Skript gestartet und im Hintergrund ausgeführt wird. Die Ergebnisse können über das Expertenprotokoll im Terminal sowie durch Überprüfung der angegebenen Ausgabeverzeichnisse auf generierte Dateien überwacht werden. In meinem Test habe ich den Ausgabebericht mit dem Namen Report_2025-08-21 gefunden. Der Inhalt dieser Datei ist weitaus umfangreicher als bei früheren Versionen. Der Bericht enthält nun gut strukturierte Tabellen, Kapitalkurven und mehrere berechnete Kennzahlen, die in einem übersichtlichen Format dargestellt werden und Händlern wertvolle Einblicke bieten. Die folgenden Screenshots zeigen die generierten Berichtsdateien und teilweise die PDF-Inhalte.

Abbildung 4: Anzeige der Ausgabedateien im Windows Explorer

Abbildung 5: Animierter Screenshot aus dem generierten PDF-Bericht

Abbildung 6: In den Berichten erstellte Kapitalkurve
Schlussfolgerung
Wir haben erfolgreich die Integration von MQL5 und Python demonstriert, um funktionsreiche Handelsberichte zu liefern, die weit über die grundlegenden Ausgaben der vorherigen Version unseres Reporting EA hinausgehen. Eines der herausragenden Merkmale ist die automatisierte Kapitalkurve, die die Salden- und Kapitaltrends im Laufe der Zeit visuell verfolgt und den Händlern ein unmittelbares Verständnis der Performancedynamik vermittelt. Das System beweist die Machbarkeit der Verknüpfung eines Expert Advisors mit einer externen Python-Reporting-Pipeline und zeigt, wie diese Zusammenarbeit weiter verfeinert und ausgebaut werden kann.
Durch die kontinuierliche Verfeinerung des EA-Codes und die Nutzung leistungsstarker Python-Bibliotheken wie Pandas, Matplotlib und FPDF können wir fortschrittliche Statistiken berechnen, aufschlussreiche Tabellen erstellen und klare Visualisierungen in Berichte einbetten. Die Hinzufügung eines leichtgewichtigen JSON-Handshakes zwischen dem EA und dem Python-Skript gewährleistet eine zuverlässige Bestätigung der Ausgaben und macht den Arbeitsablauf nahtlos und transparent. Darüber hinaus zeigen optionale Funktionen wie die automatische E-Mail-Zustellung und flexible PDF-Backends die Anpassungsfähigkeit des Frameworks an unterschiedliche Umgebungen.
Diese Entwicklung schafft eine solide Grundlage für langfristige Berichtslösungen. Die daraus resultierenden Dokumente können als umfassende Instrumente für die Analyse der Handelsleistung dienen und helfen Händlern und Anlegern gleichermaßen, Trends zu erkennen, Risiken wie Drawdowns zu messen und Strategien klarer zu bewerten. Letztendlich zeigt der Reporting EA in Kombination mit dem Skript reports_processor.py, wie die sprachübergreifende Integration die Automatisierung und die Entscheidungsfindung verbessern und die Tür zu noch anspruchsvolleren Analysen in der Zukunft öffnen kann.
Wichtige Lektionen
| Wichtigste Lektion | Beschreibung: |
|---|---|
| Validieren Sie externe Abhängigkeiten beim Starten. | Überprüfen Sie immer die Speicherorte von externen Tools und Ordnern (ausführbare Python-Datei, Skriptpfad, Ausgabeverzeichnis) während OnInit mit Hilfe von Dateiattributprüfungen. Die frühzeitige Validierung mit GetFileAttributesW verhindert obskure Laufzeitfehler. |
| Bevorzugung eines maschinenlesbaren Handshakes (JSON) | Verwenden Sie eine kleine, strukturierte Ergebnisdatei (JSON) als kanonisches Signal von Python zu MQL5, anstatt Freiform-Protokolle zu parsen. Sie macht die Validierung deterministisch und programmatisch. |
| Ergebnisdateien atomar schreiben | Schreiben Sie das JSON mit einem temporären Dateinamen und benennen Sie es dann atomar um bzw. ersetzen Sie es. Dadurch wird vermieden, dass der EA bei der Abfrage nach der Fertigstellung nur teilweise liest. |
| Berücksichtigung der Einschränkungen von MQL5 für absolute Pfade | MQL5 FileOpen kann oft nicht beliebige absolute Pfade lesen. Bieten Sie eine Ausweichmöglichkeit: Kopieren Sie die Datei in MQL5\Files oder lesen Sie sie über einen WinAPI ReadFile-Ansatz, damit der EA konsistent auf Ausgaben zugreifen kann. |
| Robuste Prüfungen der Dateiexistenz und -größe verwenden | Gehen Sie nicht davon aus, dass Anwesenheit gleichbedeutend mit Vollständigkeit ist. Überprüfen Sie die Dateiattribute und die Dateigröße (Öffnen + Suchen bis zum Ende), um sicherzustellen, dass ein Bericht oder eine Binärdatei vollständig geschrieben ist, bevor Sie sie bearbeiten. |
| Eindeutige Dateinamen verwenden, um Kollisionen zu vermeiden | Generieren Sie CSV- und Berichtsdateinamen mit Zeitstempeln und zufälligen Suffixen, damit wiederholte Läufe die Ergebnisse nicht überschreiben und historische Artefakte für Audits erhalten bleiben. |
| Konsistenter Export von CSV-Dateien für Python | Entwerfen Sie die CSV-Spalten (Kopfzeilen, Formate, Datums-/Zeitformat), die das Python-Skript erwartet. Ein konsistenter Exportvertrag beseitigt Mehrdeutigkeiten beim Parsing und reduziert Fehler bei der Datenübernahme. |
| Hilfsprogramme zentralisieren | Behalten Sie Pfadnormalisierung, Dateiexistenz-Helfer, JSON-Extraktoren und kleine Lese-/Schreib-Wrapper an einem Ort, sodass jeder Teil des EA die gleiche robuste Logik wiederverwendet (CleanPath, FileExists, ReadTerminalLogFile). |
| Implementierung von Polling mit sinnvollen Timeouts | Wenn Sie auf einen externen Prozess warten, verwenden Sie eine Schleife mit einem kurzen Abfrageintervall und einem allgemeinen Timeout. Dadurch wird ein Gleichgewicht zwischen Reaktionsfähigkeit und CPU-Nutzung hergestellt und unendliche Wartezeiten vermieden. |
| Ausweichlösungen und mehrschichtige Verifizierung bereitstellen | Mehrstufige Prüfungen: JSON-basierte Bestätigung bevorzugen; wenn diese fehlt, python_run.log überprüfen; wenn das fehlschlägt, das erwartete PDF abfragen. Mehrere Prüfpfade erhöhen die Robustheit. |
| Großzügig protokollieren und Oberflächendiagnostik | Schreiben Sie informative Protokolleinträge und drucken Sie die letzten Teile der Python-Protokolle in das Expertenprotokoll. Klare Diagnosen beschleunigen die Fehlersuche, wenn Läufe in der Produktion fehlschlagen. |
| Niemals Anmeldedaten fest codieren | Halten Sie geheime Daten aus dem Quellcode heraus: Verwenden Sie Umgebungsvariablen für E-Mail-Anmeldedaten und dokumentieren Sie, welche Variablen gesetzt werden müssen. Dies verbessert die Sicherheit und Übertragbarkeit. |
| Unterstützung mehrerer PDF-Backends und Erkennung der Verfügbarkeit | Machen Sie das Python-Skript backend-agnostisch: Erkennen Sie weasyprint, wkhtmltopdf/pdfkit oder einen reinen Python fpdf-Pfad und wählen Sie die beste verfügbare Option zur Laufzeit. |
| Virtuelle Umgebungen nutzen und die Einrichtung dokumentieren | Dokumentieren Sie ein reproduzierbares Windows-Setup: Python-Version, Virtualenv-Erstellung, Pip-Installationsliste und systemeigene Voraussetzungen für optionale Backends, damit Einsätze vorhersehbar sind. |
| „Smoke-Test“ des Ende-zu-Ende-Flusses | Führen Sie einen schnellen „Smoke-Test", Ende-zu-Ende durch: Setzen Sie den EA ein, lassen Sie ihn eine CSV exportieren, starten Sie Python, und überprüfen Sie das Expertenprotokoll sowie den Ausgabeordner für PDF/JSON. „Smoke-Tests“ geben schnelles Vertrauen, bevor eine tiefergehende QA erfolgt. |
| Gewährleistung einer deterministischen Fehlermeldung | Bei Ausnahmen wird immer ein JSON-Ergebnis mit einem exit_code und Diagnosefeldern geschrieben. Dadurch wird sichergestellt, dass der EA Fehler erkennen und eine Ausweichlogik anwenden kann, anstatt sich auf unbestimmte Zeit aufzuhängen. |
Anlagen
| Dateiname | Version | Beschreibung |
|---|---|---|
| Reporting_EA.mq5 | 1.01 | Expert Advisor, der den Handelsverlauf in eindeutig benannte CSV-Dateien exportiert, den Python-Berichtsprozessor startet und die Ausgaben überprüft. Zu den Funktionen gehören Pfadnormalisierung, Abhängigkeitsüberprüfungen, atomare Dateinamengenerierung, JSON-First-Handshake mit Python, Copy-Fallback für Lesevorgänge in absoluten Pfaden, PDF-Polling-Ausweichmöglichkeit, detaillierte Protokollierung für Experten und optionale Push-Benachrichtigungen. |
| Reports_processor.py | 1.0.1 | Python-Reporting-Engine, die die EA-CSV einliest, Analysen berechnet (Nettogewinn, Drawdown, Sharpe, Aggregate pro Symbol), Diagramme erstellt, HTML und/oder PDF rendert (mehrere Backend-Optionen: fpdf, wkhtmltopdf, WeasyPrint), optional E-Mails versendet und einen atomaren report_result_YYYYY-MM-DD.json für den EA-Handshake schreibt. Umfasst die Bereinigung von Aufbewahrungsdaten und eine robuste Fehlerbehandlung mit garantiertem JSON bei Fehlern. |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/19006
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Automatisieren von Handelsstrategien in MQL5 (Teil 30): Erstellen eines harmonischen AB-CD-Preisaktionsmusters mit visuellem Feedback
Entwicklung eines individuellen Indikators für die Marktstimmung
Selbstoptimierende Expert Advisors in MQL5 (Teil 13): Eine sanfte Einführung in die Kontrolltheorie mit Hilfe der Matrixfaktorisierung
MetaTrader trifft auf Google Sheets mit Pythonanywhere: Ein Leitfaden für einen sicheren Datenfluss
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.