//+------------------------------------------------------------------+
//|                        News Filter Integration Part1.mq5         |
//|                                soloharbinger                     |
//+------------------------------------------------------------------+
#property copyright "soloharbinger"
#property link      ""
#property version   "1.01"


#include <Trade/Trade.mqh>


//===================================================================
// INPUTS
//===================================================================
input group "News Filter Configuration"
input bool   EnableNewsFilter = false;                           // Enable Economic News Filter
input int    NewsMinutesBefore = 5;                              // Minutes before news to restrict
input int    NewsMinutesAfter = 5;                               // Minutes after news to restrict
input bool   RestrictNewTradesDuringNews = true;                 // Block new trades during news window
input bool   CloseOpenTradesBeforeHighImpactNews = false;        // Close all trades before news
input string SymbolCurrencyOverride = "";                        // Manual currency override e.g. "USD,JPY"
enum ENUM_NEWS_IMPORTANCE_MODE
  {
   NEWS_HIGH_ONLY = 0,
   NEWS_MODERATE_ONLY,
   NEWS_HIGH_AND_MODERATE
  };
input ENUM_NEWS_IMPORTANCE_MODE NewsImportanceMode = NEWS_HIGH_ONLY; // Which importance levels to consider


// Cache reload interval
input int CacheReloadHours = 6;                                  // How often to reload calendar cache (hours)


// News Testing helpers (Simple simulation for Part 1 demonstration)
input group "Testing Parameters"
input bool EnableNewsTesting = false;                             // Enable news testing simulation with Current symbol
input string TestNewsTime = "2025.12.15 14:30:00";                // Simulated news time (yyyy.MM.dd HH:mm:ss)
input int TestNewsDuration = 10;                                  // Simulated window length (minutes)
input bool TestNewsNow = false;                                   // Manual immediate trigger


//===================================================================
// GLOBALS
//===================================================================
MqlCalendarValue TodayEvents[];    // Calendar cache
datetime lastCalendarLoad = 0;
CTrade  trade;                     // CTrade instance for order management


//===================================================================
// OnInit
//===================================================================
int OnInit()
  {
   Print("News Filter Part1 initialized");
// Pre-load calendar if enabled to avoid lag on first tick
   if(EnableNewsFilter)
      LoadTodayCalendarEvents();
   return INIT_SUCCEEDED;
  }


//===================================================================
// OnTick
//===================================================================
void OnTick()
  {
// Static latch to ensure we don't try to close trades multiple times per window
   static bool tradesClosedForThisNewsWindow = false;

// 2. Main News Logic
   if(EnableNewsFilter)
     {
      bool isNews = IsNewsTime(_Symbol);

      // Feature: Close open trades before news if enabled
      if(CloseOpenTradesBeforeHighImpactNews && isNews)
        {
         if(!tradesClosedForThisNewsWindow)
           {
            Print("News window started -> Closing open trades.");
            CloseAllTradesForSymbol(_Symbol);
            tradesClosedForThisNewsWindow = true; // Latch
           }
        }
      else
         if(!isNews)
           {
            // Reset latch when we are out of the news window
            tradesClosedForThisNewsWindow = false;
           }
     }
  }


//===================================================================
// Load calendar (24-hour horizon)
//===================================================================
void LoadTodayCalendarEvents()
  {
   datetime now = TimeCurrent();
   datetime fromTime = now;
   datetime toTime = now + 24 * 3600;    // Load next 24 hours


   ArrayFree(TodayEvents);
   int count = CalendarValueHistory(TodayEvents, fromTime, toTime);
   PrintFormat("Loaded %d calendar records for the next 24 hours.", count);


   if(count <= 0)
     {
      // Still update timestamp to prevent constant reloading attempts
      lastCalendarLoad = now;
      return;
     }


// Debug: print first few events for verification
   for(int i = 0; i < MathMin(count, 5); i++)
     {
      MqlCalendarEvent ev;
      if(CalendarEventById(TodayEvents[i].event_id, ev))
        {
         PrintFormat("Event %d: %s | Time: %s | Importance: %d",
                     i, ev.name, TimeToString(TodayEvents[i].time), ev.importance);
        }
     }
   lastCalendarLoad = now;
  }


