//+------------------------------------------------------------------+
//| 80_Currency_Strength_Coverage_Auditor_v1_00.mq5                |
//| Audits the pair coverage behind the legacy xMeter calculation. |
//+------------------------------------------------------------------+
#property strict
#property copyright "FXおもしろラボ"
#property link      "https://fx-omoshiro-lab.com/"
#property version   "1.00"
#property description "Read-only audit of legacy xMeter strength, pair coverage and leave-one-out sensitivity."
#property indicator_chart_window
#property indicator_plots   0
#property indicator_buffers 41

input string InpSymbols = "EURUSD,GBPUSD,AUDUSD,USDJPY,USDCHF,USDCAD,EURJPY,EURGBP,EURCHF,EURAUD,GBPJPY,GBPCHF"; // Exact broker symbols, comma-separated
input int    InpRefreshSeconds = 1;  // Audit refresh period
input int    InpEarlyDayMinutes = 60;// Flag the first N minutes of the D1 bar
input bool   InpShowPanel = true;    // Show audit panel

#define OBJECT_PREFIX "MQL80_COVERAGE_"
#define CURRENCY_COUNT 7
#define MAX_PAIRS 21
#define MAX_COUNTERPARTS 6

enum ENUM_BUFFER_INDEX
{
   BUFFER_READY = 0,
   BUFFER_SNAPSHOTS,
   BUFFER_CONFIGURED_PAIRS,
   BUFFER_VALID_PAIRS,
   BUFFER_UNAVAILABLE_PAIRS,
   BUFFER_CURRENCY_COUNT,
   BUFFER_DAY_OPEN_MIN,
   BUFFER_DAY_OPEN_MAX,
   BUFFER_DAY_AGE_MINUTES,
   BUFFER_EARLY_DAY,
   BUFFER_PANEL_OBJECTS,
   BUFFER_GRADE_CHECKSUM,
   BUFFER_VALID_MASK,
   BUFFER_STRENGTH_FIRST,
   BUFFER_CONFIG_COVERAGE_FIRST = BUFFER_STRENGTH_FIRST + CURRENCY_COUNT,
   BUFFER_VALID_COVERAGE_FIRST = BUFFER_CONFIG_COVERAGE_FIRST + CURRENCY_COUNT,
   BUFFER_SENSITIVITY_FIRST = BUFFER_VALID_COVERAGE_FIRST + CURRENCY_COUNT
};

double g_ready_buffer[];
double g_snapshots_buffer[];
double g_configured_pairs_buffer[];
double g_valid_pairs_buffer[];
double g_unavailable_pairs_buffer[];
double g_currency_count_buffer[];
double g_day_open_min_buffer[];
double g_day_open_max_buffer[];
double g_day_age_minutes_buffer[];
double g_early_day_buffer[];
double g_panel_objects_buffer[];
double g_grade_checksum_buffer[];
double g_valid_mask_buffer[];
double g_strength_usd_buffer[];
double g_strength_eur_buffer[];
double g_strength_gbp_buffer[];
double g_strength_jpy_buffer[];
double g_strength_chf_buffer[];
double g_strength_cad_buffer[];
double g_strength_aud_buffer[];
double g_config_usd_buffer[];
double g_config_eur_buffer[];
double g_config_gbp_buffer[];
double g_config_jpy_buffer[];
double g_config_chf_buffer[];
double g_config_cad_buffer[];
double g_config_aud_buffer[];
double g_valid_usd_buffer[];
double g_valid_eur_buffer[];
double g_valid_gbp_buffer[];
double g_valid_jpy_buffer[];
double g_valid_chf_buffer[];
double g_valid_cad_buffer[];
double g_valid_aud_buffer[];
double g_sensitivity_usd_buffer[];
double g_sensitivity_eur_buffer[];
double g_sensitivity_gbp_buffer[];
double g_sensitivity_jpy_buffer[];
double g_sensitivity_chf_buffer[];
double g_sensitivity_cad_buffer[];
double g_sensitivity_aud_buffer[];

string g_currency_names[CURRENCY_COUNT] =
{
   "USD", "EUR", "GBP", "JPY", "CHF", "CAD", "AUD"
};
string g_pair_symbols[];
int    g_pair_base_index[];
int    g_pair_quote_index[];
bool   g_pair_available[];
int    g_pair_grade[];
double g_pair_ratio[];
datetime g_pair_day_open[];
int    g_pair_count = 0;

