//+------------------------------------------------------------------+
//|                 NRP_Trendforce_Histogram_TrueNRP.mq5             |
//|     MQL5 true non-repaint Leledc SSRC Force histogram             |
//|     Single-buffer DRAW_COLOR_HISTOGRAM version for MT5/offline    |
//+------------------------------------------------------------------+
#property strict
#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   1

#property indicator_label1  "NRP Trendforce"
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrDodgerBlue, clrSlateBlue, clrSilver
#property indicator_width1  2

#property indicator_maximum  1.1
#property indicator_minimum -1.1
#property indicator_level1   0.95
#property indicator_level2  -0.95
#property indicator_level3   0.0

input int    SnakeRange     = 60;
input int    FilterPeriod   = 777;
input double MartFiltr      = 777.0;
input int    PriceConst     = 1;
input double LevelsCross    = 0.95;
input int    Countbars      = 3000;
input int    HistogramWidth = 2;
input bool   ShowCurrentBar = false;
input bool   RecalculateAllBars = true; // safer for offline/Renko charts

//---- plot buffers
double Hist[];
double HistColor[];

//---- hidden calculation buffers
double SRCBuffer[];
double Axis[];
double Mart[];

//---- Spearman Rank Correlation
int    rangeN = 14;
double R2[];
double multiply = 1.0;
int    PriceInt[];
int    SortInt[];