//===================================================================
// Utility: Get currency from calendar event (via Country ID)
//===================================================================
string GetCurrencyFromEventDirect(const MqlCalendarEvent &ev)
  {
   MqlCalendarCountry country;
   if(CalendarCountryById(ev.country_id, country))
     {
      return country.currency; // Returns "USD", "EUR", etc.
     }
   return "";
  }


//===================================================================
// Helper: Return CSV list of relevant currencies for symbol
//===================================================================
string GetRelevantCurrencies(string symbol)
  {
// 1. Check Manual Override
   if(StringLen(SymbolCurrencyOverride) > 0)
     {
      return SymbolCurrencyOverride;
     }


   string upper = symbol;
   StringToUpper(upper);


// 2. Standard Forex Pair Logic (e.g. EURUSD -> EUR,USD)
   if(StringLen(symbol) == 6)
     {
      string baseCurr = StringSubstr(upper, 0, 3);
      string quoteCurr = StringSubstr(upper, 3, 3);
      return baseCurr + "," + quoteCurr;
     }


// 3. Heuristics for Indices/Commodities
   if(StringFind(upper, "GER") != -1 || StringFind(upper, "DE40") != -1 || StringFind(upper, "DAX") != -1)
      return "EUR";
   if(StringFind(upper, "UK") != -1 || StringFind(upper, "FTSE") != -1)
      return "GBP";
   if(StringFind(upper, "US30") != -1 || StringFind(upper, "DJ") != -1)
      return "USD";
   if(StringFind(upper, "SPX") != -1 || StringFind(upper, "US500") != -1)
      return "USD";
   if(StringFind(upper, "NAS") != -1 || StringFind(upper, "US100") != -1)
      return "USD";
   if(StringFind(upper, "XAU") != -1 || StringFind(upper, "GOLD") != -1)
      return "USD";
   if(StringFind(upper, "XAG") != -1 || StringFind(upper, "SILVER") != -1)
      return "USD";
   if(StringFind(upper, "OIL") != -1 || StringFind(upper, "WTI") != -1 || StringFind(upper, "BRENT") != -1)
      return "USD";
   if(StringFind(upper, "JPN") != -1 || StringFind(upper, "NIK") != -1)
      return "JPY";
   if(StringFind(upper, "AUD") != -1)
      return "AUD";
   if(StringFind(upper, "CAD") != -1)
      return "CAD";
   if(StringFind(upper, "NZD") != -1)
      return "NZD";
   if(StringFind(upper, "CHF") != -1)
      return "CHF";
   if(StringFind(upper, "EUR") != -1)
      return "EUR";
   if(StringFind(upper, "GBP") != -1)
      return "GBP";
   if(StringFind(upper, "JPY") != -1)
      return "JPY";


// Fallback: if we can't guess, return nothing (safe mode)
   return "";
  }


