//+------------------------------------------------------------------+
//|                                  13_OnChart_RCI_v1_00.mq5        |
//|                                  Copyright 2026, FXおもしろラボ |
//|                                      https://fx-omoshiro-lab.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, FXおもしろラボ"
#property link      "https://fx-omoshiro-lab.com/mql-port/13-onchart-rci/"
#property version   "1.00"
#property description "Maps RCI around a moving average inside an ATR-scaled chart window."

#property indicator_chart_window
#property indicator_buffers 4
#property indicator_plots   4

#property indicator_label1  "Center MA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrSlateGray
#property indicator_style1  STYLE_DOT
#property indicator_width1  1

#property indicator_label2  "RCI Upper Level"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrDimGray
#property indicator_style2  STYLE_DASH
#property indicator_width2  1

#property indicator_label3  "RCI Lower Level"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrDimGray
#property indicator_style3  STYLE_DASH
#property indicator_width3  1

#property indicator_label4  "OnChart RCI"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrOrange
#property indicator_style4  STYLE_SOLID
#property indicator_width4  2

enum ENUM_RCI_TIE_MODE
  {
   RCI_TIES_AVERAGE=0,          // Equal prices share their average rank
   RCI_TIES_LEGACY_SEQUENTIAL=1 // Equal prices receive sequential ranks
  };

input group "==== RCI ===="
input int                  InpRCIPeriod          = 14;             // RCI period
input ENUM_TIMEFRAMES      InpRCITimeframe       = PERIOD_CURRENT; // RCI timeframe
input ENUM_RCI_TIE_MODE    InpTieMode            = RCI_TIES_AVERAGE; // Equal-price ranking

input group "==== Center MA ===="
input int                  InpMAPeriod           = 20;             // MA period
input ENUM_MA_METHOD       InpMAMethod           = MODE_EMA;       // MA method
input ENUM_APPLIED_PRICE   InpMAPrice            = PRICE_CLOSE;    // MA applied price

input group "==== ATR scale ===="
input ENUM_TIMEFRAMES      InpATRTimeframe       = PERIOD_D1;      // ATR timeframe
input int                  InpATRPeriod          = 20;             // ATR period
input double               InpATRMultiplier      = 0.10;           // RCI 100 = ATR x this value
input double               InpUpperLevel         = 70.0;           // Upper RCI guide
input double               InpLowerLevel         = -70.0;          // Lower RCI guide

input group "==== Calculation ===="
input bool                 InpUseClosedSourceBars = true;          // Use only completed RCI/ATR bars
input int                  InpMaxBars             = 1500;          // Maximum chart bars to draw

double BufferCenter[];
double BufferUpper[];
double BufferLower[];
double BufferRCI[];

int             g_maHandle  = INVALID_HANDLE;
int             g_atrHandle = INVALID_HANDLE;
ENUM_TIMEFRAMES g_rciTimeframe;
ENUM_TIMEFRAMES g_atrTimeframe;

//+------------------------------------------------------------------+
//| Use at least the chart timeframe, as in the original MQL4 tool.  |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES ResolveRCITimeframe()
  {
   ENUM_TIMEFRAMES requested=InpRCITimeframe;
   if(requested==PERIOD_CURRENT)
      requested=(ENUM_TIMEFRAMES)_Period;

   const int requested_seconds=PeriodSeconds(requested);
   const int chart_seconds=PeriodSeconds((ENUM_TIMEFRAMES)_Period);
   if(requested_seconds<=0 || requested_seconds<chart_seconds)
      return((ENUM_TIMEFRAMES)_Period);
   return(requested);
  }

