//+------------------------------------------------------------------+
//|                                     CalendarEventMonitor-EA.mq5  |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026,MetaQuotes Ltd."
#property version   "1.00"
#property description "Non-trading EA for tracking calendar events"

#property strict

//+------------------------------------------------------------------+
//| inputs                                                           |
//+------------------------------------------------------------------+
input datetime InpDateFrom=0;                  // Start date (0=today 00:00)
input datetime InpDateTo=0;                    // End date (0=+7 days)
input string   InpCountryCode="";              // Country filter: "US","EU","JP"
input string   InpCurrency="USD";              // Currency filter: "USD","EUR","GBP"
input int      InpTimerInterval=5;             // Polling interval (sec)
input bool     InpPrintChanges=true;           // Output to the log

//+------------------------------------------------------------------+
//| global variables                                                 |
//+------------------------------------------------------------------+
MqlCalendarValue calendar_cache[];     // value cache
ulong            last_change_id=0;     // change cursor
bool             is_initialized=false;
int              total_events=0;

//+------------------------------------------------------------------+
//| initialization                                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   Print("==== CalendarEventMonitor-EA: initializing...");

   datetime from=(InpDateFrom==0)?StringToTime(TimeToString(TimeCurrent(),TIME_DATE)+" 00:00"):InpDateFrom;
   datetime to  =(InpDateTo==0)?from+7*24*3600:InpDateTo;

   string country=(InpCountryCode=="")?NULL:InpCountryCode;
   string currency=(InpCurrency=="")?NULL:InpCurrency;

   if(!LoadCalendar(calendar_cache,from,to,country,currency))
      return INIT_FAILED;

   total_events=ArraySize(calendar_cache);
   is_initialized=true;

   if(InpPrintChanges)
      ArrayPrint(calendar_cache);

   Print("✅ Loaded ",total_events," values. Initial change_id: ",last_change_id);
   EventSetTimer(MathMax(1,InpTimerInterval));
   return INIT_SUCCEEDED;
  }

//+------------------------------------------------------------------+
//| timer: incremental update                                        |
//+------------------------------------------------------------------+
void OnTimer()
  {
   if(!is_initialized)
      return;

   MqlCalendarValue updates[];
   ResetLastError();

//--- API automatically updates last_change_id by reference
   if(!CalendarValueLast(last_change_id,updates,
                         (InpCountryCode=="")?NULL:InpCountryCode,
                         (InpCurrency=="")?NULL:InpCurrency))
     {
      int err=GetLastError();
      //--- 0=SUCCESS/NO_NEW_DATA,5402=ERR_CALENDAR_NO_CHANGES
      if(err!=0&&err!=5402)
         Print("🔴️ CalendarValueLast error: ",err);
      return;
     }

   int cnt=ArraySize(updates);
   if(cnt==0)
      return;

   Print("🟢 Obtained ",cnt," updates. Новый change_id: ",last_change_id);
   if(InpPrintChanges)
      ArrayPrint(updates);
   SyncCache(updates);
  }

//+------------------------------------------------------------------+
//| cache synchronization                                            |
//+------------------------------------------------------------------+
void SyncCache(const MqlCalendarValue &updates[])
  {
   int upd_cnt=ArraySize(updates);
   int cache_sz=ArraySize(calendar_cache);

   for(int u=0;u<upd_cnt;u++)
     {
      bool found=false;
      for(int c=0;c<cache_sz;c++)
        {
         if(calendar_cache[c].id==updates[u].id)
           {
            calendar_cache[c]=updates[u];
            found=true;
            break;
           }
        }
      if(!found)
        {
         ArrayResize(calendar_cache,cache_sz+1);
         calendar_cache[cache_sz]=updates[u];
         cache_sz++;
         total_events++;
        }
     }
  }

//+------------------------------------------------------------------+
//| load history                                                     |
//+------------------------------------------------------------------+
bool LoadCalendar(MqlCalendarValue& values[],const datetime from,const datetime to,
                  const string country_code=NULL,const string currency=NULL,
                  const int max_retries=5)
  {
   int retry_count=0;
   while(retry_count<max_retries)
     {
      ResetLastError();
      if(CalendarValueHistory(values,from,to,country_code,currency))
         return true;

      int err=GetLastError();
      //--- 5400=TIMEOUT,5401=MORE_DATA,5200/5201=SERVER_BUSY/NO_DATA — recoverable
      if(err==5400||err==5401||err==5200||err==5201)
        {
         retry_count++;
         Sleep(1000);
         continue;
        }
      Print("❌ Error loading calendar: ",err);
      return false;
     }
   Print("❌ LoadCalendar error after ",max_retries," attempts.");
   return false;
  }

//+------------------------------------------------------------------+
//| deinitialization                                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
   Print("🛑 Stopped (reason=",reason,")");
   ArrayFree(calendar_cache);
  }
//+------------------------------------------------------------------+