//+------------------------------------------------------------------+
int OnInit()
{
   SetIndexBuffer(0, Hist,       INDICATOR_DATA);
   SetIndexBuffer(1, HistColor,  INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2, SRCBuffer,  INDICATOR_CALCULATIONS);
   SetIndexBuffer(3, Axis,       INDICATOR_CALCULATIONS);
   SetIndexBuffer(4, Mart,       INDICATOR_CALCULATIONS);

   ArraySetAsSeries(Hist,      true);
   ArraySetAsSeries(HistColor, true);
   ArraySetAsSeries(SRCBuffer, true);
   ArraySetAsSeries(Axis,      true);
   ArraySetAsSeries(Mart,      true);

   PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_COLOR_HISTOGRAM);
   PlotIndexSetInteger(0, PLOT_LINE_WIDTH, HistogramWidth);
   PlotIndexSetInteger(0, PLOT_COLOR_INDEXES, 3);
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, clrDodgerBlue);
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, 1, clrSlateBlue);
   PlotIndexSetInteger(0, PLOT_LINE_COLOR, 2, clrSilver);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);

   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, LevelsCross);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, -LevelsCross);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 2, 0.0);

   ArrayResize(R2,       rangeN);
   ArrayResize(PriceInt, rangeN);
   ArrayResize(SortInt,  rangeN);

   multiply = MathPow(10.0, (double)_Digits);
   IndicatorSetString(INDICATOR_SHORTNAME,
                      "NRP_Trendforce_Histogram TRUE NRP MT5 (SR:" +
                      IntegerToString(SnakeRange) + ", FP:" +
                      IntegerToString(FilterPeriod) + ")");

   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
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(open,  true);
   ArraySetAsSeries(high,  true);
   ArraySetAsSeries(low,   true);
   ArraySetAsSeries(close, true);

   int safety = MathMax(FilterPeriod, SnakeRange + rangeN + 5) + SnakeRange + rangeN + 10;
   int maxLimit = rates_total - safety;
   if(maxLimit < 1)
      return(0);

   int fullLimit = MathMin(Countbars - 1, maxLimit);
   int limit;
   if(RecalculateAllBars || prev_calculated == 0 || prev_calculated > rates_total)
      limit = fullLimit;
   else
      limit = MathMin(rates_total - prev_calculated + 2, fullLimit);

   int drawBegin = rates_total - MathMin(Countbars, rates_total);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, drawBegin);

   // Clear the whole displayed range, not only the newly-calculated section.
   // This avoids stale/gray bars on MT5 offline/Renko charts.
   for(int i = 0; i <= fullLimit; i++)
   {
      Hist[i] = EMPTY_VALUE;
      HistColor[i] = 2;
   }

   int calcLimit = MathMin(fullLimit + rangeN + SnakeRange + 2, maxLimit);

   // Same non-repaint calculation direction as the working MQL4 NRP version.
   for(int ma = 0; ma <= calcLimit; ma++)
      MartAxis(ma, rates_total, open, high, low, close);

   for(int sm = 0; sm <= calcLimit; sm++)
      SmoothOverMart(sm, rates_total);

   for(int p = fullLimit; p >= 0; p--)
   {
      if(p + rangeN >= rates_total)
         continue;

      for(int k = 0; k < rangeN; k++)
         PriceInt[k] = (int)MathRound(Mart[p + k] * multiply);

      RankPrices(PriceInt);
      SRCBuffer[p] = SpearmanRankCorrelation(R2, rangeN);

      if(SRCBuffer[p] >  1.0) SRCBuffer[p] =  1.0;
      if(SRCBuffer[p] < -1.0) SRCBuffer[p] = -1.0;

      if(p == 0 && !ShowCurrentBar)
         continue;

      Hist[p] = SRCBuffer[p];

      if(SRCBuffer[p] > 0.0)
         HistColor[p] = 0;   // blue positive
      else if(SRCBuffer[p] < 0.0)
         HistColor[p] = 1;   // purple negative
      else
         HistColor[p] = 2;   // silver exact zero only
   }

   return(rates_total);
}
//+------------------------------------------------------------------+
void MartAxis(const int Pos,
              const int rates_total,
              const double &open[],
              const double &high[],
              const double &low[],
              const double &close[])
{
   if(Pos < 0 || Pos >= rates_total)
      return;

   Axis[Pos] = LWMA_Price(Pos, SnakeRange + 1, rates_total, open, high, low, close);

   int SnakeWeight, i, w, ww, Shift;
   double SnakeSum;

   for(Shift = Pos + SnakeRange + 2; Shift > Pos; Shift--)
   {
      if(Shift >= rates_total)
         continue;

      SnakeSum = 0.0;
      SnakeWeight = 0;
      i = 0;
      w = Shift + SnakeRange;
      ww = Shift - SnakeRange;
      if(ww < Pos)
         ww = Pos;

      while(w >= Shift)
      {
         if(w < rates_total)
         {
            i++;
            SnakeSum += i * SnakePrice(w, rates_total, open, high, low, close);
            SnakeWeight += i;
         }
         w--;
      }

      while(w >= ww)
      {
         if(w < rates_total)
         {
            i--;
            SnakeSum += i * SnakePrice(w, rates_total, open, high, low, close);
            SnakeWeight += i;
         }
         w--;
      }

      if(SnakeWeight != 0)
         Axis[Shift] = SnakeSum / SnakeWeight;
   }
}
//+------------------------------------------------------------------+
double LWMA_Price(const int shift,
                  const int period,
                  const int rates_total,
                  const double &open[],
                  const double &high[],
                  const double &low[],
                  const double &close[])
{
   if(shift < 0 || shift >= rates_total || period <= 0)
      return(0.0);

   double sum = 0.0;
   double weightSum = 0.0;

   for(int j = 0; j < period; j++)
   {
      int idx = shift + j;
      if(idx >= rates_total)
         break;

      int weight = period - j;
      sum += weight * SnakePrice(idx, rates_total, open, high, low, close);
      weightSum += weight;
   }

   if(weightSum == 0.0)
      return(0.0);

   return(sum / weightSum);
}
//+------------------------------------------------------------------+
double SnakePrice(const int Shift,
                  const int rates_total,
                  const double &open[],
                  const double &high[],
                  const double &low[],
                  const double &close[])
{
   if(Shift < 0 || Shift >= rates_total)
      return(0.0);

   switch(PriceConst)
   {
      case 0: return(close[Shift]);
      case 1: return(open[Shift]);
      case 2: return(high[Shift]);
      case 3: return(low[Shift]);
      case 4: return((high[Shift] + low[Shift]) / 2.0);
      case 5: return((close[Shift] + high[Shift] + low[Shift]) / 3.0);
      case 6: return((2.0 * close[Shift] + high[Shift] + low[Shift]) / 4.0);
      default:return(close[Shift]);
   }
}
//+------------------------------------------------------------------+
void SmoothOverMart(const int Shift, const int rates_total)
{
   if(Shift < 0 || Shift >= rates_total)
      return;

   int maxIdx = AxisMaximumIndex(Shift, FilterPeriod, rates_total);
   int minIdx = AxisMinimumIndex(Shift, FilterPeriod, rates_total);
   if(maxIdx < 0 || minIdx < 0)
      return;

   double t = Axis[maxIdx];
   double b = Axis[minIdx];

   Mart[Shift] = (2.0 * (2.0 + MartFiltr) * Axis[Shift] - (t + b)) / 2.0 / (1.0 + MartFiltr);
}
//+------------------------------------------------------------------+
int AxisMaximumIndex(const int start, const int count, const int rates_total)
{
   if(start < 0 || start >= rates_total || count <= 0)
      return(-1);

   int end = MathMin(start + count, rates_total);
   int maxIdx = start;
   double maxVal = Axis[start];

   for(int i = start + 1; i < end; i++)
   {
      if(Axis[i] > maxVal)
      {
         maxVal = Axis[i];
         maxIdx = i;
      }
   }
   return(maxIdx);
}
//+------------------------------------------------------------------+
int AxisMinimumIndex(const int start, const int count, const int rates_total)
{
   if(start < 0 || start >= rates_total || count <= 0)
      return(-1);

   int end = MathMin(start + count, rates_total);
   int minIdx = start;
   double minVal = Axis[start];

   for(int i = start + 1; i < end; i++)
   {
      if(Axis[i] < minVal)
      {
         minVal = Axis[i];
         minIdx = i;
      }
   }
   return(minIdx);
}
//+------------------------------------------------------------------+
double SpearmanRankCorrelation(double &Ranks[], const int N)
{
   double z2 = 0.0;
   for(int i = 0; i < N; i++)
      z2 += MathPow(Ranks[i] - i - 1, 2.0);

   return(1.0 - 6.0 * z2 / (MathPow((double)N, 3.0) - N));
}
//+------------------------------------------------------------------+
void RankPrices(int &InitialArray[])
{
   int i, k, m, dublicat, counter, etalon;
   double dcounter, averageRank;
   double TrueRanks[];

   ArrayResize(TrueRanks, rangeN);
   ArrayCopy(SortInt, InitialArray);

   for(i = 0; i < rangeN; i++)
      TrueRanks[i] = i + 1;

   ArraySort(SortInt);
   ReverseIntArray(SortInt, rangeN); // MQL4 used MODE_DESCEND

   for(i = 0; i < rangeN - 1; i++)
   {
      if(SortInt[i] != SortInt[i + 1])
         continue;

      dublicat = SortInt[i];
      k = i + 1;
      counter = 1;
      averageRank = i + 1;

      while(k < rangeN)
      {
         if(SortInt[k] == dublicat)
         {
            counter++;
            averageRank += k + 1;
            k++;
         }
         else
            break;
      }

      dcounter = counter;
      averageRank = averageRank / dcounter;

      for(m = i; m < k; m++)
         TrueRanks[m] = averageRank;

      i = k;
   }

   for(i = 0; i < rangeN; i++)
   {
      etalon = InitialArray[i];
      k = 0;
      while(k < rangeN)
      {
         if(etalon == SortInt[k])
         {
            R2[i] = TrueRanks[k];
            break;
         }
         k++;
      }
   }
}
//+------------------------------------------------------------------+
void ReverseIntArray(int &arr[], const int count)
{
   for(int i = 0; i < count / 2; i++)
   {
      int j = count - 1 - i;
      int tmp = arr[i];
      arr[i] = arr[j];
      arr[j] = tmp;
   }
}
//+------------------------------------------------------------------+