//+------------------------------------------------------------------+
//| Keep the ATR scale above the RCI timeframe.                      |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES ResolveATRTimeframe(const ENUM_TIMEFRAMES rci_timeframe)
  {
   ENUM_TIMEFRAMES requested=InpATRTimeframe;
   if(requested==PERIOD_CURRENT)
      requested=(ENUM_TIMEFRAMES)_Period;

   const int requested_seconds=PeriodSeconds(requested);
   const int rci_seconds=PeriodSeconds(rci_timeframe);
   if(requested_seconds>rci_seconds)
      return(requested);
   if(rci_seconds<PeriodSeconds(PERIOD_D1))
      return(PERIOD_D1);
   if(rci_seconds<PeriodSeconds(PERIOD_W1))
      return(PERIOD_W1);
   return(PERIOD_MN1);
  }

//+------------------------------------------------------------------+
//| Map a chart bar to a source bar.                                 |
//+------------------------------------------------------------------+
int MappedShift(const ENUM_TIMEFRAMES timeframe,
                const datetime chart_open,
                const datetime chart_close)
  {
   const datetime reference_time=(InpUseClosedSourceBars ? chart_close : chart_open);
   ResetLastError();
   int shift=iBarShift(_Symbol,timeframe,reference_time,false);
   if(shift<0)
      return(-1);

   // At reference_time the containing source bar is still forming.  The
   // preceding bar is the newest one whose close is already known.
   if(InpUseClosedSourceBars)
      shift++;
   return(shift);
  }

//+------------------------------------------------------------------+
//| Original 00-RCI behavior: ties are ordered newest to oldest.     |
//+------------------------------------------------------------------+
double CalculateLegacyRCI(const double &series[],const int shift,const int period)
  {
   double prices[];
   double ranks[];
   bool   used[];
   if(ArrayResize(prices,period)!=period ||
      ArrayResize(ranks,period)!=period ||
      ArrayResize(used,period)!=period)
      return(EMPTY_VALUE);

   for(int i=0;i<period;i++)
     {
      prices[i]=series[shift+i];
      ranks[i]=0.0;
      used[i]=false;
     }

   for(int rank=0;rank<period;rank++)
     {
      int best=-1;
      for(int i=0;i<period;i++)
        {
         if(used[i])
            continue;
         if(best<0 || prices[i]>prices[best])
            best=i;
        }
      if(best<0)
         return(EMPTY_VALUE);
      ranks[best]=(double)rank;
      used[best]=true;
     }

   double sum_squared=0.0;
   for(int i=0;i<period;i++)
     {
      const double difference=(double)i-ranks[i];
      sum_squared+=difference*difference;
     }

   const double denominator=(double)period*((double)period*period-1.0);
   if(denominator<=0.0)
      return(EMPTY_VALUE);
   return(1.0-6.0*sum_squared/denominator);
  }

//+------------------------------------------------------------------+
//| Spearman correlation with average ranks for equal prices.       |
//+------------------------------------------------------------------+
double CalculateAverageTieRCI(const double &series[],const int shift,const int period)
  {
   double ranks[];
   if(ArrayResize(ranks,period)!=period)
      return(EMPTY_VALUE);

   for(int i=0;i<period;i++)
     {
      int higher=0;
      int equal=0;
      const double price=series[shift+i];
      for(int j=0;j<period;j++)
        {
         const double other=series[shift+j];
         if(other>price)
            higher++;
         else if(other==price)
            equal++;
        }
      ranks[i]=(double)higher+0.5*(double)(equal-1);
     }

   const double mean=0.5*(double)(period-1);
   double covariance=0.0;
   double time_variance=0.0;
   double price_variance=0.0;
   for(int i=0;i<period;i++)
     {
      const double time_delta=(double)i-mean;
      const double price_delta=ranks[i]-mean;
      covariance+=time_delta*price_delta;
      time_variance+=time_delta*time_delta;
      price_variance+=price_delta*price_delta;
     }

   const double denominator=MathSqrt(time_variance*price_variance);
   if(denominator<=0.0)
      return(0.0);
   return(covariance/denominator);
  }

