English 日本語
preview
Handel mit dem MQL5 Wirtschaftskalender (Teil 7): Vorbereitung auf Strategietests mit der ressourcenbasierten Analyse von Nachrichtenereignissen

Handel mit dem MQL5 Wirtschaftskalender (Teil 7): Vorbereitung auf Strategietests mit der ressourcenbasierten Analyse von Nachrichtenereignissen

MetaTrader 5Tester |
59 0
Allan Munene Mutiiria
Allan Munene Mutiiria

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:

  1. Bedeutung der Integration statischer Daten
  2. Implementation in MQL5
  3. Tests
  4. Schlussfolgerung

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.

EINIGE MQL5-DATEIFORMATE

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.

EINGABE VERSION

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

STANDARDMÄSSIGES MQL5-KALENDERFORMAT

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.

INITIALISIERUNG DER FILTER

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.

DATENBESCHAFFUNG IM LIVEMODUS

Aus dem Bild können wir ersehen, dass die Daten gespeichert sind und wir darauf zugreifen können.

DATENZUGANG

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.

GELADENE DATEN IM TESTERMODUS

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.

ABSCHLUSSANALYSE

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

Beigefügte Dateien |
Manuelle Backtest leicht gemacht: Aufbau eines nutzerdefinierten Toolkits für Strategietester in MQL5 Manuelle Backtest leicht gemacht: Aufbau eines nutzerdefinierten Toolkits für Strategietester in MQL5
In diesem Artikel entwickeln wir ein nutzerdefiniertes MQL5-Toolkit für einfache manuelle Backtests im Strategy Tester. Wir erläutern den Aufbau und die Umsetzung des Systems und konzentrieren uns dabei auf interaktive Handelskontrollen. Wir zeigen dann, wie man damit Strategien effektiv testen kann
Entwicklung eines Toolkits zur Analyse von Preisaktionen (Teil 20): Externer Fluss (IV) - Correlation Pathfinder Entwicklung eines Toolkits zur Analyse von Preisaktionen (Teil 20): Externer Fluss (IV) - Correlation Pathfinder
Der Correlation Pathfinder bietet als Teil der Serie der Entwicklung eines Toolkits zur Analyse von Preisaktionen einen neuen Ansatz zum Verständnis der Dynamik von Währungspaaren. Dieses Tool automatisiert die Datenerfassung und -analyse und bietet einen Einblick in die Wechselwirkungen zwischen Paaren wie EUR/USD und GBP/USD. Verbessern Sie Ihre Handelsstrategie mit praktischen Echtzeit-Informationen, die Ihnen helfen, Risiken zu managen und Chancen effektiver zu erkennen.
Klassische Strategien neu interpretieren (Teil 14): Hochwahrscheinliche Setups Klassische Strategien neu interpretieren (Teil 14): Hochwahrscheinliche Setups
Hochwahrscheinliche Setups sind in unserer Trading-Community gut bekannt, aber leider sind sie nicht gut definiert. In diesem Artikel wollen wir einen empirischen und algorithmischen Weg finden, um genau zu definieren, was ein Hochwahrscheinlichkeits-Setup ist, und um diese zu identifizieren und auszunutzen. Durch die Verwendung von Gradient Boosting Trees haben wir gezeigt, wie der Leser die Leistung einer beliebigen Handelsstrategie verbessern und unserem Computer die genaue Aufgabe auf sinnvollere und explizitere Weise mitteilen kann.
Feature Engineering mit Python und MQL5 (Teil IV): Erkennung von Kerzenmustern mit der UMAP-Regression Feature Engineering mit Python und MQL5 (Teil IV): Erkennung von Kerzenmustern mit der UMAP-Regression
Techniken zur Dimensionenreduktion werden häufig eingesetzt, um die Leistung von Modellen des maschinellen Lernens zu verbessern. Wir wollen nun eine relativ neue Technik erörtern, die als Uniform Manifold Approximation and Projection (UMAP) bekannt ist. Diese neue Technik wurde entwickelt, um die Einschränkungen herkömmlicher Methoden zu überwinden, die Artefakte und Verzerrungen in den Daten verursachen. UMAP ist eine leistungsstarke Technik zur Dimensionenreduzierung und hilft uns, ähnliche Kerzen auf eine neuartige und effektive Weise zu gruppieren, die unsere Fehlerquoten bei Daten, die nicht in der Stichprobe enthalten sind, reduziert und unsere Handelsleistung verbessert.