//+------------------------------------------------------------------+
//|                                         FXP_CurrencyStrength.mq5 |
//|                                                    FXPIPS ONLINE |
//+------------------------------------------------------------------+
#property copyright "FXPIPS ONLINE"
#property link      "https://fxpips.online/mt5-indicators/currency-strength-meter/"
#property version   "1.00"
#property description "Relative strength of the 8 major currencies from the 28 major and"
#property description "cross pairs: average % change over N bars. Handles broker suffixes."
/*
   License: MIT. Copyright (c) FXPIPS ONLINE.
   Educational tool - not financial advice. Trading involves substantial risk of loss.
*/
#property indicator_separate_window
#property indicator_buffers 8
#property indicator_plots   8
#property indicator_level1  0.0
#property indicator_levelcolor clrDimGray
#property indicator_levelstyle STYLE_DOT
#property indicator_label1  "EUR"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrDodgerBlue
#property indicator_width1  2
#property indicator_label2  "GBP"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrMediumOrchid
#property indicator_width2  2
#property indicator_label3  "AUD"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrGold
#property indicator_width3  2
#property indicator_label4  "NZD"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrTurquoise
#property indicator_width4  2
#property indicator_label5  "USD"
#property indicator_type5   DRAW_LINE
#property indicator_color5  clrLimeGreen
#property indicator_width5  2
#property indicator_label6  "CAD"
#property indicator_type6   DRAW_LINE
#property indicator_color6  clrTomato
#property indicator_width6  2
#property indicator_label7  "CHF"
#property indicator_type7   DRAW_LINE
#property indicator_color7  clrSilver
#property indicator_width7  2
#property indicator_label8  "JPY"
#property indicator_type8   DRAW_LINE
#property indicator_color8  clrDarkOrange
#property indicator_width8  2

input group "Calculation"
input int    InpLookback   = 20;        // Bars for the % change
input int    InpMaxBars    = 500;       // History bars to calculate
input group "Symbols"
input string InpPrefix     = "";        // Symbol prefix (blank = auto-detect)
input string InpSuffix     = "";        // Symbol suffix (blank = auto-detect)
input group "Panel"
input bool   InpShowPanel  = true;      // Show ranking panel
input int    InpFontSize   = 9;         // Panel font size
input int    InpRefreshSec = 2;         // Refresh interval without ticks (seconds)
input group "Alerts"
input bool   InpAlerts     = false;     // Alert when strongest/weakest pair changes (once per bar)

const string PREFIX = "FXP_CSM_";
#define NCUR 8
#define NPAIR 28

string   g_cur[NCUR] = {"EUR", "GBP", "AUD", "NZD", "USD", "CAD", "CHF", "JPY"};
color    g_col[NCUR] = {clrDodgerBlue, clrMediumOrchid, clrGold, clrTurquoise, clrLimeGreen, clrTomato, clrSilver, clrDarkOrange};
string   g_sym[NPAIR];
int      g_base[NPAIR], g_quote[NPAIR];
bool     g_have[NPAIR];
int      g_found = 0;

double   B0[], B1[], B2[], B3[], B4[], B5[], B6[], B7[];
int      g_rates = 0;
datetime g_lastTime = 0;
bool     g_needFull = false;
int      g_retries = 0;
string   g_lastPair = "";
datetime g_lastAlert = 0;

//+------------------------------------------------------------------+
void SetVal(int c, int i, double v)
  {
   switch(c)
     {
      case 0: B0[i] = v; break;
      case 1: B1[i] = v; break;
      case 2: B2[i] = v; break;
      case 3: B3[i] = v; break;
      case 4: B4[i] = v; break;
      case 5: B5[i] = v; break;
      case 6: B6[i] = v; break;
      default: B7[i] = v; break;
     }
  }

double GetVal(int c, int i)
  {
   switch(c)
     {
      case 0: return(B0[i]);
      case 1: return(B1[i]);
      case 2: return(B2[i]);
      case 3: return(B3[i]);
      case 4: return(B4[i]);
      case 5: return(B5[i]);
      case 6: return(B6[i]);
     }
   return(B7[i]);
  }

//+------------------------------------------------------------------+
bool TrySymbol(string name)
  {
   bool custom = false;
   if(name == "" || !SymbolExist(name, custom))
      return(false);
   return(SymbolSelect(name, true));
  }