//+------------------------------------------------------------------+
//| Calculate RCI from a preloaded series array.                     |
//+------------------------------------------------------------------+
double CalculateRCI(const double &series[],const int shift,const int period)
  {
   if(shift<0 || period<2 || shift+period>ArraySize(series))
      return(EMPTY_VALUE);
   if(InpTieMode==RCI_TIES_LEGACY_SEQUENTIAL)
      return(CalculateLegacyRCI(series,shift,period));
   return(CalculateAverageTieRCI(series,shift,period));
  }

//+------------------------------------------------------------------+
//| Custom indicator initialization.                                |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpRCIPeriod<2 || InpMAPeriod<1 || InpATRPeriod<1 ||
      InpATRMultiplier<=0.0 || InpMaxBars<100 ||
      InpLowerLevel<-100.0 || InpUpperLevel>100.0 ||
      InpLowerLevel>=InpUpperLevel)
     {
      Print("Invalid OnChart RCI input parameters.");
      return(INIT_PARAMETERS_INCORRECT);
     }

   SetIndexBuffer(0,BufferCenter,INDICATOR_DATA);
   SetIndexBuffer(1,BufferUpper,INDICATOR_DATA);
   SetIndexBuffer(2,BufferLower,INDICATOR_DATA);
   SetIndexBuffer(3,BufferRCI,INDICATOR_DATA);
   ArraySetAsSeries(BufferCenter,true);
   ArraySetAsSeries(BufferUpper,true);
   ArraySetAsSeries(BufferLower,true);
   ArraySetAsSeries(BufferRCI,true);

   for(int plot=0;plot<4;plot++)
     {
      PlotIndexSetDouble(plot,PLOT_EMPTY_VALUE,EMPTY_VALUE);
      PlotIndexSetInteger(plot,PLOT_DRAW_BEGIN,MathMax(InpRCIPeriod,MathMax(InpMAPeriod,InpATRPeriod)));
     }

   g_rciTimeframe=ResolveRCITimeframe();
   g_atrTimeframe=ResolveATRTimeframe(g_rciTimeframe);

   g_maHandle=iMA(_Symbol,g_rciTimeframe,InpMAPeriod,0,InpMAMethod,InpMAPrice);
   if(g_maHandle==INVALID_HANDLE)
     {
      PrintFormat("Failed to create MA handle. Error=%d",GetLastError());
      return(INIT_FAILED);
     }

   g_atrHandle=iATR(_Symbol,g_atrTimeframe,InpATRPeriod);
   if(g_atrHandle==INVALID_HANDLE)
     {
      PrintFormat("Failed to create ATR handle. Error=%d",GetLastError());
      IndicatorRelease(g_maHandle);
      g_maHandle=INVALID_HANDLE;
      return(INIT_FAILED);
     }

   const string tie_label=(InpTieMode==RCI_TIES_AVERAGE ? "AverageTies" : "LegacyTies");
   IndicatorSetString(INDICATOR_SHORTNAME,
                      StringFormat("OnChart RCI(%d,%s,%s)",
                                   InpRCIPeriod,EnumToString(g_rciTimeframe),tie_label));
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Release indicator handles.                                      |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(g_maHandle!=INVALID_HANDLE)
      IndicatorRelease(g_maHandle);
   if(g_atrHandle!=INVALID_HANDLE)
      IndicatorRelease(g_atrHandle);
  }