int    g_config_coverage[CURRENCY_COUNT];
int    g_valid_coverage[CURRENCY_COUNT];
double g_strength[CURRENCY_COUNT];
double g_sensitivity[CURRENCY_COUNT];
double g_contributions[CURRENCY_COUNT][MAX_PAIRS];

int      g_snapshots = 0;
int      g_valid_pairs = 0;
int      g_unavailable_pairs = 0;
datetime g_day_open_min = 0;
datetime g_day_open_max = 0;
double   g_day_age_minutes = 0.0;
bool     g_early_day = false;
long     g_grade_checksum = 0;
long     g_valid_mask = 0;

bool BindBuffers();
bool BuffersReady();
bool BuildPairList();
bool AddPair(const string symbol_name);
bool PairAlreadyAdded(const string symbol_name);
bool DetectPairCurrencies(const string symbol_name,
                          int &base_index,
                          int &quote_index);
int CurrencyIndex(const string currency);
void RefreshAudit();
void TakeSnapshot();
bool ReadPair(const int pair_index);
int GradeFromRatio(const double ratio);
void CalculateCurrencyResults();
void UpdateBuffers();
void DrawPanel();
bool EnsureBackground();
bool SetPanelLine(const int row, const string text, const color shade,
                  const int font_size = 10);
string StrengthText(const int currency_index);
string SensitivityText(const int currency_index);
string CoverageStatus(const int currency_index);
color CoverageColor(const int currency_index);
int CountPanelObjects();
void DeletePanel();

int OnInit()
{
   if(InpRefreshSeconds < 1 || InpRefreshSeconds > 60 ||
      InpEarlyDayMinutes < 0 || InpEarlyDayMinutes > 1440)
   {
      Print("currency strength coverage auditor: invalid input parameters");
      return INIT_PARAMETERS_INCORRECT;
   }

   if(!BindBuffers() || !BuildPairList())
      return INIT_FAILED;

   IndicatorSetString(INDICATOR_SHORTNAME,
                      "Currency strength coverage auditor");
   ResetLastError();
   if(!EventSetTimer(InpRefreshSeconds))
   {
      PrintFormat("currency strength coverage auditor: timer setup failed error=%d",
                  GetLastError());
      return INIT_FAILED;
   }
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
   EventKillTimer();
   DeletePanel();
}

int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
{
   if(rates_total < 1)
      return 0;
   if(g_snapshots == 0)
      RefreshAudit();
   else
      UpdateBuffers();
   return rates_total;
}

void OnTimer()
{
   RefreshAudit();
}

