MQL5 Bootstrap (III): Simplified Functions for Working with News
Contents
- Introduction
- Understanding the news offered in MQL5
- Getting built-in news
- Checking if news exists
- Getting the next news release
- Getting the previous news release
- Exporting news to a CSV file
- Using news from a CSV file
- Backtesting news-based programs in the strategy tester
- Working with news from a database
- Conclusion
Introduction
When it comes to forex trading, news plays a key role in moving the markets. High-impact economic events such as interest rate decisions, inflation reports, employment figures, and GDP releases can cause significant market volatility in just a few seconds. While some traders might find this window to be the right time to trade, many avoid or adapt their strategies based on the expected impact of upcoming releases.
While MetaTrader5 provides a powerful built-in economic calendar API that you can access using the MQL5 programming language, making it work properly in all scenarios could be tricky.
For example, detecting news ahead.
//+------------------------------------------------------------------+ //| Checking if there are news of a specified impact on the current | //| symbol 15 minutes=900sec from the current time. | //+------------------------------------------------------------------+ bool IsThereNewsForTheSymbol(ENUM_CALENDAR_EVENT_IMPORTANCE importance, uint seconds=900) { MqlCalendarValue values[]; //https://www.mql5.com/en/docs/constants/structures/mqlcalendar#mqlcalendarvalue ResetLastError(); int all_news = CalendarValueHistory(values,TimeCurrent(), TimeCurrent()+seconds, NULL, NULL); //All news from the current time to some time in the future if (all_news <= 0) //if CalendarValue History returns a value less than zero it is an indicator that there is either an error or there are no news { if (GetLastError()>0) //we check if there was an error { printf("Failed to get the news for Symbol=%s Err=%d",Symbol(),GetLastError()); return false; } else //if there was no error then there are no news for this symbol available since not all symbols have news { printf("No news available for all symbols"); return false; } } for (int i=0; i<all_news; i++) //we loop through all the news { MqlCalendarEvent event; CalendarEventById(values[i].event_id, event); //Here among all the news we select one after the other by its id https://www.mql5.com/en/docs/calendar/calendareventbyid MqlCalendarCountry country; //The country where the currency pair originates CalendarCountryById(event.country_id, country); //https://www.mql5.com/en/docs/calendar/calendarcountrybyid if (StringFind(Symbol(), country.currency)>-1) //We want to ensure that we filter news that has nothing to do with the base and the quote currency for the current symbol pair { if (event.importance==importance) //filter the news by importance { if ((long)MathAbs(TimeCurrent()-values[i].time)<=seconds) //filter the news by time | do not trade 15 minutes before or after the news | NB: the difference in time when subtracted gives out seconds { Comment(StringFormat("<--------- News Alert ------------>\n\nEST %d Mins remaining\nCurrency %s Time[%s]\nname[%s]\nsector[%s]\nimportance[%s]\nactual value[%.3f]\nforecast value[%.3f]\nprevious value[%.3f]",(long)MathAbs(TimeCurrent()-values[i].time)/60,country.currency,TimeToString(values[i].time,TIME_DATE|TIME_MINUTES),event.name, EnumToString(event.sector), EnumToString(event.importance),values[i].GetActualValue(),values[i].GetForecastValue(),values[i].GetPreviousValue())); return true; //There is a high impact new(s) coming shortly } } } } return false; }
While it's not that complicated, it seems like a lot of work for such a simple task, not to mention, it may not work in the strategy tester.
If you need historical news or alternative detection logic beyond the function above, you may end up rewriting the same code for similar tasks. In this article, we are going to introduce classes and methods for collecting and working with news simply and efficiently, helping you save a lot of valuable development time and headaches.
Understanding the News Offered in MQL5
It is important to understand the three structures used by the MetaTrader5 economic calendar API, as we will be building a custom structure for the news shortly.
Think of them as three pieces of information that work together to describe a news event.
01: MqlCalendarCountry
This structure contains information about the country associated with an economic event. It includes details such as the country's name, currency, and currency symbol.
For example, if the event is the U.S Non-Farm Payrolls, the country structure would contain information about the United States and its currency (USD).
Members of this structure include:
- id – A unique identifier for the country.
- name – The country's name, such as United States or Japan.
- code – The two-letter country code, such as US or JP.
- currency – The country's currency code, such as USD, EUR, or JPY.
- currency_symbol – The currency symbol, such as $ or ¥.
- url_name – The country name used in the mql5.com website URL.
02: MqlCalendarEvent
This structure describes the economic event itself. It contains information about what the event is, where it belongs, and how important it is. For example, an event could be Consumer Price Index (CPI), Gross Domestic Product (GDP), or an Interest Rate Decision with parameters such as:
- id – A unique identifier for the event.
- name – The name of the economic event.
- country_id – The identifier of a country associated with the event.
- importance – Event's importance (Low, Medium, or High).
- frequency – How often the event is released (monthly, quarterly, yearly, etc.).
- sector – The part of the economy the event belongs to, such as Trading, Government, consumption, etc.
- time_mode – Specifies how the event release time is provided (exact time, all-day, no time, etc.)
- unit – The unit used to measure the reported value, such as percent or currency.
- digits – The number of decimal places used when displaying values.
- source_url – The website where the data is officially published.
- event_code – A short code that uniquely identifies the event.
03: MqlCalendarValue
While MqlCalendarEvent describes what the event is, MqlCalendarValue stores the results of a particular release. For example, the U.S Consumer Price Index (CPI) is released every month. The event remains the same, but every month has its own set of values.
This structure contains.
- time – The date and time when the news was released.
- period – The reporting period the data refers to.
- actual_value – The value that was actually reported.
- forecast_value – The value economists expected before the release.
- prev_value – The value reported in the previous release.
- revised_prev_value – The corrected previous value if it was revised later.
- revision – The revision number for that release.
- impact_type – The expected effect of the news on the currency.
The structure also provides several helper functions, such as HasActualValue() and GetActualValue(), for checking whether a value exists before reading it.
Simply put
- MqlCalendarCountry represents a country a news event and its values belongs to.
- MqlCalendarEvent represents an economic event being announced.
- MqlCalendarValue shows the results of a particular news release.
A Universal News Structure
As we've just seen above, the three news structures represent more of the same thing (news); let's define a unified structure that wraps all attributes of news in one place.
//+------------------------------------------------------------------+ //| A custom news structure. | //+------------------------------------------------------------------+ struct NewsStructure { //--- MqlCalendarCountry ulong country_id; // country ID (ISO 3166-1) string country_name; // country text name (in the current terminal encoding) string country_code; // country code name (ISO 3166-1 alpha-2) string country_currency; // country currency code string country_currency_symbol; // country currency symbol string url_name; // country name used in the mql5.com website URL //--- MqlCalendarEvent ulong event_id; // event ID ENUM_CALENDAR_EVENT_TYPE event_type; // event type from the ENUM_CALENDAR_EVENT_TYPE enumeration ENUM_CALENDAR_EVENT_SECTOR event_sector; // sector an event is related to ENUM_CALENDAR_EVENT_FREQUENCY event_frequency; // event frequency ENUM_CALENDAR_EVENT_TIMEMODE event_time_mode; // event time mode ENUM_CALENDAR_EVENT_UNIT event_unit; // economic indicator value's unit of measure ENUM_CALENDAR_EVENT_IMPORTANCE event_importance; // event importance ENUM_CALENDAR_EVENT_MULTIPLIER event_multiplier; // economic indicator value multiplier uint event_digits; // number of decimal places string event_source_url; // URL of a source where an event is published string event_code; // event code string event_name; // event text name in the terminal language (in the current terminal encoding) //--- MqlCalendarValue ulong value_id; // value ID datetime value_time; // event date and time datetime value_period; // event reporting period int value_revision; // revision of the published indicator relative to the reporting period long actual_value; // actual value multiplied by 10^6 or LONG_MIN if the value is not set long prev_value; // previous value multiplied by 10^6 or LONG_MIN if the value is not set long revised_prev_value; // revised previous value multiplied by 10^6 or LONG_MIN if the value is not set long forecast_value; // forecast value multiplied by 10^6 or LONG_MIN if the value is not set ENUM_CALENDAR_EVENT_IMPACT value_impact_type; // potential impact on the currency rate //--- functions checking the values bool HasActualValue(void) const { return actual_value != LONG_MIN; } bool HasPreviousValue(void) const { return prev_value != LONG_MIN; } bool HasRevisedValue(void) const { return revised_prev_value != LONG_MIN; } bool HasForecastValue(void) const { return forecast_value != LONG_MIN; } //--- functions receiving the values double GetActualValue(void) const { return HasActualValue() ? actual_value / 1000000.0 : EMPTY_VALUE; } double GetPreviousValue(void) const { return HasPreviousValue() ? prev_value / 1000000.0 : EMPTY_VALUE; } double GetRevisedValue(void) const { return HasRevisedValue() ? revised_prev_value / 1000000.0 : EMPTY_VALUE; } double GetForecastValue(void) const { return HasForecastValue() ? forecast_value / 1000000.0 : EMPTY_VALUE; } }
With a tiny difference in definition, such as starting with event_ to represent attributes of an event and value_ for distinct attributes of a value, it becomes more convenient to refer to all attributes of a news that separate structures.
Getting Built-in News
At the core of a class CNewsBuiltinProvider, the function Get() is responsible for retrieving news from the terminal using specific filters (starting and ending dates, the currency, and a country code).
//+------------------------------------------------------------------+ //| Retrieves economic news from the built-in MetaTrader 5 Economic | //| Calendar within the specified time range. | //| | //| Parameters: | //| from - Start of the search period. | //| to - End of the search period. | //| results[] - Output array receiving the retrieved news. | //| currency - Currency filter (e.g. "USD"). NULL retrieves | //| news for all currencies. | //| country_code - Country filter (ISO 3166-1 alpha-2, e.g. "US"). | //| NULL retrieves news for all countries. | //| | //| Returns: | //| Number of news records retrieved, or -1 if the request fails. | //+------------------------------------------------------------------+ int CNewsBuiltinProvider::Get(datetime from, datetime to, NewsStructure &results[], string currency=NULL, string country_code=NULL) { MqlCalendarValue values[]; MqlCalendarEvent event; MqlCalendarCountry country; int total = CalendarValueHistory(values, from, to, country_code, currency); if(total<0) { printf("Failed to get news from %s to %s. Error = %d", TimeToString(from), TimeToString(to), GetLastError()); return -1; } ArrayResize(results, total); //--- for(int i=0; i<total; i++) { MqlCalendarEvent event; CalendarEventById(values[i].event_id, event); //Here among all the news we select one after the other by its id https://www.mql5.com/en/docs/calendar/calendareventbyid MqlCalendarCountry country; //The couhtry where the currency pair originates CalendarCountryById(event.country_id, country); //https://www.mql5.com/en/docs/calendar/calendarcountrybyid //--- results[i].CountryAssign(country); results[i].EventAssign(event); results[i].ValueAssign(values[i]); } return total; }
For example, obtaining news for the last 24-hours on the USD currency.
#include <Bootstrap\News\provider_builtin.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart() { //--- NewsStructure news[]; datetime now = TimeCurrent(); datetime start = now - 24 * 60 * 60; //One day prior string currency = "USD"; int t_news = CNewsBuiltinProvider::Get(start, now, news, currency); printf("Available news %d on %s from %s to %s", t_news, currency, TimeToString(start), TimeToString(now)); for(int i = 0; i < t_news; i++) { NewsStructure n = news[i]; printf("%s | %s | %s | %s | %s", TimeToString(n.value_time, TIME_DATE | TIME_SECONDS), EnumToString(n.event_importance), n.country_currency, n.country_name, n.event_name); } }
Results:
JP 0 10:59:53.064 News Testing (EURUSD,M15) Available news 32 on USD from 2026.07.16 23:59 to 2026.07.17 23:59 FH 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 02:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Fed Governor Jefferson Speech RS 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Housing Starts DR 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Building Permits CM 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Housing Starts m/m FQ 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Building Permits m/m MP 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Import Price Index m/m PK 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Import Price Index y/y PE 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Export Price Index m/m II 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Export Price Index y/y EI 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Import Price Index excl. Petroleum m/m NP 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 16:15:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Fed Industrial Production m/m PQ 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 16:15:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Fed Capacity Utilization Rate NN 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 16:15:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Fed Manufacturing Production m/m DO 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 16:15:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Fed Industrial Production y/y NJ 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Michigan Consumer Sentiment HK 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Michigan Consumer Expectations KJ 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Michigan Current Conditions JQ 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Michigan Inflation Expectations CM 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Michigan 5-Year Inflation Expectations ED 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 20:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Baker Hughes US Oil Rig Count IG 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 20:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Baker Hughes US Total Rig Count CI 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Copper Non-Commercial Net Positions GN 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Silver Non-Commercial Net Positions QN 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | CFTC Gold Non-Commercial Net Positions FJ 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | CFTC Crude Oil Non-Commercial Net Positions GG 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | CFTC S&P 500 Non-Commercial Net Positions QR 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Aluminium Non-Commercial Net Positions FR 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Corn Non-Commercial Net Positions NG 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Natural Gas Non-Commercial Net Positions JJ 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Soybeans Non-Commercial Net Positions EQ 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Wheat Non-Commercial Net Positions JN 0 10:59:53.064 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | CFTC Nasdaq 100 Non-Commercial Net Positions
Checking if News Exists
In many trading scenarios, it is useful to have a simple function that checks whether any news exists within a specified date range.
//+------------------------------------------------------------------+ //| Checks whether one or more economic news events exist within the | //| specified time range. Optionally filters the results by event | //| importance, currency, and country. | //| | //| Parameters: | //| from - Start of the search period. | //| to - End of the search period. | //| importance - Event importance filter. Use -1 to match any | //| importance level. | //| currency - Currency filter (e.g. "USD"). NULL searches | //| all currencies. | //| country_code - Country filter (ISO 3166-1 alpha-2, e.g. "US"). | //| NULL searches all countries. | //| | //| Returns: | //| true if at least one matching event exists; otherwise false. | //+------------------------------------------------------------------+ bool CNewsBuiltinProvider::Exists(datetime from, datetime to, int importance=-1, string currency=NULL, string country_code=NULL) { //--- NewsStructure candidates[]; int total = Get(from, to, candidates, currency, country_code); if(total <= 0) return false; if(importance==-1) return true; // Check for the requested importance ENUM_CALENDAR_EVENT_IMPORTANCE imp = (ENUM_CALENDAR_EVENT_IMPORTANCE)importance; for(int i=0; i<total; i++) { if(candidates[i].event_importance == imp) return true; } return false; }
For the sake of detecting specific news types, this method takes the importance parameter, allowing users to specify the importance of news to look for.
Example usage.
datetime now = TimeCurrent(); datetime start = now - 24*60*60; //One day prior string currency = "USD"; bool exists = CNewsBuiltinProvider::Exists(start, now, currency, CALENDAR_IMPORTANCE_HIGH); printf("High impact News exists on %s: %s", currency, exists ? "true" : "false"); exists = CNewsBuiltinProvider::Exists(start, now, currency, CALENDAR_IMPORTANCE_MODERATE); printf("Moderate impact News exists on %s: %s", currency, exists ? "true" : "false");
Results:
DN 0 12:51:46.500 News Testing (EURUSD,M15) High impact News exists on USD: false NO 0 12:51:46.500 News Testing (EURUSD,M15) Moderate impact News exists on USD: true
Getting the Next News Release
Sometimes you only need to know the next scheduled news release. The Next() function simplifies this task by retrieving all news events between a specified time (or the current time by default) and a look-ahead window, then returning the first event in the results.
Since the news is retrieved in chronological order, the first element always represents the next upcoming news release.
//+------------------------------------------------------------------+ //| Returns the next economic news event occurring after the current | //| terminal time. | //| | //| Parameters: | //| out - Receives the next matching news event. | //| currency - Currency filter (e.g. "USD"). NULL | //| searches all currencies. | //| from - The initial date for news lookup. | //| lookahead_seconds - Maximum number of seconds to search ahead. | //| Default is 900 seconds (15 minutes). | //| country_code - Country filter (ISO 3166-1 alpha-2, | //| e.g. "US"). NULL searches all countries. | //| | //| Returns: | //| true if a matching news event is found; otherwise false. | //+------------------------------------------------------------------+ bool CNewsBuiltinProvider::Next(NewsStructure &out, string currency, const datetime from, const uint lookahead_seconds=900, string country_code=NULL) { datetime now = from; datetime max_time = now + lookahead_seconds; //--- NewsStructure candidates[]; int total = Get(candidates, now+1, max_time, currency, country_code); if(total > 0) { out = candidates[0]; // ascending order — first is nearest return true; } return false; }
The parameter lookahead_seconds is crucial for controlling the search window; I recommend avoiding looking too far in the future to make a function call quick.
Getting a Previous News Release
Similarly, the Previous() function retrieves the most recent news event before a specified time (or the current time by default). It searches backwards within a configurable look-back window and returns the last event found.
Since retrieved news is sorted in chronological order, the last element in the results corresponds to the most recent news release before the specified time.
//+------------------------------------------------------------------+ //| Returns the most recent economic news event before the specified | //| time. | //| | //| Parameters: | //| out - Receives the most recent matching news | //| event. | //| currency - Currency filter (e.g. "USD"). NULL | //| searches all currencies. | //| from - The reference time to search backwards | //| from. | //| lookback_seconds - Maximum number of seconds to search | //| backwards. Default is 900 seconds | //| (15 minutes). | //| country_code - Country filter (ISO 3166-1 alpha-2, | //| e.g. "US"). NULL searches all countries. | //| | //| Returns: | //| true if a matching news event is found; otherwise false. | //+------------------------------------------------------------------+ bool CNewsBuiltinProvider::Previous(NewsStructure &out, string currency, const datetime from, const uint lookback_seconds=900, string country_code=NULL) { datetime start = from - (datetime)lookback_seconds; NewsStructure candidates[]; int total = Get(candidates, start, from - 1, currency, country_code); if(total <= 0) return false; // Events are sorted in ascending order by time. out = candidates[total - 1]; return true; }
The parameter lookback_seconds is crucial for controlling the search window; I recommend avoiding looking too far in the past to make a function call quick.
Exporting News to a CSV File
Let us introduce a class to help us work with news from a CSV file source, starting with a function for exporting it to a CSV file.//+------------------------------------------------------------------+ //| Exports an array of news records to a CSV file. | //| | //| Parameters: | //| results[] - Array of news records to export. | //| filename - Name of the destination CSV file. | //| common_folder - If true, the file is created in the terminal's | //| common files folder; otherwise it is created in | //| the current terminal's Files directory. | //| | //| Notes: | //| - The first row of the CSV contains the column headers. | //| - Each NewsStructure is written as a single CSV record. | //| - Records with an unexpected number of fields are skipped. | //+------------------------------------------------------------------+ void CNewsCSVProvider::Export(NewsStructure &results[], const string filename, bool common_folder=false) { CFile f = CFileIO::open(filename, "w", CP_UTF8, common_folder); //--- string csv_header = results[0].HeaderLine(); string parsed_line[]; ParseCSVLine(csv_header, parsed_line); uint header_size = parsed_line.Size(); CSVWriter csv_writer(f); csv_writer.writeRow(parsed_line); //--- for(uint i=0; i<results.Size(); i++) { string row = results[i].ToCSVLine(); ParseCSVLine(row, parsed_line); if(header_size != parsed_line.Size()) { DebugBreak(); continue; } csv_writer.writeRow(parsed_line); } f.close(); }
To export news to CSV, values must be escaped so commas inside fields are not interpreted as column separators. The ParseCSVLine() function handles this.
Example usage:
#include <Bootstrap\News\provider_csv.mqh> //+------------------------------------------------------------------+ input datetime START_TIME=D'01.01.2026'; input datetime END_TIME=D'01.06.2026'; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart() { //--- NewsStructure historical_news[]; int t_news = CNewsBuiltinProvider::Get(START_TIME, END_TIME, historical_news); if (t_news>0) CNewsCSVProvider::Export(historical_news, "news.csv"); }
Results:

