457 lines
15 KiB
MQL5
457 lines
15 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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;
|
|
}
|
|
//+------------------------------------------------------------------+
|