//===================================================================
// Core Detection Logic
// Returns true if 'now' is inside a news window for the specific symbol
//===================================================================
bool isUpcomingNews(const string symbol)
  {
   datetime now = TimeCurrent();


// A. Cache Management
   if(lastCalendarLoad == 0 || (now - lastCalendarLoad) > (datetime)CacheReloadHours * 3600)
     {
      LoadTodayCalendarEvents();
     }


   if(ArraySize(TodayEvents) == 0)
      return false;


// B. Testing Simulation (Priority Check)
   if(EnableNewsTesting)
     {
      if(TestNewsNow)
         return true;
      datetime t = StringToTime(TestNewsTime);
      if(t > 0)
        {
         datetime start = t - TestNewsDuration * 60;
         datetime end   = t + TestNewsDuration * 60;
         if(now >= start && now <= end)
            return true;
        }
     }


// C. Optimization: Prepare Currency List OUTSIDE the loop
   string relevant = GetRelevantCurrencies(symbol);
   if(StringLen(relevant) == 0)
      return false;


   string currencyArray[];
   int currencyCount = StringSplit(relevant, ',', currencyArray);
   for(int k = 0; k < currencyCount; k++)
     {
      StringTrimLeft(currencyArray[k]);
      StringTrimRight(currencyArray[k]);
      StringToUpper(currencyArray[k]);
     }


// D. Event Scan
   for(int i = 0; i < ArraySize(TodayEvents); i++)
     {
      MqlCalendarEvent ev;
      // Retrieve event details from ID
      if(!CalendarEventById(TodayEvents[i].event_id, ev))
         continue;


      // 1. Importance Filter
      bool okImportance = false;
      switch(NewsImportanceMode)
        {
         case NEWS_HIGH_ONLY:
            okImportance = (ev.importance == CALENDAR_IMPORTANCE_HIGH);
            break;
         case NEWS_MODERATE_ONLY:
            okImportance = (ev.importance == CALENDAR_IMPORTANCE_MODERATE);
            break;
         case NEWS_HIGH_AND_MODERATE:
            okImportance = (ev.importance == CALENDAR_IMPORTANCE_HIGH || ev.importance == CALENDAR_IMPORTANCE_MODERATE);
            break;
        }
      if(!okImportance)
         continue;


      // 2. Currency Relevance Filter
      string eventCurrency = GetCurrencyFromEventDirect(ev);
      if(StringLen(eventCurrency) == 0)
         continue;


      bool affect = false;
      for(int j = 0; j < currencyCount; j++)
        {
         if(eventCurrency == currencyArray[j])
           {
            affect = true;
            break;
           }
        }
      if(!affect)
         continue;


      // 3. Time Window Check
      datetime eventTime = TodayEvents[i].time;
      datetime windowStart = eventTime - (NewsMinutesBefore * 60);
      datetime windowEnd   = eventTime + (NewsMinutesAfter * 60);


      if(now >= windowStart && now <= windowEnd)
        {
         // Optional: Print once per detection to avoid log spam
         static string lastDetectedKey = "";
         string key = ev.name + "|" + TimeToString(eventTime);
         if(key != lastDetectedKey)
           {
            PrintFormat("NEWS: %s | Time: %s | Currency: %s", ev.name, TimeToString(eventTime), eventCurrency);
            lastDetectedKey = key;
           }
         return true;
        }
     }


   return false;
  }


// Wrapper for external calls
bool IsNewsTime(string symbol)
  {
   return isUpcomingNews(symbol);
  }


//===================================================================
// Check if new trades are allowed (Main Filter Gate)
//===================================================================
bool CanOpenNewTrade(string symbol)
  {
   if(!EnableNewsFilter)
      return true;
   if(!RestrictNewTradesDuringNews)
      return true;

   if(IsNewsTime(symbol))
     {
      return false; // Block trade
     }

   return true;
  }


//===================================================================
// CloseAllTradesForSymbol
// Implements safe reverse iteration and CTrade closing
//===================================================================
void CloseAllTradesForSymbol(string symbol)
  {
// Iterate backward to safely close multiple positions
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      ulong ticket = PositionGetTicket(i);
      if(ticket == 0)
         continue;

      if(!PositionSelectByTicket(ticket))
         continue;


      // Filter by symbol
      if(PositionGetString(POSITION_SYMBOL) != symbol)
         continue;


      // Close Logic
      bool closed = trade.PositionClose(ticket);

      if(closed)
        {
         PrintFormat("Closed position #%d on %s due to news.", ticket, symbol);
        }
      else
        {
         PrintFormat("Failed to close #%d. Error: %d", ticket, GetLastError());
        }
     }
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