Using News from a CSV File
Reading and parsing a CSV file every time we need to retrieve news is inefficient. Instead, the CSV file should be read for the first time only, and the results should be kept in memory for later use.
All functions in the class, such as Get(), Exists(), Next(), and Previous(), operate by reading the news cache (array) rather than re-reading the CSV file over again. This significantly improves performance by eliminating unnecessary file I/O while providing fast access to the news data.
To enforce this behavior, the CSV file is loaded inside the class constructor, ensuring that the internal cache is populated before any news retrieval function can be called.
//+------------------------------------------------------------------+ //| Constructs a CSV news provider by loading economic news records | //| from a CSV file into an in-memory cache. | //| | //| Parameters: | //| csv_filename - Name of the CSV file containing the exported | //| news records. | //| common - If true, the CSV file is loaded from the | //| terminal's common files folder; otherwise it is | //| loaded from the current terminal's Files | //| directory. | //| delimiter - Field separator used in the CSV file. The | //| default delimiter is a comma (","). | //| | //+------------------------------------------------------------------+ CNewsCSVProvider::CNewsCSVProvider(const string csv_filename, bool common=false, const string delimiter=","): m_csv_filename(csv_filename), m_csv_source(true) { CFile f = CFileIO::open(csv_filename, "r", CP_UTF8, common); CSVReader csv_reader(f, delimiter); NewsStructure news_st; int buff = 1000, read=0; bool header_skipped = false; ArrayResize(this.m_news_cache, buff); string line = ""; string csv_row[]; //--- while(csv_reader.readRow(csv_row)) { if(!header_skipped) { header_skipped = true; continue; } //--- if(!FromCSVLine(csv_row, news_st)) continue; this.m_news_cache[read] = news_st; read++; //--- optimized array resizing if(read >= (int)this.m_news_cache.Size()) ArrayResize(this.m_news_cache, this.m_news_cache.Size()+buff); } printf("%d news loaded from %s",read, csv_filename); ArrayResize(this.m_news_cache, read); }
After reading all rows from a CSV file, the values are assigned to an array called m_news_cache.
At the class declaration, it inherits the parent class called CNewsBaseCache.
class CNewsCSVProvider: public CNewsBaseCache
This parent utility class is made for a single purpose only — reading news from the array m_news_cache[].
class CNewsBaseCache { protected: NewsStructure m_news_cache[]; public: CNewsBaseCache(); ~CNewsBaseCache(void); void AssignCache(NewsStructure &src[]); int Get(datetime from, datetime to, NewsStructure &results[], string currency=NULL, string country_code=NULL); bool Exists(datetime from, datetime to, int importance=-1, string currency=NULL, string country_code=NULL); bool Next(NewsStructure &out, const uint lookahead_seconds=900, string currency=NULL, string country_code=NULL); bool Next(datetime from, NewsStructure &out, const uint lookahead_seconds=900, string currency=NULL, string country_code=NULL); bool Previous(NewsStructure &out, const uint lookback_seconds=900, string currency=NULL, string country_code=NULL); bool Previous(datetime from, NewsStructure &out, const uint lookback_seconds=900, string currency=NULL, string country_code=NULL); };
This simple class is universal as it can even be used for built-in news; it is a plug-and-play module that takes a larger array of news and uses it as a source for subsequent function calls.
It powers the class CNewsCSVProvider, similarly to how we obtained news using a built-in calendar source. Below is how you can rely on news from a CSV file.
#include <Bootstrap\News\provider_csv.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart() { //--- datetime now = TimeCurrent(); datetime start = now - 24*60*60; //One day prior string currency = "USD"; NewsStructure news[]; CNewsCSVProvider csv_news("news.csv"); int t_news = csv_news.Get(start, now, news, currency); printf("Available news %d on %s from %s to %s", t_news, currency, TimeToString(start), TimeToString(now)); for(int i=0; i<t_news; i++) { NewsStructure n = news[i]; printf("%s | %s | %s | %s | %s", TimeToString(n.value_time, TIME_DATE|TIME_SECONDS), EnumToString(n.event_importance), n.country_currency, n.country_name, n.event_name); } bool exists = csv_news.Exists(start, now, CALENDAR_IMPORTANCE_HIGH, currency); printf("High impact News exists on %s: %s", currency, exists ? "true" : "false"); exists = csv_news.Exists(start, now, CALENDAR_IMPORTANCE_MODERATE, currency); printf("Moderate impact News exists on %s: %s", currency, exists ? "true" : "false"); //--- }
Results:
MF 0 17:00:56.065 News Testing (EURUSD,M15) 7484 news loaded from news.csv CM 0 17:00:56.069 News Testing (EURUSD,M15) Available news 32 on USD from 2026.07.16 23:59 to 2026.07.17 23:59 GM 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 02:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Fed Governor Jefferson Speech CL 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Housing Starts IO 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Building Permits RN 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Housing Starts m/m GD 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Building Permits m/m PG 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Import Price Index m/m MH 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Import Price Index y/y EK 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Export Price Index m/m DD 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Export Price Index y/y PK 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Import Price Index excl. Petroleum m/m OS 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 16:15:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Fed Industrial Production m/m EO 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 16:15:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Fed Capacity Utilization Rate OK 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 16:15:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Fed Manufacturing Production m/m IJ 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 16:15:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Fed Industrial Production y/y GE 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Michigan Consumer Sentiment MI 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Michigan Consumer Expectations RO 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Michigan Current Conditions CL 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Michigan Inflation Expectations JN 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Michigan 5-Year Inflation Expectations PG 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 20:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Baker Hughes US Oil Rig Count DJ 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 20:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Baker Hughes US Total Rig Count JN 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Copper Non-Commercial Net Positions FS 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Silver Non-Commercial Net Positions LK 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | CFTC Gold Non-Commercial Net Positions GG 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | CFTC Crude Oil Non-Commercial Net Positions FR 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | CFTC S&P 500 Non-Commercial Net Positions DL 0 17:00:56.069 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Aluminium Non-Commercial Net Positions OG 0 17:00:56.070 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Corn Non-Commercial Net Positions OE 0 17:00:56.070 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Natural Gas Non-Commercial Net Positions KO 0 17:00:56.070 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Soybeans Non-Commercial Net Positions HS 0 17:00:56.070 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Wheat Non-Commercial Net Positions KM 0 17:00:56.070 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | CFTC Nasdaq 100 Non-Commercial Net Positions JK 0 17:00:56.074 News Testing (EURUSD,M15) High impact News exists on USD: false QP 0 17:00:56.077 News Testing (EURUSD,M15) Moderate impact News exists on USD: true
Backtesting News-based Programs in the Strategy Tester
One limitation of the built-in Economic Calendar is that its functions aren't working in the strategy tester, i.e., they throw errors during backtesting. Functions such as CalendarValueHistory(), CalendarValueLast(), and other calendar-related APIs rely on live calendar data provided by the terminal. As a result, these functions either fail or return no data when executed inside the strategy tester or optimization.
This presents a challenge when developing Expert Advisors (EAs) that depend on economic news. While the trading logic can be tested, any functionality that relies on the Economic Calendar cannot, making it impossible to accurately evaluate news-based strategies during historical simulations.
A common solution is to export the required economic calendar data to a CSV file while the program is running on a live or demo account. During backtesting, the Expert Advisor can then load the same data from the CSV file instead of querying the built-in calendar. This allows the program to behave consistently in both live trading and the Strategy Tester while using identical news data.
Example EA:
#define NEWS_CSV "news.csv" #property tester_file NEWS_CSV //+------------------------------------------------------------------+ #include <Bootstrap\News\provider_csv.mqh> CNewsCSVProvider csv_news(NEWS_CSV); //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ObjectsDeleteAll(0); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- ObjectsDeleteAll(0); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- NewsStructure n; if (csv_news.Next(n)) { string info = StringFormat("%s | %s | %s | %s | %s", TimeToString(n.value_time, TIME_DATE|TIME_SECONDS), EnumToString(n.event_importance), n.country_currency, n.country_name, n.event_name); DisplayLabel("time", TimeToString(n.value_time, TIME_DATE|TIME_SECONDS), 20); DisplayLabel("importance", EnumToString(n.event_importance),40); DisplayLabel("currency", n.country_currency, 70); DisplayLabel("c name", n.country_name, 100); DisplayLabel("event name", n.event_name, 130); } else { Comment(""); } } //+------------------------------------------------------------------+ //| Displays or updates a text label on the chart. | //+------------------------------------------------------------------+ void DisplayLabel(const string name, const string text, const int y=20, const int x=10, const color clr=clrOrange, const int font_size=15, const string font="Arial") { if(ObjectFind(0, name) < 0) { ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, font_size); ObjectSetString(0, name, OBJPROP_FONT, font); } ObjectSetString(0, name, OBJPROP_TEXT, text); ChartRedraw(); }
Results:

A simple function call was enough to get news up and running in the strategy tester.
Working with News From a Database
A database provides a more structured and scalable way to store economic calendar data. It is a great alternative to using a CSV file as a primary source of news data.
Instead of searching through a flat file, news can be retrieved using SQL queries that filter records by time range, currency, country, or any other available field. This makes it possible to efficiently work with years of historical news while keeping memory usage under control.
Starting with the function for exporting news to a database:
//+------------------------------------------------------------------+ //| Exports an array of news records to an SQLite database. | //| | //| Parameters: | //| results[] - Array of news records to export. | //| filename - Name of the SQLite database file. If the file | //| does not exist, it is created automatically. | //| common_folder - If true, the database is created in the | //| terminal's common files folder; otherwise it | //| is created in the current terminal's Files | //| directory. | //| | //+------------------------------------------------------------------+ void CNewsSQLiteProvider ::Export(NewsStructure &results[], string filename, bool common_folder=false) { int total = ArraySize(results); if(total == 0) { printf("Export: nothing to export, results[] is empty"); return; } CSqlite3 db(false); if(!db.connect(filename, common_folder)) return; //--- collect distinct currencies present in results[] string currencies[]; for(int i=0; i<total; i++) { string cur = results[i].country_currency; bool found = false; for(int c=0; c<ArraySize(currencies); c++) if(currencies[c] == cur) { found = true; break; } if(!found) { int n = ArraySize(currencies); ArrayResize(currencies, n+1); currencies[n] = cur; } } //--- if(!db.begin()) { printf("Failed to begin transaction. Error = %d", GetLastError()); db.close(); return; } string insert_cols = "country_id," "country_name," "country_code," "country_currency," "country_currency_symbol," "url_name," "event_id," "event_type," "event_sector," "event_frequency," "event_time_mode," "event_unit," "event_importance," "event_multiplier," "event_digits," "event_source_url," "event_code," "event_name," "value_id," "value_time," "value_period," "value_revision," "actual_value," "prev_value," "revised_prev_value," "forecast_value," "value_impact_type"; string placeholders = "?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?"; // 27 placeholders for(int c=0; c<ArraySize(currencies); c++) { string cur = currencies[c]; cur = CurrenyToTableName(cur); //--- Builtin economic calendar if(!createNewsTable(cur, db)) //--- Create a table for each currency continue; //--- string insert_sql = StringFormat("INSERT OR IGNORE INTO %s (%s) VALUES (%s)",cur, insert_cols, placeholders); int stmt = DatabasePrepare(db.get_handle(), insert_sql); if(stmt == INVALID_HANDLE) { printf("Failed to prepare insert for %s. Error = %d", cur, GetLastError()); db.rollback(); db.close(); return; } for(int i=0; i<total; i++) { if(results[i].country_currency != cur) continue; if(!bindNewsRow(stmt, results[i])) { printf("Bind failed for %s row, event_id=%I64u. Error = %d", cur, results[i].event_id, GetLastError()); continue; } DatabaseRead(stmt); // executes the bound insert DatabaseReset(stmt); // clears bindings for the next row's reuse of this statement } DatabaseFinalize(stmt); } if(!db.commit()) DebugBreak(); db.close(); printf("Exported %d news records across %d currency tables to %s", total, ArraySize(currencies), filename); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CNewsSQLiteProvider ::Export(datetime from, datetime to, string currency=NULL, string country_code=NULL) { NewsStructure news_found[]; int f = CNewsBuiltinProvider::Get(news_found, from, to, currency, country_code); //--- if(f>0) this.Export(news_found, m_db_filename, m_common_folder); }
Since databases are table-based to make news storage more convenient, we store news from each currency in a separate table.
After receiving an array of NewsStructure objects, the function loops through it, extracting unique currencies whose information will be assigned to individual tables in a database.
string currencies[]; for(int i=0; i<total; i++) { string cur = results[i].country_currency; bool found = false; for(int c=0; c<ArraySize(currencies); c++) if(currencies[c] == cur) { found = true; break; } if(!found) { int n = ArraySize(currencies); ArrayResize(currencies, n+1); currencies[n] = cur; } }
Example: exporting news across all currencies to a database.
#include <Bootstrap\News\provider_sqlite.mqh> CNewsSQLiteProvider sql_provider("news.sqlite"); //+------------------------------------------------------------------+ input datetime START_TIME=D'01.01.2026'; input datetime END_TIME=D'01.06.2026'; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart() { //--- sql_provider.Export(START_TIME, END_TIME); }
Results:
2026.07.20 14:25:44.793 News Testing (EURUSD,M15) Exported 5751 news records across 19 currency tables to news.sqlite