//+------------------------------------------------------------------+
string ResolveSymbol(string pair, string pre, string suf)
  {
   if(TrySymbol(pre + pair + suf))
      return(pre + pair + suf);
   if(TrySymbol(pair))
      return(pair);
   // fallback: shortest symbol whose name contains the pair
   string best = "";
   int total = SymbolsTotal(false);
   for(int i = 0; i < total; i++)
     {
      string s = SymbolName(i, false);
      if(StringFind(s, pair) < 0 || StringLen(s) > 14)
         continue;
      if(best == "" || StringLen(s) < StringLen(best))
         best = s;
     }
   if(best != "" && TrySymbol(best))
      return(best);
   return("");
  }

//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpLookback < 1)
      return(INIT_PARAMETERS_INCORRECT);
   SetIndexBuffer(0, B0, INDICATOR_DATA);
   SetIndexBuffer(1, B1, INDICATOR_DATA);
   SetIndexBuffer(2, B2, INDICATOR_DATA);
   SetIndexBuffer(3, B3, INDICATOR_DATA);
   SetIndexBuffer(4, B4, INDICATOR_DATA);
   SetIndexBuffer(5, B5, INDICATOR_DATA);
   SetIndexBuffer(6, B6, INDICATOR_DATA);
   SetIndexBuffer(7, B7, INDICATOR_DATA);
   ArraySetAsSeries(B0, false);
   ArraySetAsSeries(B1, false);
   ArraySetAsSeries(B2, false);
   ArraySetAsSeries(B3, false);
   ArraySetAsSeries(B4, false);
   ArraySetAsSeries(B5, false);
   ArraySetAsSeries(B6, false);
   ArraySetAsSeries(B7, false);
   for(int p = 0; p < NCUR; p++)
      PlotIndexSetDouble(p, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   IndicatorSetInteger(INDICATOR_DIGITS, 3);

   // detect prefix/suffix from the chart symbol when not given
   string pre = InpPrefix, suf = InpSuffix;
   if(pre == "" && suf == "")
     {
      for(int a = 0; a < NCUR && suf == "" && pre == ""; a++)
         for(int b = 0; b < NCUR; b++)
           {
            if(a == b)
               continue;
            int pos = StringFind(_Symbol, g_cur[a] + g_cur[b]);
            if(pos >= 0)
              {
               pre = StringSubstr(_Symbol, 0, pos);
               suf = StringSubstr(_Symbol, pos + 6);
               break;
              }
           }
     }

   int k = 0;
   g_found = 0;
   for(int a = 0; a < NCUR; a++)
      for(int b = a + 1; b < NCUR; b++)
        {
         g_base[k] = a;
         g_quote[k] = b;
         g_sym[k] = ResolveSymbol(g_cur[a] + g_cur[b], pre, suf);
         g_have[k] = (g_sym[k] != "");
         if(g_have[k])
            g_found++;
         k++;
        }
   if(g_found == 0)
     {
      Print("FXP Currency Strength: none of the 28 pairs were found on this broker");
      return(INIT_FAILED);
     }
   if(g_found < NPAIR)
      for(int i = 0; i < NPAIR; i++)
         if(!g_have[i])
            Print("FXP Currency Strength: pair not available, skipped: ", g_cur[g_base[i]], g_cur[g_quote[i]]);

   IndicatorSetString(INDICATOR_SHORTNAME, StringFormat("FXP Currency Strength (%d bars, %d/%d pairs)", InpLookback, g_found, NPAIR));
   EventSetTimer(MathMax(InpRefreshSec, 1));
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
   ObjectsDeleteAll(0, PREFIX);
   ChartRedraw();
  }

//+------------------------------------------------------------------+
void CalcBar(int i, datetime t)
  {
   double sum[NCUR], cnt[NCUR];
   ArrayInitialize(sum, 0.0);
   ArrayInitialize(cnt, 0.0);
   for(int p = 0; p < NPAIR; p++)
     {
      if(!g_have[p])
         continue;
      int sh = iBarShift(g_sym[p], _Period, t, false);
      if(sh < 0)
         continue;
      double now = iClose(g_sym[p], _Period, sh);
      double past = iClose(g_sym[p], _Period, sh + InpLookback);
      if(now <= 0.0 || past <= 0.0)
         continue;
      double pct = (now - past) / past * 100.0;
      sum[g_base[p]] += pct;
      cnt[g_base[p]] += 1.0;
      sum[g_quote[p]] -= pct;
      cnt[g_quote[p]] += 1.0;
     }
   for(int c = 0; c < NCUR; c++)
      SetVal(c, i, cnt[c] > 0.0 ? sum[c] / cnt[c] : EMPTY_VALUE);
  }

