//+------------------------------------------------------------------+
//|                                                    NewsTypes.mqh |
//|                                    Copyright 2026, soloharbinger |
//|                      https://www.mql5.com/en/users/soloharbinger |
//+------------------------------------------------------------------+
#property copyright   "Copyright 2026, soloharbinger"
#property link        "https://www.mql5.com/en/users/soloharbinger"
#property version     "1.00"
#property description "Vocabulary for the news filter: the tier scale, one event record, one rule, and the classifier's configuration."

#ifndef NEWSFILTERSUITE_NEWSTYPES_MQH
#define NEWSFILTERSUITE_NEWSTYPES_MQH

//+------------------------------------------------------------------+
//| The tier scale                                                   |
//+------------------------------------------------------------------+
enum ENUM_NEWS_TIER
  {
   NEWS_TIER_NONE     = 0,   // Untagged by the calendar
   NEWS_TIER_LOW      = 1,   // Calendar: low importance
   NEWS_TIER_MODERATE = 2,   // Calendar: moderate importance
   NEWS_TIER_HIGH     = 3,   // Calendar: high importance
   NEWS_TIER_SPECIAL  = 4    // Ours: releases the calendar cannot separate from the rest of High
  };

//+------------------------------------------------------------------+
//| How a rule recognises the event it was written for               |
//+------------------------------------------------------------------+
enum ENUM_NEWS_MATCH
  {
   NEWS_MATCH_CODE  = 0,     // Whole MqlCalendarEvent.event_code
   NEWS_MATCH_NAME  = 1,     // Substring of the event name
   NEWS_MATCH_EXACT = 2      // Whole event name
  };

//+------------------------------------------------------------------+
//| One rule: which events get moved to which tier, and how much     |
//| room they are given                                              |
//+------------------------------------------------------------------+
struct SNewsRule
  {
   string            currency;       // Three-letter code, or "*" for any
   string            match;          // Stored uppercase, read according to matchMode
   ENUM_NEWS_MATCH   matchMode;
   ENUM_NEWS_TIER    tier;           // Tier this event moves to, up or down
   int               minutesBefore;  // -1 inherits the configured window for the resulting tier
   int               minutesAfter;   // -1 inherits
  };

//+------------------------------------------------------------------+
//| One event, after classification                                  |
//+------------------------------------------------------------------+
struct SNewsEvent
  {
   datetime          time;
   string            currency;
   string            name;
   string            code;
   ENUM_NEWS_TIER    reportedTier;   // What the source said
   ENUM_NEWS_TIER    tier;           // What the rules decided
   int               minutesBefore;  // This event's own window, not a global setting
   int               minutesAfter;
   bool              affectsSymbol;  // Relevance to the chart symbol, decided once at load
   int               ruleIndex;      // Rule that classified it, -1 if none matched
  };

//+------------------------------------------------------------------+
//| Everything the classifier needs, in one place                    |
//+------------------------------------------------------------------+
struct SNewsConfig
  {
   //--- What counts as news worth acting on
   ENUM_NEWS_TIER    minimumTier;

   //--- Windows in minutes. Special events get their own pair
   int               defaultBefore;
   int               defaultAfter;
   int               specialBefore;
   int               specialAfter;

   //--- Rule table
   bool              loadBuiltInRules;
   string            rulesFile;

   //--- Diagnostics
   bool              verbose;

   void              SetDefaults(void);
  };

//+------------------------------------------------------------------+
//| Starting values, so an EA overrides only what it cares about     |
//+------------------------------------------------------------------+
void SNewsConfig::SetDefaults(void)
  {
   minimumTier      = NEWS_TIER_HIGH;

   defaultBefore    = 5;
   defaultAfter     = 5;
   specialBefore    = 30;
   specialAfter     = 60;

   loadBuiltInRules = true;
   rulesFile        = "NewsRules.csv";

   verbose          = false;
  }

//+------------------------------------------------------------------+
//| Calendar importance as the 0 to 3 number the historical file     |
//| uses. Converted by name, never by the enum's own value           |
//+------------------------------------------------------------------+
int ImportanceFromCalendar(const ENUM_CALENDAR_EVENT_IMPORTANCE importance)
  {
   switch(importance)
     {
      case CALENDAR_IMPORTANCE_HIGH:
         return 3;
      case CALENDAR_IMPORTANCE_MODERATE:
         return 2;
      case CALENDAR_IMPORTANCE_LOW:
         return 1;
      default:
         return 0;   // CALENDAR_IMPORTANCE_NONE
     }
  }

//+------------------------------------------------------------------+
//| Importance number, 0 to 3, to tier                               |
//+------------------------------------------------------------------+
ENUM_NEWS_TIER TierFromImportance(const int importance)
  {
   if(importance <= 0)
      return NEWS_TIER_NONE;
   if(importance >= 3)
      return NEWS_TIER_HIGH;
   return (ENUM_NEWS_TIER)importance;
  }

//+------------------------------------------------------------------+
//| Readable tier name for logs and the inspector                    |
//+------------------------------------------------------------------+
string TierToString(const ENUM_NEWS_TIER tier)
  {
   switch(tier)
     {
      case NEWS_TIER_SPECIAL:
         return "SPECIAL";
      case NEWS_TIER_HIGH:
         return "High";
      case NEWS_TIER_MODERATE:
         return "Moderate";
      case NEWS_TIER_LOW:
         return "Low";
      default:
         return "None";
     }
  }

//+------------------------------------------------------------------+
//| Read a tier written as a number in a CSV cell                    |
//+------------------------------------------------------------------+
ENUM_NEWS_TIER TierFromText(const string text)
  {
   int value = (int)StringToInteger(text);
   if(value <= 0)
      return NEWS_TIER_NONE;
   if(value >= 4)
      return NEWS_TIER_SPECIAL;
   return (ENUM_NEWS_TIER)value;
  }

//+------------------------------------------------------------------+
//| True when a tier is watched: at or above the minimum, never None |
//+------------------------------------------------------------------+
bool TierMeetsThreshold(const ENUM_NEWS_TIER tier, const ENUM_NEWS_TIER minimum)
  {
   return (tier >= minimum && tier > NEWS_TIER_NONE);
  }

//+------------------------------------------------------------------+
//| Trim and uppercase in one step, used on every parsed field       |
//+------------------------------------------------------------------+
string NormalizeField(string text)
  {
   StringTrimLeft(text);
   StringTrimRight(text);
   StringToUpper(text);
   return text;
  }

#endif // NEWSFILTERSUITE_NEWSTYPES_MQH
//+------------------------------------------------------------------+