Just like the built-in and CSV class providers, the SQLite provider class exposes the same high-level interface through methods such as Get(), Exists(), Next(), and Previous(). This means the underlying data source can be changed with little or no modification to the rest of the application, making it easy to switch between live calendar data, CSV files, and a local database depending on the use case.
At the core of CNewsSQLiteProvider is the most common Get() method; unlike in previous implementations, this time it queries information from a SQLite database.
//+------------------------------------------------------------------+ //| Retrieves economic news records from the SQLite database within | //| the specified time range. | //| | //| Parameters: | //| results[] - Output array receiving the matching news | //| records. | //| from - Start of the search period. | //| to - End of the search period. | //| currency - Currency whose table should be queried | //| (e.g. "USD"). | //| country_code - Optional country filter (ISO 3166-1 alpha-2, | //| e.g. "US"). NULL retrieves news for all | //| countries within the selected currency. | //| | //| Returns: | //| The number of news records retrieved. Returns 0 if no matching | //| records are found or if the query cannot be executed. | //| | //| Notes: | //| - News records are returned in ascending order of | //| publication time. | //| - The currency is internally mapped to its corresponding | //| database table name before the query is executed. | //| - The output array is automatically resized to fit the | //| retrieved records. | //+------------------------------------------------------------------+ int CNewsSQLiteProvider ::Get(NewsStructure &results[], datetime from, datetime to, string currency, string country_code=NULL) { ArrayResize(results,0); if(m_db.get_handle()==INVALID_HANDLE) { printf("Invalid database handle. Error=%d",GetLastError()); return 0; } //--- currency = CurrenyToTableName(currency); //--- string sql; if(country_code==NULL || country_code=="") { sql = StringFormat( "SELECT * FROM %s " "WHERE value_time>=? AND value_time<=? " "ORDER BY value_time ASC", currency); } else { sql = StringFormat( "SELECT * FROM %s " "WHERE value_time>=? AND value_time<=? " "AND country_code=? " "ORDER BY value_time ASC", currency); } int stmt = DatabasePrepare(m_db.get_handle(),sql); if(stmt==INVALID_HANDLE) { PrintFormat("Failed to prepare query. Error=%d",GetLastError()); return 0; } //--- int p=0; DatabaseBind(stmt,p++,(long)from); DatabaseBind(stmt,p++,(long)to); if(country_code!=NULL && country_code!="") DatabaseBind(stmt,p++,country_code); //--- uint ARRAY_BUFF = 1000; ArrayResize(results, ARRAY_BUFF); //--- int count=0; while(DatabaseRead(stmt)) { NewsStructure n; int c=0; long long_val; DatabaseColumnLong(stmt,c++,long_val); n.country_id = long_val; DatabaseColumnText(stmt,c++,n.country_name); DatabaseColumnText(stmt,c++,n.country_code); DatabaseColumnText(stmt,c++,n.country_currency); DatabaseColumnText(stmt,c++,n.country_currency_symbol); DatabaseColumnText(stmt,c++,n.url_name); DatabaseColumnLong(stmt,c++,long_val); n.event_id = long_val; string text; DatabaseColumnText(stmt,c++,text); StringToEnum(text, EventTypeNames, EventTypeValues, n.event_type); DatabaseColumnText(stmt,c++,text); StringToEnum(text, EventSectorNames, EventSectorValues, n.event_sector); DatabaseColumnText(stmt,c++,text); StringToEnum(text, EventFrequencyNames, EventFrequencyValues, n.event_frequency); DatabaseColumnText(stmt,c++,text); StringToEnum(text, EventTimeModeNames, EventTimeModeValues, n.event_time_mode); DatabaseColumnText(stmt,c++,text); StringToEnum(text, EventUnitNames, EventUnitValues, n.event_unit); DatabaseColumnText(stmt,c++,text); StringToEnum(text, EventImportanceNames, EventImportanceValues, n.event_importance); DatabaseColumnText(stmt,c++,text); StringToEnum(text, EventMultiplierNames, EventMultiplierValues, n.event_multiplier); int int_val = 0; DatabaseColumnInteger(stmt,c++, int_val); n.event_digits = int_val; DatabaseColumnText(stmt,c++,n.event_source_url); DatabaseColumnText(stmt,c++,n.event_code); DatabaseColumnText(stmt,c++,n.event_name); DatabaseColumnLong(stmt,c++,long_val); n.value_id = long_val; DatabaseColumnLong(stmt,c++, long_val); n.value_time = (datetime)long_val; DatabaseColumnLong(stmt,c++, long_val); n.value_period = (datetime)long_val; DatabaseColumnInteger(stmt,c++,n.value_revision); DatabaseColumnLong(stmt,c++,n.actual_value); DatabaseColumnLong(stmt,c++,n.prev_value); DatabaseColumnLong(stmt,c++,n.revised_prev_value); DatabaseColumnLong(stmt,c++,n.forecast_value); DatabaseColumnText(stmt,c++,text); StringToEnum(text, EventImpactNames, EventImpactValues, n.value_impact_type); uint curr_size = results.Size(); if(count>=(int)curr_size) ArrayResize(results, curr_size+ARRAY_BUFF); results[count++]=n; } ArrayResize(results, count); //Final resize DatabaseFinalize(stmt); return count; }
We can try requesting the same information as we did in previous examples and see if we can get a similar outcome:
#include <Bootstrap\News\provider_sqlite.mqh> CNewsSQLiteProvider sql_provider("news.sqlite"); //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart() { //--- start = D'17.07.2026'; now = start+24*60*60; int t_news = sql_provider.Get(news, start, now, currency); printf("Available news %d on %s from %s to %s", t_news, currency, TimeToString(start), TimeToString(now)); for(int i=0; i<t_news; i++) { NewsStructure n = news[i]; printf("%s | %s | %s | %s | %s", TimeToString(n.value_time, TIME_DATE|TIME_SECONDS), EnumToString(n.event_importance), n.country_currency, n.country_name, n.event_name); } bool exists = sql_provider.Exists(start, now, currency, CALENDAR_IMPORTANCE_HIGH); printf("High impact News exists on %s: %s", currency, exists ? "true" : "false"); exists = sql_provider.Exists(start, now, currency, CALENDAR_IMPORTANCE_MODERATE); printf("Moderate impact News exists on %s: %s", currency, exists ? "true" : "false"); }
Results:
JD 0 14:44:08.011 News Testing (EURUSD,M15) Available news 32 on USD from 2026.07.17 00:00 to 2026.07.18 00:00 PD 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 02:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Fed Governor Jefferson Speech DG 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Housing Starts FF 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Building Permits QQ 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Housing Starts m/m DM 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Building Permits m/m KL 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Import Price Index m/m FO 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Import Price Index y/y FR 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Export Price Index m/m GM 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Export Price Index y/y KD 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 15:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Import Price Index excl. Petroleum m/m LD 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 16:15:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Fed Industrial Production m/m JD 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 16:15:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Fed Capacity Utilization Rate HS 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 16:15:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Fed Manufacturing Production m/m RS 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 16:15:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Fed Industrial Production y/y HN 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Michigan Consumer Sentiment NF 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Michigan Consumer Expectations MF 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | Michigan Current Conditions LE 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Michigan Inflation Expectations QQ 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 17:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Michigan 5-Year Inflation Expectations KP 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 20:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Baker Hughes US Oil Rig Count GS 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 20:00:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | Baker Hughes US Total Rig Count EE 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Copper Non-Commercial Net Positions IJ 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Silver Non-Commercial Net Positions OR 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | CFTC Gold Non-Commercial Net Positions PO 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | CFTC Crude Oil Non-Commercial Net Positions MK 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | CFTC S&P 500 Non-Commercial Net Positions CE 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Aluminium Non-Commercial Net Positions DN 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Corn Non-Commercial Net Positions DR 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Natural Gas Non-Commercial Net Positions LG 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Soybeans Non-Commercial Net Positions OD 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_LOW | USD | United States | CFTC Wheat Non-Commercial Net Positions HR 0 14:44:08.011 News Testing (EURUSD,M15) 2026.07.17 22:30:00 | CALENDAR_IMPORTANCE_MODERATE | USD | United States | CFTC Nasdaq 100 Non-Commercial Net Positions ML 0 14:44:08.011 News Testing (EURUSD,M15) High impact News exists on USD: false JI 0 14:44:08.012 News Testing (EURUSD,M15) Moderate impact News exists on USD: true
We got the same information as in previous runs, cheers!
The great thing about obtaining information from a database is that the current implementation works even on the strategy tester without having to adjust the code. Database operations are relatively slow. In live trading, a few milliseconds are usually negligible, but in simulations even ~100 ms per call can significantly increase total test time.
To speed things up, let's use the CNewsBaseCache for the same thing as we did in a class for working with news from a CSV file. This time we are going to read all news from a database and keep such information in an array for later reference.
class CNewsSQLiteProvider { protected: string m_db_filename; CSqlite3 m_db; bool m_common_folder; CNewsBaseCache cache_layer; bool m_cache_mode; struct CurrencyCache { string currency; NewsStructure news[]; }; CurrencyCache m_currencies_cache[]; int SQLiteGet(NewsStructure &results[], datetime from, datetime to, string currency, string country_code=NULL); int SQLiteGet(NewsStructure &results[], const string sql); bool SQLiteExists(datetime from, datetime to, string currency, int importance=-1, string country_code=NULL); bool SQLiteNext(NewsStructure &out, string currency, uint lookahead_seconds=900, string country_code=NULL); bool SQLiteNext(NewsStructure &out, string currency, datetime from, uint lookahead_seconds=900, string country_code=NULL); bool SQLitePrevious(NewsStructure &out, string currency, uint lookback_seconds=900, string country_code=NULL); bool SQLitePrevious(NewsStructure &out, string currency, datetime from, uint lookback_seconds=900, string country_code=NULL); int FindCache(const string currency); int EnsureCurrencyLoaded(const string currency); public: CNewsSQLiteProvider(string db_filename, bool cache_mode, bool common=false); ~CNewsSQLiteProvider(void); static void Export(NewsStructure &results[], string filename, bool common_folder=false); void Export(datetime from, datetime to, string currency, string country_code=NULL); int Get(NewsStructure &results[], datetime from, datetime to, string currency, string country_code=NULL); bool Exists(datetime from, datetime to, string currency, int importance=-1, string country_code=NULL) { if(m_cache_mode) return this.cache_layer.Exists(from, to, currency, importance, country_code); return this.SQLiteExists(from, to, currency, importance, country_code); } bool Next(NewsStructure &out, string currency, uint lookahead_seconds=900, string country_code=NULL) { if(m_cache_mode) return this.cache_layer.Next(out, currency, lookahead_seconds, country_code); return this.SQLiteNext(out, currency, lookahead_seconds, country_code); } bool Next(NewsStructure &out, string currency, datetime from, uint lookahead_seconds=900, string country_code=NULL) { if(m_cache_mode) return this.cache_layer.Next(out, currency, from, lookahead_seconds, country_code); return this.SQLiteNext(out, currency, from, lookahead_seconds, country_code); } bool Previous(NewsStructure &out, string currency, uint lookback_seconds=900, string country_code=NULL) { if(m_cache_mode) return this.cache_layer.Next(out, currency, lookback_seconds, country_code); return this.SQLitePrevious(out, currency, lookback_seconds, country_code); } bool Previous(NewsStructure &out, string currency, datetime from, uint lookback_seconds=900, string country_code=NULL) { if(m_cache_mode) return this.cache_layer.Next(out, currency, from, lookback_seconds, country_code); return this.SQLitePrevious(out, currency, from, lookback_seconds, country_code); }
When a user sets cache_mode to true in the class constructor, the method Get() chooses whether to use information stored in the array or to search for it in the database if it doesn't exist (first-time function call). This approach reduces database querying as we collect all rows from the database the moment a given currency pair doesn't exist in memory; after that, we use information available inside m_currencies_cache[].
Below is how you can deploy news from a database source for strategy testing purposes.
#define NEWS_DB "news.sqlite" #define is_tester bool(MQLInfoInteger(MQL_TESTER)) #property tester_file NEWS_DB //+------------------------------------------------------------------+ #include <Bootstrap\News\provider_sqlite.mqh> CNewsSQLiteProvider sql_news(NEWS_DB, is_tester); //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- ObjectsDeleteAll(0); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- ObjectsDeleteAll(0); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- NewsStructure n; bool n_found = sql_news.Next(n, "USD"); if (n_found) { string info = StringFormat("%s | %s | %s | %s | %s", TimeToString(n.value_time, TIME_DATE|TIME_SECONDS), EnumToString(n.event_importance), n.country_currency, n.country_name, n.event_name); DisplayLabel("time", TimeToString(n.value_time, TIME_DATE|TIME_SECONDS), 20); DisplayLabel("importance", EnumToString(n.event_importance),40); DisplayLabel("currency", n.country_currency, 70); DisplayLabel("c name", n.country_name, 100); DisplayLabel("event name", n.event_name, 130); } else { Comment(""); } }
Final Thoughts
In this article, we built a flexible framework for working with economic news in MQL5. From exploring the built-in Economic Calendar functions provided by MetaTrader 5 to introducing a unified news structure that simplifies working with countries, events, and published values through a single data type.
To make the framework suitable for both live trading and historical testing, we implemented multiple news providers that expose the same interface while retrieving data from different sources. The built-in provider allows direct access to the Economic Calendar during live trading, the CSV provider makes it possible to backtest news-based strategies using previously exported data, and the SQLite provider offers an efficient, scalable solution for storing and querying large collections of historical news.
By designing each provider around the same set of methods in different classes, switching between data sources requires little or no change to the rest of your application. This abstraction keeps trading strategies clean while making the news source interchangeable depending on the environment.
With this foundation in place, you can now build Expert Advisors that react to economic events, filter trades around high-impact news, or perform reliable historical testing using the same programming interface.
For contributions, check out this GitHub repository: https://github.com/MegaJoctan/MQL5-Bootstrap
Attachments Table
| Filename | Description & Usage |
|---|---|
| Experts\Bootstrap\News Testing EA.mq5 | An expert advisor for testing news in the strategy tester. |
| Scripts\Bootstrap\News Testing.mq5 | A script that executes all examples as discussed above. |
| Include\Bootstrap\csv.mqh | A library with functions responsible for reading and writing information to CSV file formats. |
| Include\Bootstrap\fileIO.mqh | Resembles Python's file IO operations, read more. |
| Include\Bootstrap\SQLite3.mqh | A Python-like library for working with SQLite databases, read more. |
| Include\Bootstrap\News\provide_builtin.mqh | A library containing a static class for working with news offered by the MetaTrader5 terminal. |
| Include\Bootstrap\News\provide_csv.mqh | A library containing a class for working with news obtained from a CSV file. |
| Include\Bootstrap\News\provide_sqlite.mqh | A library containing a class for working with news from a SQLite database. |
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Fast Integration of a Large Language Model with MetaTrader 5 (Part II): Fine-Tuning on Real Data, Backtesting, and Live Trading by the Model
Self Optimizing Expert Advisors in MQL5 (Part 18): Time Lagged Independent Components Analysis
Neural Networks in Trading: Probabilistic Time Series Forecasting (Encoder)
Differential Search Algorithm (DSA)
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use