//+------------------------------------------------------------------+
//| Main calculation.                                                |
//+------------------------------------------------------------------+
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[])
  {
   const int minimum_bars=MathMax(InpRCIPeriod,MathMax(InpMAPeriod,InpATRPeriod))+2;
   if(rates_total<minimum_bars)
      return(0);

   ArraySetAsSeries(time,true);
   if(prev_calculated==0)
     {
      ArrayInitialize(BufferCenter,EMPTY_VALUE);
      ArrayInitialize(BufferUpper,EMPTY_VALUE);
      ArrayInitialize(BufferLower,EMPTY_VALUE);
      ArrayInitialize(BufferRCI,EMPTY_VALUE);
     }

   const int chart_seconds=PeriodSeconds((ENUM_TIMEFRAMES)_Period);
   if(chart_seconds<=0)
      return(0);

   const int limit=MathMin(rates_total-1,InpMaxBars-1);
   int rci_shifts[];
   int atr_shifts[];
   if(ArrayResize(rci_shifts,limit+1)!=limit+1 ||
      ArrayResize(atr_shifts,limit+1)!=limit+1)
      return(0);

   int max_rci_shift=-1;
   int max_atr_shift=-1;
   const datetime now=TimeCurrent();
   for(int i=0;i<=limit;i++)
     {
      datetime chart_close=time[i]+chart_seconds;
      if(chart_close>now)
         chart_close=now;

      rci_shifts[i]=MappedShift(g_rciTimeframe,time[i],chart_close);
      atr_shifts[i]=MappedShift(g_atrTimeframe,time[i],chart_close);
      if(rci_shifts[i]<0 || atr_shifts[i]<0)
         return(0);
      max_rci_shift=MathMax(max_rci_shift,rci_shifts[i]);
      max_atr_shift=MathMax(max_atr_shift,atr_shifts[i]);
     }

   const int available_rci_bars=Bars(_Symbol,g_rciTimeframe);
   const int available_atr_bars=BarsCalculated(g_atrHandle);
   const int available_ma_bars=BarsCalculated(g_maHandle);
   const int close_count=MathMin(available_rci_bars,max_rci_shift+InpRCIPeriod);
   const int ma_count=MathMin(available_ma_bars,max_rci_shift+1);
   const int atr_count=MathMin(available_atr_bars,max_atr_shift+1);
   if(close_count<InpRCIPeriod || ma_count<=0 || atr_count<=0)
      return(0);

   double source_close[];
   double ma_values[];
   double atr_values[];
   ArraySetAsSeries(source_close,true);
   ArraySetAsSeries(ma_values,true);
   ArraySetAsSeries(atr_values,true);

   ResetLastError();
   if(CopyClose(_Symbol,g_rciTimeframe,0,close_count,source_close)!=close_count)
      return(0);
   if(CopyBuffer(g_maHandle,0,0,ma_count,ma_values)!=ma_count)
      return(0);
   if(CopyBuffer(g_atrHandle,0,0,atr_count,atr_values)!=atr_count)
      return(0);

   for(int i=0;i<=limit;i++)
     {
      const int rci_shift=rci_shifts[i];
      const int atr_shift=atr_shifts[i];
      if(rci_shift>=ma_count || rci_shift+InpRCIPeriod>close_count || atr_shift>=atr_count)
        {
         BufferCenter[i]=EMPTY_VALUE;
         BufferUpper[i]=EMPTY_VALUE;
         BufferLower[i]=EMPTY_VALUE;
         BufferRCI[i]=EMPTY_VALUE;
         continue;
        }

      const double center=ma_values[rci_shift];
      const double atr=atr_values[atr_shift];
      const double rci=CalculateRCI(source_close,rci_shift,InpRCIPeriod);
      if(center==EMPTY_VALUE || atr==EMPTY_VALUE || rci==EMPTY_VALUE ||
         !MathIsValidNumber(center) || !MathIsValidNumber(atr) ||
         !MathIsValidNumber(rci) || atr<=0.0)
        {
         BufferCenter[i]=EMPTY_VALUE;
         BufferUpper[i]=EMPTY_VALUE;
         BufferLower[i]=EMPTY_VALUE;
         BufferRCI[i]=EMPTY_VALUE;
         continue;
        }

      const double unit=atr*InpATRMultiplier;
      BufferCenter[i]=center;
      BufferUpper[i]=center+unit*(InpUpperLevel/100.0);
      BufferLower[i]=center+unit*(InpLowerLevel/100.0);
      BufferRCI[i]=center+unit*MathMax(-1.0,MathMin(1.0,rci));
     }

   return(rates_total);
  }
//+------------------------------------------------------------------+
