//+------------------------------------------------------------------+ //| 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 ¤cies[]) { 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