//+------------------------------------------------------------------+
void DrawPanel()
  {
   if(!InpShowPanel || g_rates < 1)
      return;
   int win = ChartWindowFind();
   if(win < 0)
      return;
   int last = g_rates - 1;
   int order[NCUR];
   double val[NCUR];
   for(int c = 0; c < NCUR; c++)
     {
      order[c] = c;
      val[c] = GetVal(c, last);
     }
   for(int a = 0; a < NCUR - 1; a++)
      for(int b = a + 1; b < NCUR; b++)
        {
         double va = (val[order[a]] == EMPTY_VALUE) ? -1e9 : val[order[a]];
         double vb = (val[order[b]] == EMPTY_VALUE) ? -1e9 : val[order[b]];
         if(vb > va)
           {
            int tmp = order[a];
            order[a] = order[b];
            order[b] = tmp;
           }
        }
   int x = 8;
   int step = InpFontSize * 10;
   for(int r = 0; r < NCUR; r++)
     {
      int c = order[r];
      string n = PREFIX + "R" + IntegerToString(r);
      if(ObjectFind(0, n) < 0)
        {
         ObjectCreate(0, n, OBJ_LABEL, win, 0, 0);
         ObjectSetInteger(0, n, OBJPROP_CORNER, CORNER_LEFT_UPPER);
         ObjectSetInteger(0, n, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
         ObjectSetInteger(0, n, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(0, n, OBJPROP_HIDDEN, true);
         ObjectSetString(0, n, OBJPROP_FONT, "Consolas");
         ObjectSetInteger(0, n, OBJPROP_FONTSIZE, InpFontSize);
        }
      ObjectSetInteger(0, n, OBJPROP_XDISTANCE, x + r * step);
      ObjectSetInteger(0, n, OBJPROP_YDISTANCE, InpFontSize * 2 + 6);
      string txt = (val[c] == EMPTY_VALUE) ? g_cur[c] + " n/a" : StringFormat("%s %+.2f", g_cur[c], val[c]);
      ObjectSetString(0, n, OBJPROP_TEXT, IntegerToString(r + 1) + "." + txt);
      ObjectSetInteger(0, n, OBJPROP_COLOR, g_col[c]);
     }
   // strongest vs weakest alert
   if(val[order[0]] != EMPTY_VALUE && val[order[NCUR - 1]] != EMPTY_VALUE)
     {
      string pair = g_cur[order[0]] + "/" + g_cur[order[NCUR - 1]];
      if(pair != g_lastPair && g_lastPair != "" && InpAlerts && g_lastTime != g_lastAlert)
        {
         g_lastAlert = g_lastTime;
         Alert(StringFormat("FXP Currency Strength %s: strongest/weakest is now %s", EnumToString(_Period), pair));
        }
      g_lastPair = pair;
     }
  }

//+------------------------------------------------------------------+
bool AllSynced()
  {
   for(int p = 0; p < NPAIR; p++)
      if(g_have[p] && !SeriesInfoInteger(g_sym[p], _Period, SERIES_SYNCHRONIZED))
         return(false);
   return(true);
  }

//+------------------------------------------------------------------+
void OnTimer()
  {
   if(g_needFull)
     {
      if(AllSynced() || ++g_retries > 15)
        {
         g_needFull = false;
         ChartSetSymbolPeriod(0, _Symbol, _Period);   // forces a full recalculation
        }
      return;
     }
   if(g_rates > 0 && g_lastTime > 0)
     {
      CalcBar(g_rates - 1, g_lastTime);
      DrawPanel();
      ChartRedraw();
     }
  }

//+------------------------------------------------------------------+
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[])
  {
   ArraySetAsSeries(time, false);
   if(rates_total < 2)
      return(0);
   int start;
   if(prev_calculated == 0)
     {
      for(int c = 0; c < NCUR; c++)
         for(int i = 0; i < rates_total; i++)
            SetVal(c, i, EMPTY_VALUE);
      start = MathMax(0, rates_total - InpMaxBars);
      if(!AllSynced())
         g_needFull = true;
     }
   else
      start = prev_calculated - 1;

   for(int i = start; i < rates_total; i++)
      CalcBar(i, time[i]);
   g_rates = rates_total;
   g_lastTime = time[rates_total - 1];
   DrawPanel();
   return(rates_total);
  }
//+------------------------------------------------------------------+
