
Handel mit dem MQL5 Wirtschaftskalender (Teil 7): Vorbereitung auf Strategietests mit der ressourcenbasierten Analyse von Nachrichtenereignissen
Einführung
In diesem Artikel führen wir unsere Serie des MQL5-Wirtschaftskalenders fort, indem wir das Handelssystem für Strategietests im Nicht-Live-Modus vorbereiten und dabei eingebettete wirtschaftliche Ereignisdaten für zuverlässige Backtests nutzen. Aufbauend auf Teil 6, in dem es um die Automatisierung von Handelseinträgen mit Nachrichtenanalyse und Countdown-Timern ging, konzentrieren wir uns jetzt auf das Laden von Nachrichtenereignissen aus einer Ressourcendatei und die Anwendung nutzerdefinierter Filter, um Live-Bedingungen im Strategy Tester zu simulieren. Wir gliedern den Artikel in die folgenden Themen:
Lasst uns eintauchen!
Bedeutung der Integration statischer Daten
Die Integration statischer Daten ist für diejenigen, die robuste Strategien entwickeln und testen wollen, von entscheidender Bedeutung, insbesondere in Umgebungen wie MQL5, in denen historische wirtschaftliche Ereignisdaten nicht über lange Zeiträume gespeichert werden. Im Gegensatz zum Live-Handel, bei dem die Plattform Echtzeit-Nachrichten abrufen kann, hat der Strategietester keinen Zugang zu solchen dynamischen Updates. Es speichert keine umfangreichen Archive vergangener Ereignisse, sodass wir keine native Lösung für Backtests nachrichtenorientierter Ansätze haben. Indem wir diese Daten aus externen Quellen herunterladen und selbst organisieren - sei es in Form von Dateien, Datenbanken oder eingebetteten Ressourcen - erhalten wir die Kontrolle über einen konsistenten Datensatz, der in mehreren Tests wiederverwendet werden kann, um sicherzustellen, dass unsere Strategien jedes Mal die gleichen Bedingungen vorfinden.
Neben der Überwindung von Plattformbeschränkungen bietet die Integration statischer Daten eine Flexibilität, die Live-Feeds nicht bieten können. Der Wirtschaftskalender enthält, wie wir bereits in den Vorgängerversionen gesehen haben, oft wichtige Details wie Daten, Zeiten, Währungen und Auswirkungen, die jedoch nicht immer in einem für die algorithmische Analyse über lange Zeiträume geeigneten Format gespeichert sind. Indem wir diese Informationen manuell strukturieren, können wir sie auf unsere Bedürfnisse zuschneiden - z. B. nach bestimmten Währungen oder wichtigen Ereignissen filtern - und erhalten so tiefere Einblicke in die Art und Weise, wie Nachrichten das Marktverhalten beeinflussen, ohne auf die Verfügbarkeit in Echtzeit angewiesen zu sein.
Darüber hinaus wird dieser Ansatz die Effizienz und Unabhängigkeit erhöhen. Die Erfassung und Speicherung statischer Daten im Vorfeld bedeutet, dass wir während der Tests nicht an die Internetverbindung oder an Dienste von Drittanbietern gebunden sind, wodurch die Variablen, die die Ergebnisse verfälschen könnten, reduziert werden. Außerdem können wir damit seltene oder spezifische Szenarien simulieren, wie z. B. wichtige wirtschaftliche Ankündigungen, indem wir Datensätze kuratieren, die sich über Jahre erstrecken oder sich auf Schlüsselmomente konzentrieren, was mit Live-Systemen oder begrenztem Plattformspeicher nicht so einfach möglich ist. Letztendlich überbrückt die statische Datenintegration die Lücke zwischen den Erkenntnissen aus dem Live-Handel und der Präzision der Backtests und schafft so eine solide Grundlage für die Strategieentwicklung.
Die Datenspeicherung ist ein wichtiger Aspekt, und MQL5 bietet eine breite Palette an Variabilität, von Textformaten (txt), kommagetrennte Werte (CSV), American National Standards Institute (ANSI), Binary (bin), Unicode und auch Datenbankorganisationen wie unten.
Wir werden nicht das einfachste, sondern das bequemste Format verwenden, nämlich das CSV-Format. Auf diese Weise haben wir die Daten bei uns und müssen nicht stundenlang auf den Backtest unserer Strategie warten, was uns viel Zeit und Energie spart. Los geht's.
Implementation in MQL5
Zunächst müssen wir die Datenerfassung und -organisation in einer Weise strukturieren, die unsere bisherige Struktur widerspiegelt. Daher benötigen wir einige Eingaben, die der Nutzer anpassen kann, so wie wir es bereits weiter unten getan haben.
//+------------------------------------------------------------------+ //| MQL5 NEWS CALENDAR PART 7.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://youtube.com/@ForexAlgo-Trader?" #property version "1.00" #property strict //---- Input parameter for start date of event filtering input datetime StartDate = D'2025.03.01'; // Download Start Date //---- Input parameter for end date of event filtering input datetime EndDate = D'2025.03.21'; // Download End Date //---- Input parameter to enable/disable time filtering input bool ApplyTimeFilter = true; //---- Input parameter for hours before event to consider input int HoursBefore = 4; //---- Input parameter for minutes before event to consider input int MinutesBefore = 10; //---- Input parameter for hours after event to consider input int HoursAfter = 1; //---- Input parameter for minutes after event to consider input int MinutesAfter = 5; //---- Input parameter to enable/disable currency filtering input bool ApplyCurrencyFilter = true; //---- Input parameter defining currencies to filter (comma-separated) input string CurrencyFilter = "USD,EUR,GBP,JPY,AUD,NZD,CAD,CHF"; // All 8 major currencies //---- Input parameter to enable/disable impact filtering input bool ApplyImpactFilter = true; //---- Enumeration for event importance filtering options enum ENUM_IMPORTANCE { IMP_NONE = 0, // None IMP_LOW, // Low IMP_MEDIUM, // Medium IMP_HIGH, // High IMP_NONE_LOW, // None,Low IMP_NONE_MEDIUM, // None,Medium IMP_NONE_HIGH, // None,High IMP_LOW_MEDIUM, // Low,Medium IMP_LOW_HIGH, // Low,High IMP_MEDIUM_HIGH, // Medium,High IMP_NONE_LOW_MEDIUM, // None,Low,Medium IMP_NONE_LOW_HIGH, // None,Low,High IMP_NONE_MEDIUM_HIGH, // None,Medium,High IMP_LOW_MEDIUM_HIGH, // Low,Medium,High IMP_ALL // None,Low,Medium,High (default) }; //---- Input parameter for selecting importance filter input ENUM_IMPORTANCE ImportanceFilter = IMP_ALL; // Impact Levels (Default to all)
Hier richten wir die grundlegenden Eingabeparameter und eine Enumeration ein, um anzupassen, wie unser Handelssystem wirtschaftliche Ereignisse für Strategietests verarbeitet. Wir definieren „StartDate“ und „EndDate“ als Datetime-Variablen, die auf den 1. März 2025 bzw. den 21. März 2025 gesetzt werden, um den Bereich für das Herunterladen und Analysieren von Ereignisdaten festzulegen. Um die zeitliche Filterung dieser Ereignisse zu steuern, fügen wir „ApplyTimeFilter“ als booleschen Wert ein, der standardmäßig auf true gesetzt ist, zusammen mit „HoursBefore“ (4 Stunden), „MinutesBefore“ (10 Minuten), „HoursAfter“ (1 Stunde) und „MinutesAfter“ (5 Minuten), die das Zeitfenster für die Berücksichtigung von Ereignissen relativ zu einem bestimmten Balken festlegen.
Für währungsspezifische Analysen führen wir „ApplyCurrencyFilter“ (standardmäßig true) und „CurrencyFilter“ ein, eine Zeichenkette, die alle acht Hauptwährungen auflistet - „USD, EUR, GBP, JPY, AUD, NZD, CAD, CHF“ - um sich auf die relevanten Märkte zu konzentrieren. Wir aktivieren auch die wirkungsbasierte Filterung mit „ApplyImpactFilter“ auf true, unterstützt durch die Enumeration „ENUM_IMPORTANCE“, die flexible Optionen wie „IMP_NONE“, „IMP_LOW“, „IMP_MEDIUM“, „IMP_HIGH“ und Kombinationen bis hin zu „IMP_ALL“, wobei „ImportanceFilter“ standardmäßig auf „IMP_ALL“ eingestellt ist, um alle Auswirkungsstufen zu berücksichtigen. Das Ergebnis ist wie folgt.
Mit den Eingaben müssen wir als Nächstes eine Struktur mit 8 Eingabefeldern deklarieren, die die normale und standardmäßige Struktur für den MQL5-Wirtschaftskalender nachahmt (siehe unten).
Wir erreichen das Format über die folgende Logik.
//---- Structure to hold economic event data struct EconomicEvent { string eventDate; //---- Date of the event string eventTime; //---- Time of the event string currency; //---- Currency affected by the event string event; //---- Event description string importance; //---- Importance level of the event double actual; //---- Actual value of the event double forecast; //---- Forecasted value of the event double previous; //---- Previous value of the event }; //---- Array to store all economic events EconomicEvent allEvents[]; //---- Array for currency filter values string curr_filter[]; //---- Array for importance filter values string imp_filter[];
Zunächst definieren wir die Struktur „EconomicEvent“, um wichtige Ereignisdetails zu kapseln, einschließlich „eventDate“ und „eventTime“ als Strings für den Zeitpunkt des Ereignisses, „currency“ zur Identifizierung des betroffenen Marktes, „event“ für die Beschreibung und „importance“ zur Angabe des Ausmaßes der Auswirkungen sowie „actual“, „forecast“ und „previous“ als Doppelwerte für die numerischen Ergebnisse des Ereignisses.
Um diese Ereignisse zu speichern und zu verarbeiten, erstellen wir drei Arrays: „allEvents“, ein Array der Strukturen „EconomicEvent“, um alle geladenen Ereignisse zu speichern, „curr_filter“ als String-Array, um die in der Eingabe „CurrencyFilter“ angegebenen Währungen zu speichern, und „imp_filter“ als String-Array, um die über „ImportanceFilter“ ausgewählten Wichtigkeitsstufen zu verwalten. Dies entspricht der Standardstruktur, nur dass wir den Abschnitt „Zeitraum“ so verschieben, dass er die Ereignisdaten am Anfang der Struktur enthält. Im nächsten Schritt müssen wir die Filter aus den Nutzereingaben abrufen, sie so interpretieren, dass der Computer sie versteht, und sie initialisieren. Um den Code zu modularisieren, werden wir Funktionen verwenden.
//---- Function to initialize currency and impact filters void InitializeFilters() { //---- Currency Filter Section //---- Check if currency filter is enabled and has content if (ApplyCurrencyFilter && StringLen(CurrencyFilter) > 0) { //---- Split the currency filter string into array int count = StringSplit(CurrencyFilter, ',', curr_filter); //---- Loop through each currency filter entry for (int i = 0; i < ArraySize(curr_filter); i++) { //---- Temporary variable for trimming string temp = curr_filter[i]; //---- Remove leading whitespace StringTrimLeft(temp); //---- Remove trailing whitespace StringTrimRight(temp); //---- Assign trimmed value back to array curr_filter[i] = temp; //---- Print currency filter for debugging Print("Currency filter [", i, "]: '", curr_filter[i], "'"); } } else if (ApplyCurrencyFilter) { //---- Warn if currency filter is enabled but empty Print("Warning: CurrencyFilter is empty, no currency filtering applied"); //---- Resize array to zero if no filter applied ArrayResize(curr_filter, 0); } }
Hier richten wir den Teil der Währungsfilterung der Funktion „InitializeFilters“ in unserem System ein, um eine effektive Ereignisanalyse während der Strategietests vorzubereiten. Zunächst wird geprüft, ob die Variable „ApplyCurrencyFilter“ wahr ist und ob die Zeichenkette „CurrencyFilter“ mit der Funktion StringLen einen Inhalt hat. Ist dies der Fall, wird die kommagetrennte Zeichenkette „CurrencyFilter“ (z. B. „USD, EUR, GBP“) mit der Funktion StringSplit in das Array „curr_filter“ aufgeteilt, wobei die Anzahl der Elemente in „count“ erfasst wird.
Als Nächstes durchlaufen wir jedes Element in „curr_filter“ mit einer for-Schleife, weisen es der temporären Zeichenkette „temp“ zu, bereinigen sie durch Entfernen führender und nachfolgender Leerzeichen mit den Funktionen StringTrimLeft und StringTrimRight, aktualisieren dann „curr_filter“ mit dem bereinigten Wert und zeigen ihn zu Debugging-Zwecken über die Funktion Print an (z. B. „Currency filter [0]: USD'“). Wenn jedoch „ApplyCurrencyFilter“ aktiviert ist, aber „CurrencyFilter“ leer ist, verwenden wir die Funktion „Print“, um eine Warnung auszugeben: „Warning: CurrencyFilter is empty, no currency filtering applied“ (Warnung: CurrencyFilter ist leer, keine Währungsfilterung angewendet) und die Größe des Arrays mit der Funktion ArrayResize auf Null setzen, um die Filterung zu deaktivieren. Durch diese sorgfältige Initialisierung wird sichergestellt, dass der Währungsfilter zuverlässig von den Nutzereingaben abgeleitet wird, was eine genaue Ereignisverarbeitung im Strategy Tester unterstützt. Für den Impact-Filter wenden wir eine ähnliche kuratierte Logik an.
//---- Impact Filter Section (using enum) //---- Check if impact filter is enabled if (ApplyImpactFilter) { //---- Switch based on selected importance filter switch (ImportanceFilter) { case IMP_NONE: //---- Resize array for single importance level ArrayResize(imp_filter, 1); //---- Set importance to "None" imp_filter[0] = "None"; break; case IMP_LOW: //---- Resize array for single importance level ArrayResize(imp_filter, 1); //---- Set importance to "Low" imp_filter[0] = "Low"; break; case IMP_MEDIUM: //---- Resize array for single importance level ArrayResize(imp_filter, 1); //---- Set importance to "Medium" imp_filter[0] = "Medium"; break; case IMP_HIGH: //---- Resize array for single importance level ArrayResize(imp_filter, 1); //---- Set importance to "High" imp_filter[0] = "High"; break; case IMP_NONE_LOW: //---- Resize array for two importance levels ArrayResize(imp_filter, 2); //---- Set first importance level imp_filter[0] = "None"; //---- Set second importance level imp_filter[1] = "Low"; break; case IMP_NONE_MEDIUM: //---- Resize array for two importance levels ArrayResize(imp_filter, 2); //---- Set first importance level imp_filter[0] = "None"; //---- Set second importance level imp_filter[1] = "Medium"; break; case IMP_NONE_HIGH: //---- Resize array for two importance levels ArrayResize(imp_filter, 2); //---- Set first importance level imp_filter[0] = "None"; //---- Set second importance level imp_filter[1] = "High"; break; case IMP_LOW_MEDIUM: //---- Resize array for two importance levels ArrayResize(imp_filter, 2); //---- Set first importance level imp_filter[0] = "Low"; //---- Set second importance level imp_filter[1] = "Medium"; break; case IMP_LOW_HIGH: //---- Resize array for two importance levels ArrayResize(imp_filter, 2); //---- Set first importance level imp_filter[0] = "Low"; //---- Set second importance level imp_filter[1] = "High"; break; case IMP_MEDIUM_HIGH: //---- Resize array for two importance levels ArrayResize(imp_filter, 2); //---- Set first importance level imp_filter[0] = "Medium"; //---- Set second importance level imp_filter[1] = "High"; break; case IMP_NONE_LOW_MEDIUM: //---- Resize array for three importance levels ArrayResize(imp_filter, 3); //---- Set first importance level imp_filter[0] = "None"; //---- Set second importance level imp_filter[1] = "Low"; //---- Set third importance level imp_filter[2] = "Medium"; break; case IMP_NONE_LOW_HIGH: //---- Resize array for three importance levels ArrayResize(imp_filter, 3); //---- Set first importance level imp_filter[0] = "None"; //---- Set second importance level imp_filter[1] = "Low"; //---- Set third importance level imp_filter[2] = "High"; break; case IMP_NONE_MEDIUM_HIGH: //---- Resize array for three importance levels ArrayResize(imp_filter, 3); //---- Set first importance level imp_filter[0] = "None"; //---- Set second importance level imp_filter[1] = "Medium"; //---- Set third importance level imp_filter[2] = "High"; break; case IMP_LOW_MEDIUM_HIGH: //---- Resize array for three importance levels ArrayResize(imp_filter, 3); //---- Set first importance level imp_filter[0] = "Low"; //---- Set second importance level imp_filter[1] = "Medium"; //---- Set third importance level imp_filter[2] = "High"; break; case IMP_ALL: //---- Resize array for all importance levels ArrayResize(imp_filter, 4); //---- Set first importance level imp_filter[0] = "None"; //---- Set second importance level imp_filter[1] = "Low"; //---- Set third importance level imp_filter[2] = "Medium"; //---- Set fourth importance level imp_filter[3] = "High"; break; } //---- Loop through impact filter array to print values for (int i = 0; i < ArraySize(imp_filter); i++) { //---- Print each impact filter value Print("Impact filter [", i, "]: '", imp_filter[i], "'"); } } else { //---- Notify if impact filter is disabled Print("Impact filter disabled"); //---- Resize impact filter array to zero ArrayResize(imp_filter, 0); }
Bei der Filterung der Auswirkungen wird zunächst geprüft, ob die Variable „ApplyImpactFilter“ wahr ist. Ist dies der Fall, wird eine Switch-Anweisung auf der Grundlage der Enumeration „ImportanceFilter“ verwendet, um zu bestimmen, welche Auswirkungsstufen in das Array „imp_filter“ aufgenommen werden sollen. Für einstufige Optionen wie „IMP_NONE“, „IMP_LOW“, „IMP_MEDIUM“ oder „IMP_HIGH“ ändern wir die Größe von „imp_filter“ mit der Funktion ArrayResize auf 1 und weisen der entsprechenden Zeichenkette zu (z.B., „imp_filter[0] = 'None'“); für zweistufige Optionen wie „IMP_NONE_LOW“ oder „IMP_MEDIUM_HIGH“ ändern wir die Größe auf 2 und setzen zwei Werte (z.B., „imp_filter[0] = 'None', imp_filter[1] = 'Low'“); für dreistufige Optionen wie „IMP_LOW_MEDIUM_HIGH“ ändern wir die Größe auf 3, und für „IMP_ALL“ ändern wir die Größe auf 4, die „None“, „Low“, „Medium“ und „High“ abdecken.
Nach dem Setzen des Arrays durchlaufen wir „imp_filter“ in einer Schleife, wobei wir die Funktion ArraySize verwenden, um die Größe des Arrays zu bestimmen, und geben jeden Wert mit der Funktion Print zur Fehlersuche aus (z. B. „Impact filter [0]: 'None'“). Wenn „ApplyImpactFilter“ falsch ist, benachrichtigen wir den Nutzer mit der Funktion „Print“ - „Impact filter disabled“ - und setzen „imp_filter“ auf Null.
Damit müssen wir nun die Funktion in OnInit aufrufen.
int OnInit() { //---- Initialize filters InitializeFilters(); //---- Return successful initialization return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { //---- Print termination reason Print("EA terminated, reason: ", reason); }
Wir rufen die Funktion in OnInit auf und geben den Grund für den Programmabbruch auch in OnDeinit aus. Hier ist das Ergebnis.
Aus dem Bild können wir ersehen, dass wir die Filtereingänge korrekt initialisiert, dekodiert und gespeichert haben. Jetzt müssen wir nur noch die Daten aus dem Live-Stream beziehen und speichern. Hier besteht die Logik darin, dass wir das Programm zunächst einmal im Live-Modus ausführen müssen, damit es die Daten des MQL5-Wirtschaftskalender herunterladen kann, um sie dann im Test zu laden und zu verwenden. Hier ist die Initialisierungslogik.
//---- Check if not running in tester mode if (!MQLInfoInteger(MQL_TESTER)) { //---- Validate date range if (StartDate >= EndDate) { //---- Print error for invalid date range Print("Error: StartDate (", TimeToString(StartDate), ") must be earlier than EndDate (", TimeToString(EndDate), ")"); //---- Return initialization failure return(INIT_PARAMETERS_INCORRECT); } //---- Array to hold calendar values MqlCalendarValue values[]; //---- Fetch calendar data for date range if (!CalendarValueHistory(values, StartDate, EndDate)) { //---- Print error if calendar data fetch fails Print("Error fetching calendar data: ", GetLastError()); //---- Return initialization failure return(INIT_FAILED); } //---- Array to hold economic events EconomicEvent events[]; //---- Counter for events int eventCount = 0; //---- Loop through calendar values for (int i = 0; i < ArraySize(values); i++) { //---- Structure for event details MqlCalendarEvent eventDetails; //---- Fetch event details by ID if (!CalendarEventById(values[i].event_id, eventDetails)) continue; //---- Structure for country details MqlCalendarCountry countryDetails; //---- Fetch country details by ID if (!CalendarCountryById(eventDetails.country_id, countryDetails)) continue; //---- Structure for value details MqlCalendarValue value; //---- Fetch value details by ID if (!CalendarValueById(values[i].id, value)) continue; //---- Resize events array for new event ArrayResize(events, eventCount + 1); //---- Convert event time to string string dateTimeStr = TimeToString(values[i].time, TIME_DATE | TIME_MINUTES); //---- Extract date from datetime string events[eventCount].eventDate = StringSubstr(dateTimeStr, 0, 10); //---- Extract time from datetime string events[eventCount].eventTime = StringSubstr(dateTimeStr, 11, 5); //---- Assign currency from country details events[eventCount].currency = countryDetails.currency; //---- Assign event name events[eventCount].event = eventDetails.name; //---- Map importance level from enum to string events[eventCount].importance = (eventDetails.importance == 0) ? "None" : // CALENDAR_IMPORTANCE_NONE (eventDetails.importance == 1) ? "Low" : // CALENDAR_IMPORTANCE_LOW (eventDetails.importance == 2) ? "Medium" : // CALENDAR_IMPORTANCE_MODERATE "High"; // CALENDAR_IMPORTANCE_HIGH //---- Assign actual value events[eventCount].actual = value.GetActualValue(); //---- Assign forecast value events[eventCount].forecast = value.GetForecastValue(); //---- Assign previous value events[eventCount].previous = value.GetPreviousValue(); //---- Increment event count eventCount++; } }
Hier wird der Abruf der Live-Modus-Daten innerhalb der Funktion OnInit unseres Programms abgewickelt, um sicherzustellen, dass wirtschaftliche Ereignisdaten für die spätere Verwendung bei Strategietests gesammelt werden. Wir beginnen mit der Prüfung, ob sich das System nicht im Testermodus befindet, indem wir die Funktion MQLInfoInteger mit MQL_TESTER verwenden. Wenn dies der Fall ist, prüfen wir, ob „StartDate“ vor „EndDate“ liegt, geben einen Fehler aus und geben bei Ungültigkeit INIT_PARAMETERS_INCORRECT zurück. Als Nächstes deklarieren wir ein Datenbankvom Typ MqlCalendarValue mit dem Namen „values“ und holen die Kalenderdaten zwischen „StartDate“ und „EndDate“ mit der Funktion CalendarValueHistory, wobei wir mit GetLastError einen Fehler ausgeben und bei einem Fehlschlag „INIT_FAILED“ zurückgeben.
Dann initialisieren wir das Array „events“ vom Typ „EconomicEvent“ und eine Ganzzahl „eventCount“, um die Ereignisse zu verfolgen, wobei wir mit der Funktion ArraySize eine Schleife durch „values“ ziehen. Für jede Iteration holen wir Ereignisdetails in die Struktur MqlCalendarEvent mit dem Namen „eventDetails“ mit der Funktion CalendarEventById, Länderdetails in eine MqlCalendarCountry-Struktur „countryDetails“ mit CalendarCountryById, und Wertangaben in eine „MqlCalendarValue“-Struktur „value“ über „CalendarValueById“, wobei übersprungen wird, wenn ein Abruf fehlschlägt. Wir passen die Größe von „events“ mit der Funktion ArrayResize an, konvertieren die Ereigniszeit in einen String „dateTimeStr“ mit der Funktion TimeToString und extrahieren „eventDate“ und „eventTime“ mit der Funktion StringSubstr, weisen „currency“ aus „countryDetails“, „event“ aus „eventDetails.name“ zu und ordnen „importance“ von numerischen Werten auf Strings (“None“, „Low“, „Medium“, „High“) zu. Schließlich setzen wir „actual“, „forecast“ und „previous“ mit „value“-Methoden und erhöhen „eventCount“, um einen umfassenden Ereignisdatensatz für die Verarbeitung im Live-Modus zu erstellen. Nun brauchen wir eine Funktion, die die Speicherung dieser Informationen in einer Datei übernimmt.
//---- Function to write events to a CSV file void WriteToCSV(string fileName, EconomicEvent &events[]) { //---- Open file for writing in CSV format int handle = FileOpen(fileName, FILE_WRITE | FILE_CSV, ','); //---- Check if file opening failed if (handle == INVALID_HANDLE) { //---- Print error message with last error code Print("Error creating file: ", GetLastError()); //---- Exit function on failure return; } //---- Write CSV header row FileWrite(handle, "Date", "Time", "Currency", "Event", "Importance", "Actual", "Forecast", "Previous"); //---- Loop through all events to write to file for (int i = 0; i < ArraySize(events); i++) { //---- Write event data to CSV file FileWrite(handle, events[i].eventDate, events[i].eventTime, events[i].currency, events[i].event, events[i].importance, DoubleToString(events[i].actual, 2), DoubleToString(events[i].forecast, 2), DoubleToString(events[i].previous, 2)); //---- Print event details for debugging Print("Writing event ", i, ": ", events[i].eventDate, ", ", events[i].eventTime, ", ", events[i].currency, ", ", events[i].event, ", ", events[i].importance, ", ", DoubleToString(events[i].actual, 2), ", ", DoubleToString(events[i].forecast, 2), ", ", DoubleToString(events[i].previous, 2)); } //---- Flush data to file FileFlush(handle); //---- Close the file handle FileClose(handle); //---- Print confirmation of data written Print("Data written to ", fileName, " with ", ArraySize(events), " events."); //---- Verify written file by reading it back int verifyHandle = FileOpen(fileName, FILE_READ | FILE_TXT); //---- Check if verification file opening succeeded if (verifyHandle != INVALID_HANDLE) { //---- Read entire file content string content = FileReadString(verifyHandle, (int)FileSize(verifyHandle)); //---- Print file content for verification Print("File content after writing (size: ", FileSize(verifyHandle), " bytes):\n", content); //---- Close verification file handle FileClose(verifyHandle); } }
Hier verwenden wir die Funktion „WriteToCSV“, um systematisch wirtschaftliche Ereignisdaten in eine CSV-Datei zu exportieren. Zunächst öffnen wir die durch „fileName“ angegebene Datei mit der Funktion FileOpen im Modus „FILE_WRITE | FILE_CSV“ mit einem Komma als Trennzeichen und speichern das Ergebnis in „handle“; Wenn dies fehlschlägt und „handle“ gleich „INVALID_HANDLE“ ist, verwenden wir die Funktion „Print“, um eine Fehlermeldung mit dem GetLastError-Code auszugeben und die Funktion mit „return“ zu beenden. Sobald die Datei geöffnet ist, schreiben wir mit der Funktion FileWrite eine Kopfzeile und definieren die Spalten „Date“, „Time“, „Currency“, „Event“, „Importance“, „Actual“, „Prognose“ und „Previous“, um die Daten zu organisieren.
Dann durchlaufen wir das Array „events“, bestimmen seine Größe mit der Funktion ArraySize und rufen für jedes Ereignis „FileWrite“ auf, um seine Eigenschaften aufzuzeichnen - „eventDate“, „eventTime“, „currency“, „event“, „importance“und die numerischen Werte „actual“, „forecast“ und „previous“, die mit der Funktion DoubleToString in Zeichenketten umgewandelt werden (formatiert auf 2 Dezimalstellen), während diese Details gleichzeitig mit der Funktion „Print“ zu Debugging-Zwecken protokolliert werden.
Nach Abschluss der Schleife stellen wir sicher, dass alle Daten in die Datei geschrieben wurden, indem wir die Funktion FileFlush für „handle“ aufrufen, dann die Datei mit der Funktion FileClose schließen und den Erfolg der Operation mit einer Meldung bestätigen.
Um die Ausgabe zu überprüfen, öffnen wir die Datei erneut im Lesemodus mit „FILE_READ | FILE_TXT“ und speichern diesen Handle in „verifyHandle“; bei Erfolg lesen wir den vollständigen Inhalt mit der Funktion FileReadString auf der Grundlage der Bytegröße aus FileSize in „content“ ein und geben ihn zur Überprüfung aus (z. B. „File content after writing (size: X bytes):\n"content""), und schließen. Dieser gründliche Prozess garantiert, dass die Ereignisdaten korrekt gespeichert werden und überprüft werden können, was sie zu einer zuverlässigen Quelle für die Backtests im Strategy Tester macht. Jetzt können wir die Funktion für den Datensicherungsprozess verwenden.
//---- Define file path for CSV string fileName = "Database\\EconomicCalendar.csv"; //---- Check if file exists and print appropriate message if (!FileExists(fileName)) Print("Creating new file: ", fileName); else Print("Overwriting existing file: ", fileName); //---- Write events to CSV file WriteToCSV(fileName, events); //---- Print instructions for tester mode Print("Live mode: Data written. To use in tester, manually add ", fileName, " as a resource and recompile.");
Um die Datenverarbeitung im Live-Modus abzuschließen, haben wir „fileName“ auf „Database\EconomicCalendar.csv“ gesetzt und die nutzerdefinierte Funktion „FileExists“ verwendet, um den Status zu überprüfen. Anschließend rufen wir die Funktion „WriteToCSV“ mit den Eingaben „fileName“ und „events“ auf, um die Daten zu speichern, und drucken die Anweisungen mit „Print“ aus - “Live-Modus: Data written. To use in tester, add "fileName" as a resource and recompile." (Live-Modus: Daten geschrieben. Zur Verwendung im Tester „fileName“ als Ressource einbinden und neu kompilieren.) - für die Verwendung im Tester. Das Codefragment der nutzerdefinierten Funktion zur Überprüfung des Vorhandenseins der Datei lautet wie folgt.
//---- Function to check if a file exists bool FileExists(string fileName) { //---- Open file in read mode to check existence int handle = FileOpen(fileName, FILE_READ | FILE_CSV); //---- Check if file opened successfully if (handle != INVALID_HANDLE) { //---- Close the file handle FileClose(handle); //---- Return true if file exists return true; } //---- Return false if file doesn't exist return false; }
In der Funktion „FileExists“, mit der das Vorhandensein von Dateien für Strategietests geprüft wird, wird „fileName“ mit der Funktion FileOpen im Modus „FILE_READ | FILE_CSV“ geöffnet, und wenn „handle“ nicht „INVALID_HANDLE“ ist, wird die Datei mit FileClose geschlossen und „true“ zurückgegeben; andernfalls wird „false“ zurückgegeben. Damit wird der Dateistatus für die Datenverarbeitung bestätigt. Nach der Ausführung im Live-Modus ist das Ergebnis wie folgt.
Aus dem Bild können wir ersehen, dass die Daten gespeichert sind und wir darauf zugreifen können.
Um die Daten im Testermodus zu verwenden, müssen wir sie in der ausführbaren Datei speichern. Dazu fügen wir sie als Ressource hinzu.
//---- Define resource file for economic calendar data #resource "\\Files\\Database\\EconomicCalendar.csv" as string EconomicCalendarData
Hier integrieren wir die statische Datenquelle in unser Programm, um die Strategieprüfung zu unterstützen. Mit der Direktive #resource betten wir die Datei „\Files\Database\EconomicCalendar.csv“ ein und weisen sie der String-Variablen „EconomicCalendarData“ zu. Auf diese Weise kann auf die Datei zugegriffen werden, sodass wir uns keine Sorgen machen müssen, selbst wenn sie gelöscht wird. Wir können nun eine Funktion zum Laden des Inhalts aus der Datei einrichten.
//---- Function to load events from resource file bool LoadEventsFromResource() { //---- Get data from resource string fileData = EconomicCalendarData; //---- Print raw resource content for debugging Print("Raw resource content (size: ", StringLen(fileData), " bytes):\n", fileData); //---- Array to hold lines from resource string lines[]; //---- Split resource data into lines int lineCount = StringSplit(fileData, '\n', lines); //---- Check if resource has valid data if (lineCount <= 1) { //---- Print error if no data lines found Print("Error: No data lines found in resource! Raw data: ", fileData); //---- Return false on failure return false; } //---- Reset events array ArrayResize(allEvents, 0); //---- Index for event array int eventIndex = 0; //---- Loop through each line (skip header at i=0) for (int i = 1; i < lineCount; i++) { //---- Check for empty lines if (StringLen(lines[i]) == 0) { //---- Print message for skipped empty line Print("Skipping empty line ", i); //---- Skip to next iteration continue; } //---- Array to hold fields from each line string fields[]; //---- Split line into fields int fieldCount = StringSplit(lines[i], ',', fields); //---- Print line details for debugging Print("Line ", i, ": ", lines[i], " (field count: ", fieldCount, ")"); //---- Check if line has minimum required fields if (fieldCount < 8) { //---- Print error for malformed line Print("Malformed line ", i, ": ", lines[i], " (field count: ", fieldCount, ")"); //---- Skip to next iteration continue; } //---- Extract date from field string dateStr = fields[0]; //---- Extract time from field string timeStr = fields[1]; //---- Extract currency from field string currency = fields[2]; //---- Extract event description (handle commas in event name) string event = fields[3]; //---- Combine multiple fields if event name contains commas for (int j = 4; j < fieldCount - 4; j++) { event += "," + fields[j]; } //---- Extract importance from field string importance = fields[fieldCount - 4]; //---- Extract actual value from field string actualStr = fields[fieldCount - 3]; //---- Extract forecast value from field string forecastStr = fields[fieldCount - 2]; //---- Extract previous value from field string previousStr = fields[fieldCount - 1]; //---- Convert date and time to datetime format datetime eventDateTime = StringToTime(dateStr + " " + timeStr); //---- Check if datetime conversion failed if (eventDateTime == 0) { //---- Print error for invalid datetime Print("Error: Invalid datetime conversion for line ", i, ": ", dateStr, " ", timeStr); //---- Skip to next iteration continue; } //---- Resize events array for new event ArrayResize(allEvents, eventIndex + 1); //---- Assign event date allEvents[eventIndex].eventDate = dateStr; //---- Assign event time allEvents[eventIndex].eventTime = timeStr; //---- Assign event currency allEvents[eventIndex].currency = currency; //---- Assign event description allEvents[eventIndex].event = event; //---- Assign event importance allEvents[eventIndex].importance = importance; //---- Convert and assign actual value allEvents[eventIndex].actual = StringToDouble(actualStr); //---- Convert and assign forecast value allEvents[eventIndex].forecast = StringToDouble(forecastStr); //---- Convert and assign previous value allEvents[eventIndex].previous = StringToDouble(previousStr); //---- Print loaded event details Print("Loaded event ", eventIndex, ": ", dateStr, " ", timeStr, ", ", currency, ", ", event); //---- Increment event index eventIndex++; } //---- Print total events loaded Print("Loaded ", eventIndex, " events from resource into array."); //---- Return success if events were loaded return eventIndex > 0; }
Wir definieren die Funktion „LoadEventsFromResource“, um wirtschaftliche Ereignisdaten aus der eingebetteten Ressource für Strategietests zu laden. Wir weisen die Ressource „EconomicCalendarData“ der Datei „fileData“ zu und geben ihren Rohinhalt mit der Funktion „Print“ aus, einschließlich der Größe über die Funktion StringLen, um sie zu debuggen. Wir teilen „fileData“ in das Array „lines“ auf, indem wir die Funktion StringSplit mit einem Zeilenbegrenzer verwenden und die Anzahl in „lineCount“ speichern. Wenn „lineCount“ 1 oder weniger ist, geben wir einen Fehler aus und geben false zurück. Wir setzen das Array „allEvents“ mit der Funktion ArrayResize auf Null zurück und initialisieren „eventIndex“ mit 0, dann durchlaufen wir „lines“ in einer Schleife, beginnend bei Index 1 (wobei wir die Kopfzeile überspringen). Für jede Zeile wird mit StringLen geprüft, ob sie leer ist, und wenn ja, wird eine Meldung ausgegeben, dass sie übersprungen wurde, und fahren fort; andernfalls wird sie mit Kommas in „fields“ (Felder) unterteilt.
Wenn „fieldCount“ kleiner als 8 ist, wird ein Fehler ausgegeben und übersprungen; andernfalls werden „dateStr“, „timeStr“ und „currency“ extrahiert und „event“ durch Verkettung von Feldern (mit Kommas) in einer Schleife und erfassen dann „importance“, „actualStr“, „forecastStr“ und „previousStr“. Wir konvertieren „datestr“ und „timestr“ in „eventdatetime“ mit der funktion „StringToTime“, wobei wir mit einer Fehlermeldung abbrechen, wenn die Konvertierung fehlschlägt, dann ändern wir die Größe von „allevents“ mit „arrayresize“ und alle Werte werden mit „StringToDouble“ in Zahlen umgewandelt. Das Ereignis wird ausgedruckt und „eventindex“ erhöht. Schließlich geben wir den gesamten „eventIndex“ aus und geben „true“ zurück, wenn Ereignisse geladen wurden, sodass die Daten für den Strategietester bereitstehen. Wir können diese Funktion nun bei der Initialisierung im Testermodus aufrufen.
else { //---- Check if resource data is empty in tester mode if (StringLen(EconomicCalendarData) == 0) { //---- Print error for empty resource Print("Error: Resource EconomicCalendarData is empty. Please run in live mode, add the file as a resource, and recompile."); //---- Return initialization failure return(INIT_FAILED); } //---- Print message for tester mode Print("Running in Strategy Tester, using embedded resource: Database\\EconomicCalendar.csv"); //---- Load events from resource if (!LoadEventsFromResource()) { //---- Print error if loading fails Print("Failed to load events from resource."); //---- Return initialization failure return(INIT_FAILED); } }
Wenn „EconomicCalendarData“ per StringLen leer ist, wird ein Fehler gedruckt und „INIT_FAILED“ zurückgegeben; andernfalls wird mit der Funktion „Print“ eine Meldung im Testermodus gedruckt und „LoadEventsFromResource“ aufgerufen, wobei „INIT_FAILED“ mit einem Fehler zurückgegeben wird, wenn der Vorgang fehlschlägt. Dadurch wird sichergestellt, dass unsere Ereignisdaten für die Backtests korrekt geladen werden. Hier ist das Ergebnis.
Anhand des Bildes können wir bestätigen, dass die Daten erfolgreich geladen wurden. Auch verfälschte Daten und das Überspringen von Leerzeilen wird korrekt gehandhabt. Wir können nun zu OnTick übergehen und die Datenverarbeitung simulieren, als ob wir uns im Live-Modus befänden. Dazu wollen wir die Daten pro Balken und nicht bei jedem Tick verarbeiten.
//---- Variable to track last bar time datetime lastBarTime = 0; //---- Tick event handler void OnTick() { //---- Get current bar time datetime currentBarTime = iTime(_Symbol, _Period, 0); //---- Check if bar time has changed if (currentBarTime != lastBarTime) { //---- Update last bar time lastBarTime = currentBarTime; //---- } }
Wir definieren „lastBarTime“ als eine „datetime“-Variable, die mit 0 initialisiert wird, um die Zeit des vorhergehenden Balkens zu erfassen. In der Funktion OnTick wird die Zeit des aktuellen Balkens mit der Funktion iTime unter Verwendung von _Symbol, _Period und dem Balkenindex 0 abgerufen und in „currentBarTime“ gespeichert; wenn sich „currentBarTime“ von „lastBarTime“ unterscheidet, wird „lastBarTime“ auf „currentBarTime“ aktualisiert, um sicherzustellen, dass das System auf neue Balken für die Ereignisverarbeitung reagiert. Wir können dann eine Funktion definieren, um die Live-Simulationsdaten in einem ähnlichen Format zu verarbeiten, wie wir es in der vorherigen Version getan haben (siehe unten).
//---- Function to filter and print economic events void FilterAndPrintEvents(datetime barTime) { //---- Get total number of events int totalEvents = ArraySize(allEvents); //---- Print total events considered Print("Total considered data size: ", totalEvents, " events"); //---- Check if there are events to filter if (totalEvents == 0) { //---- Print message if no events loaded Print("No events loaded to filter."); //---- Exit function return; } //---- Array to store filtered events EconomicEvent filteredEvents[]; //---- Counter for filtered events int filteredCount = 0; //---- Variables for time range datetime timeBefore, timeAfter; //---- Apply time filter if enabled if (ApplyTimeFilter) { //---- Structure for bar time MqlDateTime barStruct; //---- Convert bar time to structure TimeToStruct(barTime, barStruct); //---- Calculate time before event MqlDateTime timeBeforeStruct = barStruct; //---- Subtract hours before timeBeforeStruct.hour -= HoursBefore; //---- Subtract minutes before timeBeforeStruct.min -= MinutesBefore; //---- Adjust for negative minutes if (timeBeforeStruct.min < 0) { timeBeforeStruct.min += 60; timeBeforeStruct.hour -= 1; } //---- Adjust for negative hours if (timeBeforeStruct.hour < 0) { timeBeforeStruct.hour += 24; timeBeforeStruct.day -= 1; } //---- Convert structure to datetime timeBefore = StructToTime(timeBeforeStruct); //---- Calculate time after event MqlDateTime timeAfterStruct = barStruct; //---- Add hours after timeAfterStruct.hour += HoursAfter; //---- Add minutes after timeAfterStruct.min += MinutesAfter; //---- Adjust for minutes overflow if (timeAfterStruct.min >= 60) { timeAfterStruct.min -= 60; timeAfterStruct.hour += 1; } //---- Adjust for hours overflow if (timeAfterStruct.hour >= 24) { timeAfterStruct.hour -= 24; timeAfterStruct.day += 1; } //---- Convert structure to datetime timeAfter = StructToTime(timeAfterStruct); //---- Print time range for debugging Print("Bar time: ", TimeToString(barTime), ", Time range: ", TimeToString(timeBefore), " to ", TimeToString(timeAfter)); } else { //---- Print message if no time filter applied Print("Bar time: ", TimeToString(barTime), ", No time filter applied, using StartDate to EndDate only."); //---- Set time range to date inputs timeBefore = StartDate; timeAfter = EndDate; } //---- Loop through all events for filtering for (int i = 0; i < totalEvents; i++) { //---- Convert event date and time to datetime datetime eventDateTime = StringToTime(allEvents[i].eventDate + " " + allEvents[i].eventTime); //---- Check if event is within date range bool inDateRange = (eventDateTime >= StartDate && eventDateTime <= EndDate); //---- Skip if not in date range if (!inDateRange) continue; //---- Time Filter Check //---- Check if event is within time range if filter applied bool timeMatch = !ApplyTimeFilter || (eventDateTime >= timeBefore && eventDateTime <= timeAfter); //---- Skip if time doesn't match if (!timeMatch) continue; //---- Print event details if time passes Print("Event ", i, ": Time passes (", allEvents[i].eventDate, " ", allEvents[i].eventTime, ") - ", "Currency: ", allEvents[i].currency, ", Event: ", allEvents[i].event, ", Importance: ", allEvents[i].importance, ", Actual: ", DoubleToString(allEvents[i].actual, 2), ", Forecast: ", DoubleToString(allEvents[i].forecast, 2), ", Previous: ", DoubleToString(allEvents[i].previous, 2)); //---- Currency Filter Check //---- Default to match if filter disabled bool currencyMatch = !ApplyCurrencyFilter; //---- Apply currency filter if enabled if (ApplyCurrencyFilter && ArraySize(curr_filter) > 0) { //---- Initially set to no match currencyMatch = false; //---- Check each currency in filter for (int j = 0; j < ArraySize(curr_filter); j++) { //---- Check if event currency matches filter if (allEvents[i].currency == curr_filter[j]) { //---- Set match to true if found currencyMatch = true; //---- Exit loop on match break; } } //---- Skip if currency doesn't match if (!currencyMatch) continue; } //---- Print event details if currency passes Print("Event ", i, ": Currency passes (", allEvents[i].currency, ") - ", "Date: ", allEvents[i].eventDate, " ", allEvents[i].eventTime, ", Event: ", allEvents[i].event, ", Importance: ", allEvents[i].importance, ", Actual: ", DoubleToString(allEvents[i].actual, 2), ", Forecast: ", DoubleToString(allEvents[i].forecast, 2), ", Previous: ", DoubleToString(allEvents[i].previous, 2)); //---- Impact Filter Check //---- Default to match if filter disabled bool impactMatch = !ApplyImpactFilter; //---- Apply impact filter if enabled if (ApplyImpactFilter && ArraySize(imp_filter) > 0) { //---- Initially set to no match impactMatch = false; //---- Check each importance in filter for (int k = 0; k < ArraySize(imp_filter); k++) { //---- Check if event importance matches filter if (allEvents[i].importance == imp_filter[k]) { //---- Set match to true if found impactMatch = true; //---- Exit loop on match break; } } //---- Skip if importance doesn't match if (!impactMatch) continue; } //---- Print event details if impact passes Print("Event ", i, ": Impact passes (", allEvents[i].importance, ") - ", "Date: ", allEvents[i].eventDate, " ", allEvents[i].eventTime, ", Currency: ", allEvents[i].currency, ", Event: ", allEvents[i].event, ", Actual: ", DoubleToString(allEvents[i].actual, 2), ", Forecast: ", DoubleToString(allEvents[i].forecast, 2), ", Previous: ", DoubleToString(allEvents[i].previous, 2)); //---- Add event to filtered array ArrayResize(filteredEvents, filteredCount + 1); //---- Assign event to filtered array filteredEvents[filteredCount] = allEvents[i]; //---- Increment filtered count filteredCount++; } //---- Print summary of filtered events Print("After ", (ApplyTimeFilter ? "time filter" : "date range filter"), ApplyCurrencyFilter ? " and currency filter" : "", ApplyImpactFilter ? " and impact filter" : "", ": ", filteredCount, " events remaining."); //---- Check if there are filtered events to print if (filteredCount > 0) { //---- Print header for filtered events Print("Filtered Events at Bar Time: ", TimeToString(barTime)); //---- Print filtered events array ArrayPrint(filteredEvents, 2, " | "); } else { //---- Print message if no events found Print("No events found within the specified range."); } }
Hier konstruieren wir die Funktion „FilterAndPrintEvents“, um wirtschaftliche Ereignisse, die für einen bestimmten Balken relevant sind, zu filtern und anzuzeigen. Wir beginnen mit der Berechnung von „totalEvents“ mit der Funktion ArraySize auf „allEvents“ und drucken es aus; wenn es Null ist, beenden wir mit „return“. Wir initialisieren „filteredEvents“ als „EconomicEvent“-Array und „filteredCount“ mit 0 und definieren dann „timeBefore“ und „timeAfter“ für die zeitliche Filterung. Wenn „ApplyTimeFilter“ wahr ist, wandeln wir „barTime“ mit der Funktion TimeToStruct in „barStruct“ um, passen „timeBeforeStruct“ durch Subtraktion von „HoursBefore“ und „MinutesBefore“ (Korrektur negativer Werte) und „timeAfterStruct“ durch Addition von „HoursAfter“ und „MinutesAfter“ (Korrektur von Überläufen) anpassen, beide mit der Funktion StructToTime in „datetime“ konvertieren und den Bereich ausdrucken; andernfalls setzen wir sie auf „StartDate“ und „EndDate“ und drucken eine Meldung ohne Filter.
Wir durchlaufen „allEvents“ mit „totalEvents“, konvertieren jedes „eventDate“ und „eventTime“ in „eventDateTime“ mit StringToTime, prüfen, ob es innerhalb von „StartDate“ und „EndDate“ für „inDateRange“ liegt, und überspringen es, wenn nicht. Für die Zeitfilterung testen wir „timeMatch“ mit „ApplyTimeFilter“ und dem Bereich und drucken die Details, wenn sie übereinstimmen; für die Währung setzen wir „currencyMatch“ basierend auf „ApplyCurrencyFilter“ und „curr_filter“ über die Funktion ArraySize und eine Schleife und drucken bei Übereinstimmung; und für „impact“ setzen wir „impactMatch“ mit „ApplyImpactFilter“ und „imp_filter“ und drucken bei Übereinstimmung. Übereinstimmende Ereignisse werden mit der Funktion ArrayResize zu „filteredEvents“ hinzugefügt, wodurch „filteredCount“ erhöht wird.
Abschließend wird eine Zusammenfassung gedruckt, und wenn „filteredCount“ positiv ist, wird die gefilterte Liste mit ArrayPrint gedruckt; andernfalls wird eine Meldung über keine Ereignisse gedruckt, um eine gründliche Ereignisanalyse für die Prüfung zu gewährleisten. Anschließend rufen wir die Funktion in der Ereignisbehandlung für die Ticks auf.
void OnTick() { //---- Get current bar time datetime currentBarTime = iTime(_Symbol, _Period, 0); //---- Check if bar time has changed if (currentBarTime != lastBarTime) { //---- Update last bar time lastBarTime = currentBarTime; //---- Filter and print events for current bar FilterAndPrintEvents(currentBarTime); } }
Beim Ausführen des Programms sehen wir Folgendes.
Auf dem Bild ist zu erkennen, dass die Filterung aktiviert ist und wie erwartet funktioniert. Das Einzige, was noch bleibt, ist das Testen unserer Logik, und das wird im nächsten Abschnitt behandelt.
Tests
Für einen detaillierten Test haben wir alles in einem Video visualisiert, das Sie sich im Anhang ansehen können.
<// VIDEO HIER //>
Schlussfolgerung
Abschließend haben wir unsere MQL5-Wirtschaftskalender-Serie erweitert, indem wir das System für Strategietests vorbereitet haben, indem wir statische Daten in einer gespeicherten Datei verwenden, um zuverlässige Backtests zu ermöglichen. Dadurch wird eine Brücke zwischen der Analyse von Live-Ereignissen und dem Strategy Tester mit flexiblen Filtern geschlagen, wodurch Datenbeschränkungen für eine präzise Strategievalidierung überwunden werden. Als Nächstes werden wir die Optimierung der Handelsausführung anhand dieser Ergebnisse und ihre Integration in das Dashboard untersuchen. Bleiben Sie dran!
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/17603
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.





- 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.