bool BindBuffers()
{
   if(!SetIndexBuffer(0, g_ready_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(1, g_snapshots_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(2, g_configured_pairs_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(3, g_valid_pairs_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(4, g_unavailable_pairs_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(5, g_currency_count_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(6, g_day_open_min_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(7, g_day_open_max_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(8, g_day_age_minutes_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(9, g_early_day_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(10, g_panel_objects_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(11, g_grade_checksum_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(12, g_valid_mask_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(13, g_strength_usd_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(14, g_strength_eur_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(15, g_strength_gbp_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(16, g_strength_jpy_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(17, g_strength_chf_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(18, g_strength_cad_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(19, g_strength_aud_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(20, g_config_usd_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(21, g_config_eur_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(22, g_config_gbp_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(23, g_config_jpy_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(24, g_config_chf_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(25, g_config_cad_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(26, g_config_aud_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(27, g_valid_usd_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(28, g_valid_eur_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(29, g_valid_gbp_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(30, g_valid_jpy_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(31, g_valid_chf_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(32, g_valid_cad_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(33, g_valid_aud_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(34, g_sensitivity_usd_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(35, g_sensitivity_eur_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(36, g_sensitivity_gbp_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(37, g_sensitivity_jpy_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(38, g_sensitivity_chf_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(39, g_sensitivity_cad_buffer, INDICATOR_CALCULATIONS) ||
      !SetIndexBuffer(40, g_sensitivity_aud_buffer, INDICATOR_CALCULATIONS))
   {
      PrintFormat("currency strength coverage auditor: SetIndexBuffer failed error=%d",
                  GetLastError());
      return false;
   }

   ArraySetAsSeries(g_ready_buffer, true);
   ArraySetAsSeries(g_snapshots_buffer, true);
   ArraySetAsSeries(g_configured_pairs_buffer, true);
   ArraySetAsSeries(g_valid_pairs_buffer, true);
   ArraySetAsSeries(g_unavailable_pairs_buffer, true);
   ArraySetAsSeries(g_currency_count_buffer, true);
   ArraySetAsSeries(g_day_open_min_buffer, true);
   ArraySetAsSeries(g_day_open_max_buffer, true);
   ArraySetAsSeries(g_day_age_minutes_buffer, true);
   ArraySetAsSeries(g_early_day_buffer, true);
   ArraySetAsSeries(g_panel_objects_buffer, true);
   ArraySetAsSeries(g_grade_checksum_buffer, true);
   ArraySetAsSeries(g_valid_mask_buffer, true);
   ArraySetAsSeries(g_strength_usd_buffer, true);
   ArraySetAsSeries(g_strength_eur_buffer, true);
   ArraySetAsSeries(g_strength_gbp_buffer, true);
   ArraySetAsSeries(g_strength_jpy_buffer, true);
   ArraySetAsSeries(g_strength_chf_buffer, true);
   ArraySetAsSeries(g_strength_cad_buffer, true);
   ArraySetAsSeries(g_strength_aud_buffer, true);
   ArraySetAsSeries(g_config_usd_buffer, true);
   ArraySetAsSeries(g_config_eur_buffer, true);
   ArraySetAsSeries(g_config_gbp_buffer, true);
   ArraySetAsSeries(g_config_jpy_buffer, true);
   ArraySetAsSeries(g_config_chf_buffer, true);
   ArraySetAsSeries(g_config_cad_buffer, true);
   ArraySetAsSeries(g_config_aud_buffer, true);
   ArraySetAsSeries(g_valid_usd_buffer, true);
   ArraySetAsSeries(g_valid_eur_buffer, true);
   ArraySetAsSeries(g_valid_gbp_buffer, true);
   ArraySetAsSeries(g_valid_jpy_buffer, true);
   ArraySetAsSeries(g_valid_chf_buffer, true);
   ArraySetAsSeries(g_valid_cad_buffer, true);
   ArraySetAsSeries(g_valid_aud_buffer, true);
   ArraySetAsSeries(g_sensitivity_usd_buffer, true);
   ArraySetAsSeries(g_sensitivity_eur_buffer, true);
   ArraySetAsSeries(g_sensitivity_gbp_buffer, true);
   ArraySetAsSeries(g_sensitivity_jpy_buffer, true);
   ArraySetAsSeries(g_sensitivity_chf_buffer, true);
   ArraySetAsSeries(g_sensitivity_cad_buffer, true);
   ArraySetAsSeries(g_sensitivity_aud_buffer, true);
   return true;
}

bool BuffersReady()
{
   return ArraySize(g_ready_buffer) >= 1 &&
          ArraySize(g_snapshots_buffer) >= 1 &&
          ArraySize(g_configured_pairs_buffer) >= 1 &&
          ArraySize(g_valid_pairs_buffer) >= 1 &&
          ArraySize(g_unavailable_pairs_buffer) >= 1 &&
          ArraySize(g_currency_count_buffer) >= 1 &&
          ArraySize(g_day_open_min_buffer) >= 1 &&
          ArraySize(g_day_open_max_buffer) >= 1 &&
          ArraySize(g_day_age_minutes_buffer) >= 1 &&
          ArraySize(g_early_day_buffer) >= 1 &&
          ArraySize(g_panel_objects_buffer) >= 1 &&
          ArraySize(g_grade_checksum_buffer) >= 1 &&
          ArraySize(g_valid_mask_buffer) >= 1 &&
          ArraySize(g_strength_usd_buffer) >= 1 &&
          ArraySize(g_strength_eur_buffer) >= 1 &&
          ArraySize(g_strength_gbp_buffer) >= 1 &&
          ArraySize(g_strength_jpy_buffer) >= 1 &&
          ArraySize(g_strength_chf_buffer) >= 1 &&
          ArraySize(g_strength_cad_buffer) >= 1 &&
          ArraySize(g_strength_aud_buffer) >= 1 &&
          ArraySize(g_config_usd_buffer) >= 1 &&
          ArraySize(g_config_eur_buffer) >= 1 &&
          ArraySize(g_config_gbp_buffer) >= 1 &&
          ArraySize(g_config_jpy_buffer) >= 1 &&
          ArraySize(g_config_chf_buffer) >= 1 &&
          ArraySize(g_config_cad_buffer) >= 1 &&
          ArraySize(g_config_aud_buffer) >= 1 &&
          ArraySize(g_valid_usd_buffer) >= 1 &&
          ArraySize(g_valid_eur_buffer) >= 1 &&
          ArraySize(g_valid_gbp_buffer) >= 1 &&
          ArraySize(g_valid_jpy_buffer) >= 1 &&
          ArraySize(g_valid_chf_buffer) >= 1 &&
          ArraySize(g_valid_cad_buffer) >= 1 &&
          ArraySize(g_valid_aud_buffer) >= 1 &&
          ArraySize(g_sensitivity_usd_buffer) >= 1 &&
          ArraySize(g_sensitivity_eur_buffer) >= 1 &&
          ArraySize(g_sensitivity_gbp_buffer) >= 1 &&
          ArraySize(g_sensitivity_jpy_buffer) >= 1 &&
          ArraySize(g_sensitivity_chf_buffer) >= 1 &&
          ArraySize(g_sensitivity_cad_buffer) >= 1 &&
          ArraySize(g_sensitivity_aud_buffer) >= 1;
}

bool BuildPairList()
{
   ArrayResize(g_pair_symbols, 0);
   ArrayInitialize(g_config_coverage, 0);
   g_pair_count = 0;

   string requested = InpSymbols;
   StringTrimLeft(requested);
   StringTrimRight(requested);
   if(StringLen(requested) < 1)
   {
      Print("currency strength coverage auditor: InpSymbols is empty");
      return false;
   }

   string parts[];
   const int count = StringSplit(requested, (ushort)',', parts);
   for(int index = 0; index < count && g_pair_count < MAX_PAIRS; ++index)
   {
      string candidate = parts[index];
      StringTrimLeft(candidate);
      StringTrimRight(candidate);
      if(StringLen(candidate) < 1 || PairAlreadyAdded(candidate))
         continue;
      if(!AddPair(candidate))
         return false;
   }

   if(g_pair_count < 1)
   {
      Print("currency strength coverage auditor: no usable pair names");
      return false;
   }
   return true;
}

bool AddPair(const string symbol_name)
{
   int base_index = -1;
   int quote_index = -1;
   if(!DetectPairCurrencies(symbol_name, base_index, quote_index))
   {
      PrintFormat("currency strength coverage auditor: cannot identify currencies for %s",
                  symbol_name);
      return true;
   }

   for(int index = 0; index < g_pair_count; ++index)
      if((g_pair_base_index[index] == base_index &&
          g_pair_quote_index[index] == quote_index) ||
         (g_pair_base_index[index] == quote_index &&
          g_pair_quote_index[index] == base_index))
      {
         PrintFormat("currency strength coverage auditor: duplicate pair ignored %s",
                     symbol_name);
         return true;
      }

   const int next_size = g_pair_count + 1;
   if(ArrayResize(g_pair_symbols, next_size) != next_size ||
      ArrayResize(g_pair_base_index, next_size) != next_size ||
      ArrayResize(g_pair_quote_index, next_size) != next_size ||
      ArrayResize(g_pair_available, next_size) != next_size ||
      ArrayResize(g_pair_grade, next_size) != next_size ||
      ArrayResize(g_pair_ratio, next_size) != next_size ||
      ArrayResize(g_pair_day_open, next_size) != next_size)
      return false;

   g_pair_symbols[g_pair_count] = symbol_name;
   g_pair_base_index[g_pair_count] = base_index;
   g_pair_quote_index[g_pair_count] = quote_index;
   g_pair_available[g_pair_count] = false;
   g_pair_grade[g_pair_count] = -1;
   g_pair_ratio[g_pair_count] = EMPTY_VALUE;
   g_pair_day_open[g_pair_count] = 0;
   ++g_config_coverage[base_index];
   ++g_config_coverage[quote_index];
   ++g_pair_count;
   return true;
}

bool PairAlreadyAdded(const string symbol_name)
{
   for(int index = 0; index < g_pair_count; ++index)
      if(g_pair_symbols[index] == symbol_name)
         return true;
   return false;
}

bool DetectPairCurrencies(const string symbol_name,
                          int &base_index,
                          int &quote_index)
{
   base_index = -1;
   quote_index = -1;
   bool custom = false;
   if(SymbolExist(symbol_name, custom))
   {
      const string base = SymbolInfoString(symbol_name, SYMBOL_CURRENCY_BASE);
      const string quote = SymbolInfoString(symbol_name, SYMBOL_CURRENCY_PROFIT);
      base_index = CurrencyIndex(base);
      quote_index = CurrencyIndex(quote);
      if(base_index >= 0 && quote_index >= 0 && base_index != quote_index)
         return true;
   }

   string upper_name = symbol_name;
   StringToUpper(upper_name);
   int matches = 0;
   for(int base = 0; base < CURRENCY_COUNT; ++base)
      for(int quote = 0; quote < CURRENCY_COUNT; ++quote)
      {
         if(base == quote)
            continue;
         const string token = g_currency_names[base] + g_currency_names[quote];
         if(StringFind(upper_name, token) < 0)
            continue;
         base_index = base;
         quote_index = quote;
         ++matches;
      }
   return matches == 1;
}

int CurrencyIndex(const string currency)
{
   string upper = currency;
   StringToUpper(upper);
   for(int index = 0; index < CURRENCY_COUNT; ++index)
      if(upper == g_currency_names[index])
         return index;
   return -1;
}

void RefreshAudit()
{
   TakeSnapshot();
   if(InpShowPanel)
      DrawPanel();
   else
      DeletePanel();
   UpdateBuffers();
   ChartRedraw(0);
}

void TakeSnapshot()
{
   g_valid_pairs = 0;
   g_unavailable_pairs = 0;
   g_day_open_min = 0;
   g_day_open_max = 0;
   g_day_age_minutes = 0.0;
   g_early_day = false;
   g_grade_checksum = 0;
   g_valid_mask = 0;
   ArrayInitialize(g_valid_coverage, 0);
   for(int currency = 0; currency < CURRENCY_COUNT; ++currency)
   {
      g_strength[currency] = EMPTY_VALUE;
      g_sensitivity[currency] = EMPTY_VALUE;
      for(int item = 0; item < MAX_PAIRS; ++item)
         g_contributions[currency][item] = EMPTY_VALUE;
   }

   for(int pair = 0; pair < g_pair_count; ++pair)
   {
      g_pair_available[pair] = false;
      g_pair_grade[pair] = -1;
      g_pair_ratio[pair] = EMPTY_VALUE;
      g_pair_day_open[pair] = 0;
      if(!ReadPair(pair))
      {
         ++g_unavailable_pairs;
         continue;
      }

      g_pair_available[pair] = true;
      ++g_valid_pairs;
      g_valid_mask |= ((long)1 << pair);
      g_grade_checksum += (long)(pair + 1) * g_pair_grade[pair];
      const int base = g_pair_base_index[pair];
      const int quote = g_pair_quote_index[pair];
      g_contributions[base][g_valid_coverage[base]] =
         (double)g_pair_grade[pair];
      ++g_valid_coverage[base];
      g_contributions[quote][g_valid_coverage[quote]] =
         (double)(9 - g_pair_grade[pair]);
      ++g_valid_coverage[quote];

      const datetime open_time = g_pair_day_open[pair];
      if(g_day_open_min == 0 || open_time < g_day_open_min)
         g_day_open_min = open_time;
      if(open_time > g_day_open_max)
         g_day_open_max = open_time;
   }

   CalculateCurrencyResults();
   const datetime reference = TimeCurrent();
   if(reference > 0 && g_day_open_max > 0 && reference >= g_day_open_max)
      g_day_age_minutes = (double)(reference - g_day_open_max) / 60.0;
   g_early_day = g_day_open_max > 0 &&
                 g_day_age_minutes < (double)InpEarlyDayMinutes;
   ++g_snapshots;
}

bool ReadPair(const int pair_index)
{
   const string symbol_name = g_pair_symbols[pair_index];
   bool custom = false;
   if(!SymbolExist(symbol_name, custom))
      return false;

   ResetLastError();
   if(!(bool)SymbolInfoInteger(symbol_name, SYMBOL_SELECT))
      return false;

   MqlRates daily[1];
   ResetLastError();
   if(CopyRates(symbol_name, PERIOD_D1, 0, 1, daily) != 1)
      return false;

   MqlTick tick = {};
   ResetLastError();
   if(!SymbolInfoTick(symbol_name, tick) || tick.time_msc <= 0 ||
      tick.bid <= 0.0 || daily[0].time <= 0 ||
      daily[0].high <= daily[0].low)
      return false;

   double ratio = (tick.bid - daily[0].low) /
                  (daily[0].high - daily[0].low);
   ratio = MathMax(0.0, MathMin(1.0, ratio));
   g_pair_ratio[pair_index] = ratio;
   g_pair_grade[pair_index] = GradeFromRatio(ratio);
   g_pair_day_open[pair_index] = daily[0].time;
   return true;
}

int GradeFromRatio(const double ratio)
{
   const double percent = MathMax(0.0, MathMin(100.0, ratio * 100.0));
   if(percent < 3.0)
      return 0;
   if(percent < 10.0)
      return 1;
   if(percent < 25.0)
      return 2;
   if(percent < 40.0)
      return 3;
   if(percent < 50.0)
      return 4;
   if(percent < 60.0)
      return 5;
   if(percent < 75.0)
      return 6;
   if(percent < 90.0)
      return 7;
   if(percent < 97.0)
      return 8;
   return 9;
}

void CalculateCurrencyResults()
{
   for(int currency = 0; currency < CURRENCY_COUNT; ++currency)
   {
      const int count = g_valid_coverage[currency];
      if(count < 1)
         continue;
      double total = 0.0;
      for(int item = 0; item < count; ++item)
         total += g_contributions[currency][item];
      g_strength[currency] = total / (double)count;

      if(count < 2)
         continue;
      double maximum_delta = 0.0;
      for(int omitted = 0; omitted < count; ++omitted)
      {
         const double leave_one_out =
            (total - g_contributions[currency][omitted]) /
            (double)(count - 1);
         maximum_delta = MathMax(maximum_delta,
                                 MathAbs(g_strength[currency] -
                                         leave_one_out));
      }
      g_sensitivity[currency] = maximum_delta;
   }
}

void UpdateBuffers()
{
   if(!BuffersReady())
      return;
   const bool ready = g_pair_count > 0 && g_valid_pairs == g_pair_count;
   g_ready_buffer[0] = ready ? 1.0 : 0.0;
   g_snapshots_buffer[0] = (double)g_snapshots;
   g_configured_pairs_buffer[0] = (double)g_pair_count;
   g_valid_pairs_buffer[0] = (double)g_valid_pairs;
   g_unavailable_pairs_buffer[0] = (double)g_unavailable_pairs;
   g_currency_count_buffer[0] = (double)CURRENCY_COUNT;
   g_day_open_min_buffer[0] = (double)g_day_open_min;
   g_day_open_max_buffer[0] = (double)g_day_open_max;
   g_day_age_minutes_buffer[0] = g_day_age_minutes;
   g_early_day_buffer[0] = g_early_day ? 1.0 : 0.0;
   g_panel_objects_buffer[0] = (double)CountPanelObjects();
   g_grade_checksum_buffer[0] = (double)g_grade_checksum;
   g_valid_mask_buffer[0] = (double)g_valid_mask;

   g_strength_usd_buffer[0] = g_valid_coverage[0] > 0 ? g_strength[0] : -1.0;
   g_strength_eur_buffer[0] = g_valid_coverage[1] > 0 ? g_strength[1] : -1.0;
   g_strength_gbp_buffer[0] = g_valid_coverage[2] > 0 ? g_strength[2] : -1.0;
   g_strength_jpy_buffer[0] = g_valid_coverage[3] > 0 ? g_strength[3] : -1.0;
   g_strength_chf_buffer[0] = g_valid_coverage[4] > 0 ? g_strength[4] : -1.0;
   g_strength_cad_buffer[0] = g_valid_coverage[5] > 0 ? g_strength[5] : -1.0;
   g_strength_aud_buffer[0] = g_valid_coverage[6] > 0 ? g_strength[6] : -1.0;
   g_config_usd_buffer[0] = (double)g_config_coverage[0];
   g_config_eur_buffer[0] = (double)g_config_coverage[1];
   g_config_gbp_buffer[0] = (double)g_config_coverage[2];
   g_config_jpy_buffer[0] = (double)g_config_coverage[3];
   g_config_chf_buffer[0] = (double)g_config_coverage[4];
   g_config_cad_buffer[0] = (double)g_config_coverage[5];
   g_config_aud_buffer[0] = (double)g_config_coverage[6];
   g_valid_usd_buffer[0] = (double)g_valid_coverage[0];
   g_valid_eur_buffer[0] = (double)g_valid_coverage[1];
   g_valid_gbp_buffer[0] = (double)g_valid_coverage[2];
   g_valid_jpy_buffer[0] = (double)g_valid_coverage[3];
   g_valid_chf_buffer[0] = (double)g_valid_coverage[4];
   g_valid_cad_buffer[0] = (double)g_valid_coverage[5];
   g_valid_aud_buffer[0] = (double)g_valid_coverage[6];
   g_sensitivity_usd_buffer[0] = g_valid_coverage[0] > 1 ? g_sensitivity[0] : -1.0;
   g_sensitivity_eur_buffer[0] = g_valid_coverage[1] > 1 ? g_sensitivity[1] : -1.0;
   g_sensitivity_gbp_buffer[0] = g_valid_coverage[2] > 1 ? g_sensitivity[2] : -1.0;
   g_sensitivity_jpy_buffer[0] = g_valid_coverage[3] > 1 ? g_sensitivity[3] : -1.0;
   g_sensitivity_chf_buffer[0] = g_valid_coverage[4] > 1 ? g_sensitivity[4] : -1.0;
   g_sensitivity_cad_buffer[0] = g_valid_coverage[5] > 1 ? g_sensitivity[5] : -1.0;
   g_sensitivity_aud_buffer[0] = g_valid_coverage[6] > 1 ? g_sensitivity[6] : -1.0;
}

void DrawPanel()
{
   if(!EnsureBackground())
      return;

   SetPanelLine(0, "CURRENCY STRENGTH COVERAGE AUDITOR", clrLime, 14);
   SetPanelLine(1, "Legacy xMeter: 12-pair D1 range-location grades",
                clrWhite, 10);
   SetPanelLine(2,
                "PAIR GRADE: D1 position 0..9; quote contribution = 9-grade",
                clrWhite, 10);
   SetPanelLine(3,
                StringFormat("PAIRS: %d configured | %d valid | %d unavailable | checksum %I64d",
                             g_pair_count, g_valid_pairs, g_unavailable_pairs,
                             g_grade_checksum),
                g_unavailable_pairs == 0 ? clrLime : clrGold, 10);

   string day_open_text = "D1 OPEN: UNAVAILABLE";
   string day_age_text = "D1 AGE: UNAVAILABLE";
   if(g_day_open_max > 0)
   {
      day_open_text = "D1 OPEN: " +
                      TimeToString(g_day_open_max,
                                   TIME_DATE | TIME_MINUTES);
      if(g_day_open_min != g_day_open_max)
         day_open_text += " | OPEN-TIME MISMATCH";
      day_age_text = StringFormat("D1 AGE: %.1f min | EARLY FLAG: %s (<%d min)",
                                  g_day_age_minutes,
                                  g_early_day ? "ON" : "OFF",
                                  InpEarlyDayMinutes);
   }
   SetPanelLine(4, day_open_text, clrAqua, 10);
   SetPanelLine(5, day_age_text, g_early_day ? clrGold : clrAqua, 10);
   SetPanelLine(6,
                "CCY | SCORE | LIVE/SET | SET/6 | LOO DELTA | RISK",
                clrSilver, 10);

   for(int currency = 0; currency < CURRENCY_COUNT; ++currency)
   {
      const string row = StringFormat("%s | %s | %d/%d | %d/6 | %s | %s",
                                      g_currency_names[currency],
                                      StrengthText(currency),
                                      g_valid_coverage[currency],
                                      g_config_coverage[currency],
                                      g_config_coverage[currency],
                                      SensitivityText(currency),
                                      CoverageStatus(currency));
      SetPanelLine(7 + currency, row, CoverageColor(currency), 11);
   }

   SetPanelLine(14, "SET COVERAGE: USD 6 | EUR 5 | GBP 4 | JPY 3",
                clrWhite, 10);
   SetPanelLine(15, "SET COVERAGE: CHF 3 | CAD 1 | AUD 2 | maximum 6",
                clrWhite, 10);
   SetPanelLine(16,
                "CAD: 1 pair only -> a one-pair proxy, not broad strength.",
                clrTomato, 10);
   SetPanelLine(17,
                "PATH ORDER IS NOT MEASURED by high/low/current-bid grades.",
                clrGold, 10);
   SetPanelLine(18,
                "LOO DELTA: max score change when one live pair is removed.",
                clrSilver, 9);
   SetPanelLine(19,
                "READ ONLY | NO SYMBOLSELECT | NO ORDERS | NO FILE/DLL/WEB",
                clrLime, 10);
}

bool EnsureBackground()
{
   const string name = OBJECT_PREFIX + "BACKGROUND";
   if(ObjectFind(0, name) < 0 &&
      !ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0))
      return false;
   return ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER) &&
          ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 10) &&
          ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 12) &&
          ObjectSetInteger(0, name, OBJPROP_XSIZE, 1170) &&
          ObjectSetInteger(0, name, OBJPROP_YSIZE, 575) &&
          ObjectSetInteger(0, name, OBJPROP_BGCOLOR, C'5,18,10') &&
          ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, clrDarkGreen) &&
          ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false) &&
          ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
}

bool SetPanelLine(const int row,
                  const string text,
                  const color shade,
                  const int font_size)
{
   const string name = OBJECT_PREFIX + "LINE_" + IntegerToString(row);
   if(ObjectFind(0, name) < 0 &&
      !ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0))
      return false;
   return ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER) &&
          ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 27) &&
          ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 22 + row * 27) &&
          ObjectSetInteger(0, name, OBJPROP_COLOR, shade) &&
          ObjectSetInteger(0, name, OBJPROP_FONTSIZE, font_size) &&
          ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false) &&
          ObjectSetInteger(0, name, OBJPROP_HIDDEN, true) &&
          ObjectSetString(0, name, OBJPROP_FONT, "Consolas") &&
          ObjectSetString(0, name, OBJPROP_TEXT, text);
}

string StrengthText(const int currency_index)
{
   if(g_valid_coverage[currency_index] < 1 ||
      g_strength[currency_index] == EMPTY_VALUE)
      return " N/A ";
   return DoubleToString(g_strength[currency_index], 2);
}

string SensitivityText(const int currency_index)
{
   if(g_valid_coverage[currency_index] < 2 ||
      g_sensitivity[currency_index] == EMPTY_VALUE)
      return " N/A ";
   return DoubleToString(g_sensitivity[currency_index], 2);
}

string CoverageStatus(const int currency_index)
{
   if(g_config_coverage[currency_index] < 1)
      return "NO PAIR";
   if(g_valid_coverage[currency_index] < g_config_coverage[currency_index])
      return "DATA GAP";
   if(g_config_coverage[currency_index] == MAX_COUNTERPARTS)
      return "FULL";
   if(g_config_coverage[currency_index] >= 4)
      return "PARTIAL";
   if(g_config_coverage[currency_index] == 3)
      return "THIN";
   if(g_config_coverage[currency_index] == 2)
      return "FRAGILE";
   return "SINGLE-PAIR";
}

color CoverageColor(const int currency_index)
{
   if(g_config_coverage[currency_index] < 1 ||
      g_valid_coverage[currency_index] < g_config_coverage[currency_index])
      return clrGold;
   if(g_config_coverage[currency_index] == MAX_COUNTERPARTS)
      return clrLime;
   if(g_config_coverage[currency_index] >= 4)
      return clrGreenYellow;
   if(g_config_coverage[currency_index] == 3)
      return clrGold;
   if(g_config_coverage[currency_index] == 2)
      return clrOrange;
   return clrTomato;
}

int CountPanelObjects()
{
   int count = 0;
   const int total = ObjectsTotal(0, -1, -1);
   for(int index = 0; index < total; ++index)
   {
      const string name = ObjectName(0, index, -1, -1);
      if(StringFind(name, OBJECT_PREFIX) == 0)
         ++count;
   }
   return count;
}

void DeletePanel()
{
   ObjectsDeleteAll(0, OBJECT_PREFIX);
}
//+------------------------------------------------------------------+
