| //+------------------------------------------------------------------+
|
| //| NewsFeedHandler.mqh |
|
| //| Copyright 2025, EscapeEA Team |
|
| //| https://www.escapeea.com |
|
| //+------------------------------------------------------------------+
|
| #property copyright "Copyright 2025, EscapeEA Team"
|
| #property link "https://www.escapeea.com"
|
| #property version "1.00"
|
| #property strict
|
|
|
| #include <Arrays\ArrayObj.mqh>
|
| #include <Escape/Common/ErrorHandler.mqh>
|
|
|
| //+------------------------------------------------------------------+
|
| //| News Event Structure |
|
| //+------------------------------------------------------------------+
|
| struct SNewsEvent : public CObject
|
| {
|
| datetime time; // Event time
|
| string currency; // Currency code (e.g., "USD")
|
| string title; // Event title
|
| int importance; // 1-100 importance level
|
| double actual; // Actual value
|
| double forecast; // Forecasted value
|
| double previous; // Previous value
|
| string impact; // Impact level (Low/Medium/High)
|
|
|
| //--- Constructor
|
| SNewsEvent() : time(0), importance(0), actual(0), forecast(0), previous(0) {}
|
|
|
| //--- Comparison operator for sorting
|
| int Compare(const CObject *node, const int mode=0) const
|
| {
|
| const SNewsEvent* other = node;
|
| if(this.time < other.time) return -1;
|
| if(this.time > other.time) return 1;
|
| return 0;
|
| }
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| News Feed Handler Class |
|
| //+------------------------------------------------------------------+
|
| class CNewsFeedHandler
|
| {
|
| private:
|
| CArrayObj m_events; // Sorted array of news events
|
| bool m_initialized; // Initialization flag
|
| datetime m_lastUpdate; // Last update time
|
|
|
| // Configuration
|
| int m_updateInterval; // Update interval in seconds
|
| string m_currencies[]; // Currencies to monitor
|
|
|
| // Cache
|
| string m_cacheDir; // Cache directory
|
|
|
| public:
|
| //--- Constructor/Destructor
|
| CNewsFeedHandler(void);
|
| ~CNewsFeedHandler(void);
|
|
|
| //--- Initialization
|
| bool Initialize(void);
|
| void Shutdown(void);
|
|
|
| //--- Event Management
|
| bool UpdateEvents(void);
|
| bool GetNextEvent(const datetime time, MqlCalendarEvent &event);
|
| bool HasUpcomingEvent(const string currency, const int hoursAhead=24);
|
|
|
| //--- Configuration
|
| void SetUpdateInterval(const int seconds) { m_updateInterval = seconds; }
|
| void SetCurrencies(const string ¤cies[]);
|
|
|
| private:
|
| //--- Helper Methods
|
| bool LoadEventsFromWeb(void);
|
| bool LoadEventsFromFile(const string filename);
|
| bool SaveEventsToFile(const string filename);
|
| void SortEvents(void);
|
| bool IsEventRelevant(const SNewsEvent &event);
|
|
|
| //--- Web API Helpers
|
| string BuildRequestUrl(void) const;
|
| bool ParseJsonResponse(const string json);
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| Constructor |
|
| //+------------------------------------------------------------------+
|
| CNewsFeedHandler::CNewsFeedHandler(void) :
|
| m_initialized(false),
|
| m_lastUpdate(0),
|
| m_updateInterval(3600) // Default: 1 hour
|
| {
|
| // Default currencies to monitor
|
| ArrayResize(m_currencies, 8);
|
| m_currencies[0] = "USD";
|
| m_currencies[1] = "EUR";
|
| m_currencies[2] = "GBP";
|
| m_currencies[3] = "JPY";
|
| m_currencies[4] = "AUD";
|
| m_currencies[5] = "NZD";
|
| m_currencies[6] = "CAD";
|
| m_currencies[7] = "CHF";
|
|
|
| // Set cache directory
|
| m_cacheDir = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Files\\EscapeEA\\Cache\\";
|
| FileCreateDirectory(m_cacheDir);
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Destructor |
|
| //+------------------------------------------------------------------+
|
| CNewsFeedHandler::~CNewsFeedHandler(void)
|
| {
|
| Shutdown();
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Initialize the news handler |
|
| //+------------------------------------------------------------------+
|
| bool CNewsFeedHandler::Initialize(void)
|
| {
|
| if(m_initialized)
|
| return true;
|
|
|
| // Load cached events if available
|
| string cacheFile = m_cacheDir + "news_events.cache";
|
| if(FileIsExist(cacheFile))
|
| {
|
| if(LoadEventsFromFile(cacheFile))
|
| {
|
| m_lastUpdate = TimeCurrent();
|
| m_initialized = true;
|
| return true;
|
| }
|
| }
|
|
|
| // Otherwise, force an update
|
| m_initialized = UpdateEvents();
|
| return m_initialized;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Shutdown the news handler |
|
| //+------------------------------------------------------------------+
|
| void CNewsFeedHandler::Shutdown(void)
|
| {
|
| if(!m_initialized)
|
| return;
|
|
|
| // Save events to cache
|
| string cacheFile = m_cacheDir + "news_events.cache";
|
| SaveEventsToFile(cacheFile);
|
|
|
| // Clear events
|
| m_events.Clear();
|
| m_initialized = false;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Update the list of events from the web |
|
| //+------------------------------------------------------------------+
|
| bool CNewsFeedHandler::UpdateEvents(void)
|
| {
|
| // Check if we need to update
|
| if(TimeCurrent() - m_lastUpdate < m_updateInterval && m_events.Total() > 0)
|
| return true;
|
|
|
| // Clear existing events
|
| m_events.Clear();
|
|
|
| // Try to load from web
|
| bool success = LoadEventsFromWeb();
|
|
|
| // If web load failed, try to load from cache
|
| if(!success)
|
| {
|
| string cacheFile = m_cacheDir + "news_events.cache";
|
| if(FileIsExist(cacheFile))
|
| success = LoadEventsFromFile(cacheFile);
|
| }
|
|
|
| if(success)
|
| {
|
| // Sort events by time
|
| SortEvents();
|
| m_lastUpdate = TimeCurrent();
|
|
|
| // Save to cache
|
| string cacheFile = m_cacheDir + "news_events.cache";
|
| SaveEventsToFile(cacheFile);
|
| }
|
|
|
| return success;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Get the next upcoming event after the specified time |
|
| //+------------------------------------------------------------------+
|
| bool CNewsFeedHandler::GetNextEvent(const datetime time, MqlCalendarEvent &event)
|
| {
|
| if(!m_initialized && !Initialize())
|
| return false;
|
|
|
| // Find the first event after the specified time
|
| for(int i = 0; i < m_events.Total(); i++)
|
| {
|
| SNewsEvent* e = m_events.At(i);
|
| if(e.time > time)
|
| {
|
| // Convert to MqlCalendarEvent
|
| event.time = e.time;
|
| event.currency = e.currency;
|
| event.title = e.title;
|
| event.importance = e.importance;
|
| event.actual = e.actual;
|
| event.forecast = e.forecast;
|
| event.previous = e.previous;
|
| return true;
|
| }
|
| }
|
|
|
| return false;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Check if there's an upcoming event for the specified currency |
|
| //+------------------------------------------------------------------+
|
| bool CNewsFeedHandler::HasUpcomingEvent(const string currency, const int hoursAhead=24)
|
| {
|
| if(!m_initialized && !Initialize())
|
| return false;
|
|
|
| datetime now = TimeCurrent();
|
| datetime endTime = now + hoursAhead * 3600;
|
|
|
| for(int i = 0; i < m_events.Total(); i++)
|
| {
|
| SNewsEvent* e = m_events.At(i);
|
| if(e.time >= now && e.time <= endTime && e.currency == currency)
|
| return true;
|
| }
|
|
|
| return false;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Set the list of currencies to monitor |
|
| //+------------------------------------------------------------------+
|
| void CNewsFeedHandler::SetCurrencies(const string ¤cies[])
|
| {
|
| int count = ArraySize(currencies);
|
| ArrayResize(m_currencies, count);
|
| for(int i = 0; i < count; i++)
|
| m_currencies[i] = currencies[i];
|
|
|
| // Force reload on next update
|
| m_events.Clear();
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Load events from a web API |
|
| //+------------------------------------------------------------------+
|
| bool CNewsFeedHandler::LoadEventsFromWeb(void)
|
| {
|
| string url = BuildRequestUrl();
|
| string headers;
|
| char data[];
|
| char result[];
|
|
|
| // Make HTTP request
|
| int res = WebRequest("GET", url, headers, 5000, data, result, headers);
|
| if(res != 200)
|
| {
|
| CErrorHandler::HandleError(ERR_WEB_REQUEST, "Failed to fetch news data: " + IntegerToString(res));
|
| return false;
|
| }
|
|
|
| // Parse JSON response
|
| return ParseJsonResponse(CharArrayToString(result, 0, WHOLE_ARRAY, CP_UTF8));
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Load events from a file |
|
| //+------------------------------------------------------------------+
|
| bool CNewsFeedHandler::LoadEventsFromFile(const string filename)
|
| {
|
| int handle = FileOpen(filename, FILE_READ|FILE_BIN);
|
| if(handle == INVALID_HANDLE)
|
| {
|
| CErrorHandler::HandleError(ERR_CANNOT_OPEN_FILE, "Failed to open cache file: " + filename);
|
| return false;
|
| }
|
|
|
| // Read number of events
|
| int count;
|
| if(FileReadInteger(handle, INT_VALUE) != 0x454E5753) // "SWNE" magic number
|
| {
|
| FileClose(handle);
|
| return false;
|
| }
|
|
|
| int version = FileReadInteger(handle, INT_VALUE);
|
| if(version != 1)
|
| {
|
| FileClose(handle);
|
| return false;
|
| }
|
|
|
| count = FileReadInteger(handle, INT_VALUE);
|
|
|
| // Read events
|
| for(int i = 0; i < count; i++)
|
| {
|
| SNewsEvent* event = new SNewsEvent();
|
| event.time = (datetime)FileReadLong(handle);
|
| event.currency = FileReadString(handle);
|
| event.title = FileReadString(handle);
|
| event.importance = FileReadInteger(handle, INT_VALUE);
|
| event.actual = FileReadDouble(handle);
|
| event.forecast = FileReadDouble(handle);
|
| event.previous = FileReadDouble(handle);
|
| event.impact = FileReadString(handle);
|
|
|
| if(IsEventRelevant(*event))
|
| m_events.Add(event);
|
| else
|
| delete event;
|
| }
|
|
|
| FileClose(handle);
|
| return m_events.Total() > 0;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Save events to a file |
|
| //+------------------------------------------------------------------+
|
| bool CNewsFeedHandler::SaveEventsToFile(const string filename)
|
| {
|
| int handle = FileOpen(filename, FILE_WRITE|FILE_BIN);
|
| if(handle == INVALID_HANDLE)
|
| {
|
| CErrorHandler::HandleError(ERR_CANNOT_CREATE_FILE, "Failed to create cache file: " + filename);
|
| return false;
|
| }
|
|
|
| // Write header
|
| FileWriteInteger(handle, 0x454E5753, INT_VALUE); // "SWNE" magic number
|
| FileWriteInteger(handle, 1, INT_VALUE); // Version
|
| FileWriteInteger(handle, m_events.Total(), INT_VALUE);
|
|
|
| // Write events
|
| for(int i = 0; i < m_events.Total(); i++)
|
| {
|
| SNewsEvent* event = m_events.At(i);
|
| FileWriteLong(handle, event.time);
|
| FileWriteString(handle, event.currency, -1);
|
| FileWriteString(handle, event.title, -1);
|
| FileWriteInteger(handle, event.importance, INT_VALUE);
|
| FileWriteDouble(handle, event.actual, DOUBLE_VALUE);
|
| FileWriteDouble(handle, event.forecast, DOUBLE_VALUE);
|
| FileWriteDouble(handle, event.previous, DOUBLE_VALUE);
|
| FileWriteString(handle, event.impact, -1);
|
| }
|
|
|
| FileClose(handle);
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Sort events by time |
|
| //+------------------------------------------------------------------+
|
| void CNewsFeedHandler::SortEvents(void)
|
| {
|
| m_events.Sort(0, WHOLE_ARRAY);
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Check if an event is relevant to our trading |
|
| //+------------------------------------------------------------------+
|
| bool CNewsFeedHandler::IsEventRelevant(const SNewsEvent &event)
|
| {
|
| // Check currency
|
| bool currencyMatch = false;
|
| for(int i = 0; i < ArraySize(m_currencies); i++)
|
| {
|
| if(event.currency == m_currencies[i])
|
| {
|
| currencyMatch = true;
|
| break;
|
| }
|
| }
|
|
|
| if(!currencyMatch)
|
| return false;
|
|
|
| // Check importance (only medium and high impact)
|
| if(event.importance < 2) // Assuming 0-2 scale where 2 is highest
|
| return false;
|
|
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Build the web request URL |
|
| //+------------------------------------------------------------------+
|
| string CNewsFeedHandler::BuildRequestUrl(void) const
|
| {
|
| // This is a placeholder - replace with actual economic calendar API
|
| string url = "https://api.example.com/economic-calendar?apikey=YOUR_API_KEY";
|
|
|
| // Add date range (next 7 days)
|
| datetime now = TimeCurrent();
|
| string from = TimeToString(now, TIME_DATE|TIME_DATE);
|
| string to = TimeToString(now + 7*24*60*60, TIME_DATE|TIME_DATE);
|
|
|
| url += "&from=" + from + "&to=" + to;
|
|
|
| // Add currencies
|
| for(int i = 0; i < ArraySize(m_currencies); i++)
|
| url += "¤cy=" + m_currencies[i];
|
|
|
| return url;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Parse JSON response from web API |
|
| //+------------------------------------------------------------------+
|
| bool CNewsFeedHandler::ParseJsonResponse(const string json)
|
| {
|
| // This is a placeholder - implement actual JSON parsing
|
| // using your preferred method (JSON Parser library, StringSplit, etc.)
|
|
|
| // Example structure (simplified):
|
| /*
|
| [
|
| {
|
| "date": "2025-08-01T14:30:00Z",
|
| "currency": "USD",
|
| "title": "Non-Farm Payrolls",
|
| "importance": 3,
|
| "actual": 185,
|
| "forecast": 190,
|
| "previous": 178,
|
| "impact": "High"
|
| },
|
| ...
|
| ]
|
| */
|
|
|
| // For now, just return false to indicate parsing failure
|
| return false;
|
| }
|
| //+------------------------------------------------------------------+
|