
Trading with the MQL5 Economic Calendar (Part 7): Preparing for Strategy Testing with Resource-Based News Event Analysis
Introduction
In this article, we advance our MQL5 Economic Calendar series by preparing the trading system for strategy testing in non-live mode, leveraging embedded economic event data for reliable backtesting. Building on Part 6’s automation of trade entries with news analysis and countdown timers, we now focus on loading news events from a resource file and applying user-defined filters to simulate live conditions in the Strategy Tester. We structure the article with the following topics:
Let's dive in!
Importance of Static Data Integration
Static data integration is essential for those aiming to develop and test robust strategies, particularly in environments like MQL5, where historical economic event data isn’t retained for long periods. Unlike live trading, where the platform can pull real-time news feeds, the Strategy Tester lacks access to such dynamic updates. It doesn’t store extensive archives of past events, leaving us without a native solution for backtesting news-driven approaches. By downloading this data from external sources and organizing it ourselves—whether as files, databases, or embedded resources—we gain control over a consistent dataset that can be reused across multiple tests, ensuring our strategies face the same conditions each time.
Beyond overcoming platform limitations, static data integration will offer flexibility that live feeds cannot. The Economic calendar, as we did see already in the prior versions, often includes critical details like event dates, times, currencies, and impact levels, but these aren’t always preserved in a format suited for algorithmic analysis over long timeframes. By structuring this information manually, we can tailor it to our needs—filtering for specific currencies or high-impact events, for example—allowing for deeper insights into how news influences market behavior without relying on real-time availability.
Additionally, this approach will enhance efficiency and independence. Gathering and storing static data upfront means we’re not tethered to internet connectivity or third-party services during testing, reducing variables that could skew results. It also empowers us to simulate rare or specific scenarios—like major economic announcements—by curating datasets that span years or focus on key moments, something live systems or limited platform storage can’t easily replicate. Ultimately, static data integration bridges the gap between live trading insights and backtesting precision, laying a solid foundation for strategy development.
Data storage will be a key consideration, and MQL5 provides a wide range of variability, from Text (txt) formats, Comma Separated Values (CSV), American National Standards Institute (ANSI), Binary (bin), Unicode and also database organizations as below.
We will use not the easiest but the most convenient format, which is CSV format. That way, we will have the data with us and will not be required we to wait for hours to backtest our strategy, saving us lots of time and energy. Let's go.
Implementation in MQL5
As a start off, we will need to structure the data gathering and organization in a manner that mirrors our previous structure. Thus, we will need some inputs that the user can customize, just as we did earlier on as below.
//+------------------------------------------------------------------+ //| 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)
Here, we set up the foundational input parameters and an enumeration to customize how our trading system processes economic events for strategy testing. We define "StartDate" and "EndDate" as datetime variables, set to March 1, 2025, and March 21, 2025, respectively, to specify the range for downloading and analyzing event data. To control time-based filtering around these events, we include "ApplyTimeFilter" as a boolean defaulting to true, alongside "HoursBefore" (4 hours), "MinutesBefore" (10 minutes), "HoursAfter" (1 hour), and "MinutesAfter" (5 minutes), which determine the time window for considering events relative to a given bar.
For currency-specific analysis, we introduce "ApplyCurrencyFilter" (true by default) and "CurrencyFilter", a string listing all eight major currencies—"USD, EUR, GBP, JPY, AUD, NZD, CAD, CHF"—to focus on relevant markets. We also enable impact-based filtering with "ApplyImpactFilter" set to true, supported by the "ENUM_IMPORTANCE" enumeration, which offers flexible options like "IMP_NONE", "IMP_LOW", "IMP_MEDIUM", "IMP_HIGH", and combinations up to "IMP_ALL", with "ImportanceFilter" defaulting to "IMP_ALL" to include all impact levels. The outcome is as below.
With the inputs, the next thing that we need to do is declare a structure with 8 input fields mimicking the normal and default structure for the MQL5 Economic Calendar as below.
We achieve the format via the following logic.
//---- 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[];
First, we define the "EconomicEvent" structure (struct) to encapsulate key event details, including "eventDate" and "eventTime" as strings for the event’s timing, "currency" to identify the affected market, "event" for the description, and "importance" to indicate its impact level, alongside "actual", "forecast", and "previous" as doubles to hold the event’s numerical outcomes.
To store and process these events, we create three arrays: "allEvents", an array of "EconomicEvent" structures to hold all loaded events, "curr_filter" as a string array to store the currencies specified in the "CurrencyFilter" input, and "imp_filter" as a string array to manage the importance levels selected via "ImportanceFilter". This mimics the default structure, only that we shift the "Period" section to contain the event dates at the beginning of the structure. Proceeding, we need to get the filters from user input, interpret them in a way the computer understands and initialize them. To keep the code modularized, we will use functions.
//---- 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); } }
Here, we set up the currency filtering portion of the "InitializeFilters" function in our system to prepare for effective event analysis during strategy testing. We start by checking if the "ApplyCurrencyFilter" variable is true and if the "CurrencyFilter" string has content using the StringLen function; if so, we split the comma-separated "CurrencyFilter" (like "USD, EUR, GBP") into the "curr_filter" array using the StringSplit function, capturing the number of elements in "count".
Next, we iterate through each element in "curr_filter" with a for-loop, assigning it to a temporary "temp" string, cleaning it by removing leading and trailing whitespace with the StringTrimLeft and StringTrimRight functions, then updating "curr_filter" with the trimmed value and displaying it via the Print function for debugging purposes (e.g., "Currency filter [0]: 'USD'"). However, if "ApplyCurrencyFilter" is enabled but "CurrencyFilter" is empty, we use the "Print" function to issue a warning—"Warning: CurrencyFilter is empty, no currency filtering applied"—and resize the array to zero with the ArrayResize function to disable filtering. This careful initialization will ensure that the currency filter is reliably derived from user inputs, supporting accurate event processing in the Strategy Tester. For the impact filter, we apply a similar curated logic.
//---- 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); }
For the impact filtering process, we begin by checking if the "ApplyImpactFilter" variable is true; if so, we use a switch statement based on the "ImportanceFilter" enum to determine which impact levels to include in the "imp_filter" array. For single-level options like "IMP_NONE", "IMP_LOW", "IMP_MEDIUM", or "IMP_HIGH", we resize "imp_filter" to 1 using the ArrayResize function and assign the corresponding string (e.g., "imp_filter[0] = 'None'"); for dual-level options like "IMP_NONE_LOW" or "IMP_MEDIUM_HIGH", we resize it to 2 and set two values (e.g., "imp_filter[0] = 'None', imp_filter[1] = 'Low'"); for triple-level options like "IMP_LOW_MEDIUM_HIGH", we resize to 3; and for "IMP_ALL", we resize to 4, covering "None", "Low", "Medium", and "High".
After setting the array, we loop through "imp_filter" using the ArraySize function to determine its size, printing each value with the Print function for debugging (e.g., "Impact filter [0]: 'None'"). If "ApplyImpactFilter" is false, we notify the user with the "Print" function—"Impact filter disabled"—and resize "imp_filter" to zero.
With that, we now need to call the function on the OnInit event handler.
int OnInit() { //---- Initialize filters InitializeFilters(); //---- Return successful initialization return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { //---- Print termination reason Print("EA terminated, reason: ", reason); }
We call the function in the OnInit event handler and also print the reason for the program termination in the OnDeinit event handler. Here is the outcome.
From the image, we can see that we initialized, and decoded the filter inputs correctly and stored them. All we need to do now is source the data from live stream and store it. Here, the logic is first we need to run the program once in live mode, so it can download the data from the MQL5 Economic Calendar database, and then load and use that data in testing mode. Here is the initialization logic.
//---- 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++; } }
Here, we handle the live mode data retrieval within the OnInit function of our program, ensuring economic event data is collected for later use in strategy testing. We start by checking if the system is not in tester mode using the MQLInfoInteger function with MQL_TESTER; if true, we validate that "StartDate" is earlier than "EndDate", printing an error and returning INIT_PARAMETERS_INCORRECT if invalid. Next, we declare a MqlCalendarValue array named "values" and fetch calendar data between "StartDate" and "EndDate" using the CalendarValueHistory function, printing an error with GetLastError and returning "INIT_FAILED" if it fails.
We then initialize an "EconomicEvent" array "events" and an "eventCount" integer to track events, looping through "values" with the ArraySize function. For each iteration, we fetch event details into a MqlCalendarEvent structure "eventDetails" using the CalendarEventById function, country details into a MqlCalendarCountry structure "countryDetails" with CalendarCountryById, and value details into a "MqlCalendarValue" structure "value" via "CalendarValueById", skipping if any fetch fails. We resize "events" with the ArrayResize function, convert the event time to a string "dateTimeStr" using the TimeToString function, and extract "eventDate" and "eventTime" with the StringSubstr function, assigning "currency" from "countryDetails", "event" from "eventDetails.name", and mapping "importance" from numeric values to strings ("None", "Low", "Medium", "High"). Finally, we set "actual", "forecast", and "previous" using "value" methods and increment "eventCount", building a comprehensive event dataset for live mode processing. Now, we need a function to handle the storage of this information in a data file.
//---- 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); } }
Here, we craft the "WriteToCSV" function to systematically export economic event data into a CSV file. We begin by opening the file specified by "fileName" using the FileOpen function in "FILE_WRITE | FILE_CSV" mode with a comma delimiter, storing the result in "handle"; if this fails and "handle" equals "INVALID_HANDLE", we use the "Print" function to display an error message including the GetLastError code and exit the function with "return". Once the file is open, we write a header row with the FileWrite function, defining columns as "Date", "Time", "Currency", "Event", "Importance", "Actual", "Forecast", and "Previous" to organize the data.
We then iterate through the "events" array, determining its size with the ArraySize function, and for each event, we call "FileWrite" to record its properties—"eventDate", "eventTime", "currency", "event", "importance", and the numeric "actual", "forecast", and "previous" values converted to strings with the DoubleToString function (formatted to 2 decimal places)—while simultaneously logging these details with the "Print" function for debugging purposes.
After completing the loop, we ensure all data is written to the file by invoking the FileFlush function on "handle", then close the file using the FileClose function, and confirm the operation’s success with a message.
To verify the output, we reopen the file in read mode using "FILE_READ | FILE_TXT", storing this handle in "verifyHandle"; if successful, we read the full content into "content" with the FileReadString function based on the byte size from FileSize, print it for inspection (e.g., "File content after writing (size: X bytes):\n"content""), and close. This thorough process guarantees that the event data is accurately saved and can be checked, making it a dependable resource for backtesting in the Strategy Tester. We now can use the function for the data-saving process.
//---- 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.");
To wrap up live mode data handling, we set "fileName" to "Database\EconomicCalendar.csv" and used the "FileExists" custom function to check its status. We then call the "WriteToCSV" function with "fileName" and "events" inputs to save the data, and print instructions with "Print"—"Live mode: Data written. To use in tester, add "fileName" as a resource and recompile."—for tester use. The custom function's code snippet to check the existence of the file is as below.
//---- 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 the "FileExists" function to check file presence for strategy testing, we open "fileName" with FileOpen function in "FILE_READ | FILE_CSV" mode, and if "handle" isn’t "INVALID_HANDLE", we close it with FileClose and return true; else, we return false. This confirms the file status for data handling. Upon running in live mode, here is the outcome.
From the image, we can see that the data is saved and we can access it.
To use the data in tester mode, we need to save it in the executable. For that, we add it as a resource.
//---- Define resource file for economic calendar data #resource "\\Files\\Database\\EconomicCalendar.csv" as string EconomicCalendarData
Here, we integrate the static data resource into our program to support strategy testing. Using the #resource directive, we embed the file located at "\Files\Database\EconomicCalendar.csv" and assign it to the "EconomicCalendarData" string variable. That way, the file is located in the executive so we don't have to worry even if it is deleted. We can now have a function to load the contents from the file.
//---- 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; }
We define the "LoadEventsFromResource" function to populate economic event data from the embedded resource for strategy testing. We assign the "EconomicCalendarData" resource to "fileData" and print its raw content with the "Print" function, including its size via the StringLen function, for debugging. We split "fileData" into the "lines" array using the StringSplit function with a newline delimiter, storing the count in "lineCount", and if "lineCount" is 1 or less, we print an error and return false. We reset the "allEvents" array with the ArrayResize function to zero and initialize "eventIndex" at 0, then loop through "lines" starting at index 1 (skipping the header). For each line, we check if it’s empty with StringLen, printing a skip message and continuing if so; otherwise, we split it into "fields" using commas.
If "fieldCount" is less than 8, we print an error and skip; else, we extract "dateStr", "timeStr", and "currency", and build "event" by concatenating fields (handling commas) in a loop, then grab "importance", "actualStr", "forecastStr", and "previousStr". We convert "dateStr" and "timeStr" to "eventDateTime" with the StringToTime function, skipping with an error if it fails, then resize "allEvents" with "ArrayResize", assign all values—converting numbers with StringToDouble—print the event and increment "eventIndex". Finally, we print the total "eventIndex" and return true if events were loaded, ensuring data readiness for the Strategy Tester. We can now call this function on initialization in tester mode.
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); } }
Here, if "EconomicCalendarData" is empty per StringLen, we print an error and return "INIT_FAILED"; else, we print a tester mode message with the "Print" function and call "LoadEventsFromResource", returning "INIT_FAILED" with an error if it fails. This will ensure that our event data loads correctly for backtesting. Here is the outcome.
From the image, we can confirm the data is loaded successfully. The malformation of data and skipping of empty lines is handled correctly too. We can now proceed to the OnTick event handler and simulate the data processing as if we were in live mode. For this, we want to process the data per bar and not on every tick.
//---- 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; //---- } }
We define "lastBarTime" as a "datetime" variable initialized to 0 to track the previous bar’s time. In the OnTick function, we retrieve the current bar’s time with the iTime function using _Symbol, _Period, and bar index 0, storing it in "currentBarTime"; if "currentBarTime" differs from "lastBarTime", we update "lastBarTime" to "currentBarTime", ensuring the system reacts to new bars for event processing. We can then define a function to handle the live simulation data processing in a similar format we did with the prior version as below.
//---- 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."); } }
Here, we construct the "FilterAndPrintEvents" function to filter and display economic events relevant to a given bar. We start by calculating "totalEvents" with the ArraySize function on "allEvents" and print it; if zero, we exit with "return". We initialize "filteredEvents" as an "EconomicEvent" array and "filteredCount" at 0, then define "timeBefore" and "timeAfter" for time filtering. If "ApplyTimeFilter" is true, we convert "barTime" to "barStruct" with TimeToStruct function, adjust "timeBeforeStruct" by subtracting "HoursBefore" and "MinutesBefore" (correcting negatives), and "timeAfterStruct" by adding "HoursAfter" and "MinutesAfter" (correcting overflows), converting both to "datetime" with StructToTime function and printing the range; otherwise, we set them to "StartDate" and "EndDate" and print a no-filter message.
We loop through "allEvents" with "totalEvents", converting each "eventDate" and "eventTime" to "eventDateTime" with StringToTime, checking if it’s within "StartDate" and "EndDate" for "inDateRange", and skipping if not. For time filtering, we test "timeMatch" with "ApplyTimeFilter" and the range, printing details if it passes; for currency, we set "currencyMatch" based on "ApplyCurrencyFilter" and "curr_filter" via ArraySize function and a loop, printing if matched; and for impact, we set "impactMatch" with "ApplyImpactFilter" and "imp_filter", printing if matched. Matching events are added to "filteredEvents" with the ArrayResize function, incrementing "filteredCount".
Finally, we print a summary, and if "filteredCount" is positive, we print the filtered list with ArrayPrint; otherwise, we print a no-events message, ensuring thorough event analysis for testing. We then call the function in the 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; //---- Filter and print events for current bar FilterAndPrintEvents(currentBarTime); } }
Upon running the program, we have the following outcome.
From the image, we can see that filtering is enabled and works as anticipated. The only thing that remains is testing our logic, and that is handled in the next section.
Testing
For a detailed testing, we visualized everything in a video, and you can view it as attached below.
<// VIDEO HERE //>
Conclusion
In conclusion, we’ve enhanced our MQL5 Economic Calendar series by preparing the system for strategy testing, using static data in a saved file to enable reliable backtesting. This bridges live event analysis to the Strategy Tester with flexible filters, overcoming data limitations for precise strategy validation. Next, we’ll explore optimizing trade execution from these results, and their integration into the dashboard. Keep tuned!





- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use