mql5/Experts/Advisors/DualEA/Include/CEconomicCalendar.mqh
2026-04-14 20:08:20 -04:00

653 lines
20 KiB
MQL5

//+------------------------------------------------------------------+
//| CEconomicCalendar.mqh - MQL5 Economic Calendar Integration |
//| Provides direct access to MQL5 Economic Calendar for real-time |
//| news event detection and impact analysis |
//| Based on MQL5 article: "Using the MQL5 Economic Calendar for News |
//| Filter (Part 3): Surviving Terminal Restarts During News Window" |
//+------------------------------------------------------------------+
#ifndef CECONOMICCALENDAR_MQH
#define CECONOMICCALENDAR_MQH
#include "CErrorRecovery.mqh"
#include "CUnifiedFileIO.mqh"
// Event importance levels
enum ENUM_EVENT_IMPORTANCE
{
EVENT_IMPORTANCE_LOW = 0,
EVENT_IMPORTANCE_MEDIUM = 1,
EVENT_IMPORTANCE_HIGH = 2
};
// Event type
enum ENUM_EVENT_TYPE
{
EVENT_TYPE_NONE = 0,
EVENT_TYPE_ECONOMIC = 1,
EVENT_TYPE_POLITICAL = 2,
EVENT_TYPE_NATURAL = 3,
EVENT_TYPE_CENTRAL_BANK = 4
};
//+------------------------------------------------------------------+
//| Economic Event Structure |
//+------------------------------------------------------------------+
struct SEconomicEvent
{
long id;
string name;
string currency;
string country;
datetime event_time;
int importance; // 0=low, 1=medium, 2=high
double actual_value;
double forecast_value;
double previous_value;
bool has_actual;
bool has_forecast;
bool has_previous;
string event_type;
SEconomicEvent()
{
id = 0;
name = "";
currency = "";
country = "";
event_time = 0;
importance = 0;
actual_value = 0.0;
forecast_value = 0.0;
previous_value = 0.0;
has_actual = false;
has_forecast = false;
has_previous = false;
event_type = "";
}
};
//+------------------------------------------------------------------+
//| News Impact Analysis Result |
//+------------------------------------------------------------------+
struct SNewsImpact
{
bool is_news_time;
int current_importance;
int upcoming_importance;
datetime next_event_time;
string next_event_name;
double volatility_prediction;
double confidence_adjustment;
string reason;
SNewsImpact()
{
is_news_time = false;
current_importance = 0;
upcoming_importance = 0;
next_event_time = 0;
next_event_name = "";
volatility_prediction = 0.0;
confidence_adjustment = 1.0;
reason = "";
}
};
//+------------------------------------------------------------------+
//| Economic Calendar Manager |
//+------------------------------------------------------------------+
class CEconomicCalendar
{
private:
static CEconomicCalendar* s_instance;
// Event cache
SEconomicEvent m_events[];
int m_event_count;
int m_max_events;
datetime m_last_update;
int m_update_interval_sec;
// Currency filter
string m_currencies[];
int m_currency_count;
// Configuration
int m_lookback_hours;
int m_lookahead_hours;
bool m_use_api;
bool m_use_fallback_file;
string m_fallback_filename;
// Statistics
int m_api_calls;
int m_cache_hits;
int m_cache_misses;
// Error recovery
CErrorRecovery* m_error_recovery;
CUnifiedFileIO* m_file_io;
// Market watch indices for fast lookup
int m_currency_handles[];
public:
CEconomicCalendar()
{
m_event_count = 0;
m_max_events = 500;
ArrayResize(m_events, m_max_events);
m_last_update = 0;
m_update_interval_sec = 300; // 5 minutes
m_currency_count = 0;
ArrayResize(m_currencies, 10);
m_lookback_hours = 2;
m_lookahead_hours = 24;
m_use_api = true;
m_use_fallback_file = true;
m_fallback_filename = "DualEA\\economic_calendar.json";
m_api_calls = 0;
m_cache_hits = 0;
m_cache_misses = 0;
m_error_recovery = CErrorRecovery::Instance();
m_file_io = CUnifiedFileIO::Instance();
}
~CEconomicCalendar()
{
}
static CEconomicCalendar* Instance()
{
if(s_instance == NULL)
s_instance = new CEconomicCalendar();
return s_instance;
}
static void Destroy()
{
if(s_instance != NULL)
{
delete s_instance;
s_instance = NULL;
}
}
//+------------------------------------------------------------------+
//| Configuration |
//+------------------------------------------------------------------+
void SetCurrencies(const string &currencies[])
{
int count = ArraySize(currencies);
ArrayResize(m_currencies, count);
for(int i = 0; i < count; i++)
{
m_currencies[i] = currencies[i];
}
m_currency_count = count;
}
void SetUpdateInterval(int seconds) { m_update_interval_sec = seconds; }
void SetLookbackHours(int hours) { m_lookback_hours = hours; }
void SetLookaheadHours(int hours) { m_lookahead_hours = hours; }
void SetUseAPI(bool use) { m_use_api = use; }
void SetFallbackFile(const string filename) { m_fallback_filename = filename; }
//+------------------------------------------------------------------+
//| Update event cache from MQL5 Calendar |
//+------------------------------------------------------------------+
bool UpdateEvents()
{
datetime now = TimeCurrent();
// Check if update needed
if(m_last_update > 0 && (now - m_last_update) < m_update_interval_sec)
{
m_cache_hits++;
return true;
}
m_cache_misses++;
// Clear existing events
m_event_count = 0;
ArrayResize(m_events, m_max_events);
bool success = false;
if(m_use_api)
{
success = FetchFromAPI();
}
if(!success && m_use_fallback_file)
{
success = FetchFromFallbackFile();
}
if(success)
{
m_last_update = now;
SortEventsByTime();
Log("Updated economic calendar: " + IntegerToString(m_event_count) + " events");
}
return success;
}
//+------------------------------------------------------------------+
//| Fetch events from MQL5 Calendar API |
//+------------------------------------------------------------------+
bool FetchFromAPI()
{
// Calculate date range
datetime from_date = TimeCurrent() - (m_lookback_hours * 3600);
datetime to_date = TimeCurrent() + (m_lookahead_hours * 3600);
MqlCalendarValue values[];
// Try to get calendar values
// If specific currencies are set, filter by them
int retrieved = 0;
if(m_currency_count > 0)
{
// Fetch for specific currencies
for(int c = 0; c < m_currency_count && m_event_count < m_max_events; c++)
{
string currency = m_currencies[c];
// Reset array
ArrayResize(values, 0);
// Get calendar values
int count = CalendarValueHistory(values, from_date, to_date, currency);
if(count > 0)
{
for(int i = 0; i < count && m_event_count < m_max_events; i++)
{
if(AddCalendarValue(values[i]))
retrieved++;
}
}
m_api_calls++;
}
}
else
{
// Get all calendar values
int count = CalendarValueHistory(values, from_date, to_date, NULL);
if(count > 0)
{
for(int i = 0; i < count && m_event_count < m_max_events; i++))
{
if(AddCalendarValue(values[i]))
retrieved++;
}
}
m_api_calls++;
}
return (retrieved > 0);
}
//+------------------------------------------------------------------+
//| Add calendar value to cache |
//+------------------------------------------------------------------+
bool AddCalendarValue(const MqlCalendarValue &value)
{
if(m_event_count >= m_max_events)
return false;
SEconomicEvent event;
// Get event details
MqlCalendarEvent event_info;
if(!CalendarEventById(value.event_id, event_info))
return false;
// Get country info
MqlCalendarCountry country_info;
if(!CalendarCountryById(event_info.country_id, country_info))
return false;
event.id = value.event_id;
event.name = event_info.name;
event.currency = country_info.currency;
event.country = country_info.code;
event.event_time = value.time;
// Determine importance
switch(event_info.importance)
{
case CALENDAR_IMPORTANCE_LOW:
event.importance = EVENT_IMPORTANCE_LOW;
break;
case CALENDAR_IMPORTANCE_MODERATE:
event.importance = EVENT_IMPORTANCE_MEDIUM;
break;
case CALENDAR_IMPORTANCE_HIGH:
event.importance = EVENT_IMPORTANCE_HIGH;
break;
}
// Values
if(value.actual_type != CALENDAR_VALUE_TYPE_NA)
{
event.actual_value = value.actual_value;
event.has_actual = true;
}
if(value.forecast_type != CALENDAR_VALUE_TYPE_NA)
{
event.forecast_value = value.forecast_value;
event.has_forecast = true;
}
if(value.prev_value != 0)
{
event.previous_value = value.prev_value;
event.has_previous = true;
}
// Event type
switch(event_info.type)
{
case CALENDAR_TYPE_EVENT:
event.event_type = "economic";
break;
case CALENDAR_TYPE_INDICATOR:
event.event_type = "indicator";
break;
case CALENDAR_TYPE_HOLIDAY:
event.event_type = "holiday";
break;
default:
event.event_type = "other";
}
// Check for duplicates
for(int i = 0; i < m_event_count; i++)
{
if(m_events[i].id == event.id && m_events[i].event_time == event.event_time)
return false; // Duplicate
}
m_events[m_event_count] = event;
m_event_count++;
return true;
}
//+------------------------------------------------------------------+
//| Fetch from fallback file |
//+------------------------------------------------------------------+
bool FetchFromFallbackFile()
{
string content;
if(!m_file_io.ReadFile(m_fallback_filename, content, true))
return false;
// Parse JSON (simplified - would need full JSON parser for production)
// For now, just log that we loaded the file
Log("Loaded fallback calendar file: " + m_fallback_filename);
return true;
}
//+------------------------------------------------------------------+
//| Sort events by time |
//+------------------------------------------------------------------+
void SortEventsByTime()
{
// Simple bubble sort for small arrays
for(int i = 0; i < m_event_count - 1; i++)
{
for(int j = 0; j < m_event_count - i - 1; j++)
{
if(m_events[j].event_time > m_events[j + 1].event_time)
{
SEconomicEvent temp = m_events[j];
m_events[j] = m_events[j + 1];
m_events[j + 1] = temp;
}
}
}
}
//+------------------------------------------------------------------+
//| Check if news time (main entry point for trading decisions) |
//+------------------------------------------------------------------+
SNewsImpact CheckNewsImpact(const string symbol, int buffer_minutes_before = 30, int buffer_minutes_after = 30)
{
SNewsImpact impact;
// Ensure events are current
UpdateEvents();
datetime now = TimeCurrent();
// Extract currency from symbol
string base_currency = StringSubstr(symbol, 0, 3);
string quote_currency = StringSubstr(symbol, 3, 3);
// Check for active news events
for(int i = 0; i < m_event_count; i++)
{
SEconomicEvent &event = m_events[i];
// Skip if not relevant to this symbol
if(!IsEventRelevant(event, base_currency, quote_currency))
continue;
// Calculate effective window
datetime window_start = event.event_time - (buffer_minutes_before * 60);
datetime window_end = event.event_time + (buffer_minutes_after * 60);
// Check if currently in news window
if(now >= window_start && now <= window_end)
{
impact.is_news_time = true;
impact.current_importance = MathMax(impact.current_importance, event.importance);
if(impact.reason != "") impact.reason += "; ";
impact.reason += event.name + " (" + IntegerToString(event.importance) + ")";
}
// Check for upcoming events
if(event.event_time > now && event.event_time < impact.next_event_time)
{
impact.next_event_time = event.event_time;
impact.next_event_name = event.name;
impact.upcoming_importance = event.importance;
}
}
// Calculate confidence adjustment
impact.confidence_adjustment = CalculateConfidenceAdjustment(impact);
impact.volatility_prediction = PredictVolatility(impact);
return impact;
}
//+------------------------------------------------------------------+
//| Check if event is relevant to symbol |
//+------------------------------------------------------------------+
bool IsEventRelevant(const SEconomicEvent &event, const string base_currency, const string quote_currency)
{
// Direct currency match
if(event.currency == base_currency || event.currency == quote_currency)
return true;
// Major USD events affect all pairs
if(event.currency == "USD" && (base_currency == "USD" || quote_currency == "USD"))
return true;
// Global events (no specific currency)
if(event.currency == "" && event.importance >= EVENT_IMPORTANCE_HIGH)
return true;
return false;
}
//+------------------------------------------------------------------+
//| Calculate confidence adjustment based on news |
//+------------------------------------------------------------------+
double CalculateConfidenceAdjustment(const SNewsImpact &impact)
{
if(!impact.is_news_time)
return 1.0;
// Reduce confidence during news
double adjustment = 1.0;
switch(impact.current_importance)
{
case EVENT_IMPORTANCE_LOW:
adjustment = 0.9; // 10% reduction
break;
case EVENT_IMPORTANCE_MEDIUM:
adjustment = 0.75; // 25% reduction
break;
case EVENT_IMPORTANCE_HIGH:
adjustment = 0.5; // 50% reduction
break;
}
return adjustment;
}
//+------------------------------------------------------------------+
//| Predict volatility based on news |
//+------------------------------------------------------------------+
double PredictVolatility(const SNewsImpact &impact)
{
if(!impact.is_news_time)
return 0.0;
// Base prediction on importance
double base_volatility = 0.0;
switch(impact.current_importance)
{
case EVENT_IMPORTANCE_LOW:
base_volatility = 0.2;
break;
case EVENT_IMPORTANCE_MEDIUM:
base_volatility = 0.5;
break;
case EVENT_IMPORTANCE_HIGH:
base_volatility = 1.0;
break;
}
// Could be enhanced with historical volatility data
return base_volatility;
}
//+------------------------------------------------------------------+
//| Get next significant event |
//+------------------------------------------------------------------+
SEconomicEvent GetNextEvent(int min_importance = EVENT_IMPORTANCE_MEDIUM)
{
SEconomicEvent next;
UpdateEvents();
datetime now = TimeCurrent();
for(int i = 0; i < m_event_count; i++)
{
if(m_events[i].event_time > now && m_events[i].importance >= min_importance)
{
next = m_events[i];
break;
}
}
return next;
}
//+------------------------------------------------------------------+
//| Export events to file (for ML training) |
//+------------------------------------------------------------------+
bool ExportEventsToFile(const string filename)
{
string content = "timestamp,event_name,currency,importance,actual,forecast,previous,type\n";
for(int i = 0; i < m_event_count; i++)
{
SEconomicEvent &e = m_events[i];
content += StringFormat("%s,%s,%s,%d,%.5f,%.5f,%.5f,%s\n",
TimeToString(e.event_time, TIME_DATE|TIME_SECONDS),
e.name,
e.currency,
e.importance,
e.actual_value,
e.forecast_value,
e.previous_value,
e.event_type
);
}
return m_file_io.WriteFile(filename, content, true, false);
}
//+------------------------------------------------------------------+
//| Statistics |
//+------------------------------------------------------------------+
string GetStatistics()
{
string stats = StringFormat(
"=== Economic Calendar Statistics ===\n" +
"Events cached: %d\n" +
"API calls: %d\n" +
"Cache hits: %d\n" +
"Cache misses: %d\n" +
"Last update: %s",
m_event_count,
m_api_calls,
m_cache_hits,
m_cache_misses,
(m_last_update > 0 ? TimeToString(m_last_update) : "Never")
);
return stats;
}
//+------------------------------------------------------------------+
//| Utility functions |
//+------------------------------------------------------------------+
void Log(const string message)
{
Print("[EconomicCalendar] " + message);
}
int GetEventCount() const { return m_event_count; }
bool IsCacheValid() const { return (TimeCurrent() - m_last_update) < m_update_interval_sec; }
datetime GetLastUpdate() const { return m_last_update; }
};
// Static instance initialization
CEconomicCalendar* CEconomicCalendar::s_instance = NULL;
// Convenience macros
#define CHECK_NEWS_IMPACT(symbol, buffer_before, buffer_after) \
CEconomicCalendar::Instance().CheckNewsImpact(symbol, buffer_before, buffer_after)
#define GET_NEXT_EVENT(min_importance) \
CEconomicCalendar::Instance().GetNextEvent(min_importance)
#define UPDATE_CALENDAR() \
CEconomicCalendar::Instance().UpdateEvents()
#endif // CECONOMICCALENDAR_MQH