//+------------------------------------------------------------------+
//|                                    Relative_Trend_Index_v2.0.mq4 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "2.0"
#property strict
#property indicator_separate_window

#property indicator_buffers 4
#property indicator_plots 4

#property indicator_minimum -10
#property indicator_maximum 110

#property indicator_color1 clrWhite
#property indicator_label1 "Relative Trend Index"
#property indicator_style1 STYLE_SOLID
#property indicator_width1 1

#property indicator_color2 clrYellow
#property indicator_label2 "MA Relative Trend Index"
#property indicator_style2 STYLE_SOLID
#property indicator_width2 1

#property indicator_color3 clrOrangeRed
#property indicator_label3 "Overbought"
#property indicator_style3 STYLE_SOLID
#property indicator_width3 5

#property indicator_color4 clrDodgerBlue
#property indicator_label4 "Oversold"
#property indicator_style4 STYLE_SOLID
#property indicator_width4 5

#property indicator_level1 80.0
#property indicator_level2 50.0
#property indicator_level3 20.0
#property indicator_levelcolor clrSilver
#property indicator_levelstyle STYLE_DOT
#property indicator_levelwidth 1

enum source {
            s1,         //Close
            s2,         //Open
            s3,         //High
            s4,         //Low
            s5,         //HL2
            s6,         //HLC3
            s7,         //HLCC4
            };

input string IndiPrefix="111";                              // Indicator Prefix
input source Price = s1;                                    // Select Price
input int trend_data_count=240;                             // Trend Length
input int trend_sensitivity_percentage= 98;                 // Sensitivity
input int signal_length=60;                                 // Signal Length
input int std_dev_length = 2;  // Dev.Standard Length (2 default)
input double ob  = 80;                                      // Overbought
input double os  = 20;                                      // Oversold
input int MaxBars = 1000;                                   // Maximum Bars to Process

input bool RTIcrossOB = false;                                     // RTI cross OB
input bool RTIcrossOS = false;                                     // RTI cross OB
input bool RTIcrossMA = true;                                     // RTI cross MA

input bool Alerts = false;                                        // PopUp Alert
input bool Phone = false;                                         // Push Notification 
input bool Email = false;                                         // Email Alert

double upper_trend[], lower_trend[], src[], stdv[], upper_array[], lower_array[], RelativeTrendIndex[];
double UpperTrend[], LowerTrend[], MA_RelativeTrendIndex[], OverboughtHist[], OversoldHist[];
int myBars, upper_index, lower_index;
datetime newtime=0;
string Indiname="Relative_Trend_Index";
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   IndicatorShortName(Indiname + "_" + Symbol() + IndiPrefix);
      //--- indicator buffers mapping
   ArraySetAsSeries(UpperTrend, true); ArraySetAsSeries(LowerTrend, true);ArraySetAsSeries(MA_RelativeTrendIndex, true);
   ArraySetAsSeries(upper_trend, true); ArraySetAsSeries(lower_trend, true);
   ArraySetAsSeries(upper_array, true); ArraySetAsSeries(lower_array, true);ArraySetAsSeries(RelativeTrendIndex, true);
   ArraySetAsSeries(OverboughtHist, true); ArraySetAsSeries(OversoldHist, true);
   
   if (Alerts || Phone || Email)
      {
      if (!GlobalVariableCheck(Indiname + "RTIcrossOB" + Symbol() + IndiPrefix) && RTIcrossOB) 
         GlobalVariableSet(Indiname + "RTIcrossOB" + Symbol() + IndiPrefix, TimeCurrent());
         
      if (!GlobalVariableCheck(Indiname + "RTIcrossOS" + Symbol() + IndiPrefix) && RTIcrossOS) 
         GlobalVariableSet(Indiname + "RTIcrossOS" + Symbol() + IndiPrefix, TimeCurrent());
         
      if (!GlobalVariableCheck(Indiname + "RTIcrossMA" + Symbol() + IndiPrefix) && RTIcrossMA) 
         GlobalVariableSet(Indiname + "RTIcrossMA" + Symbol() + IndiPrefix, TimeCurrent());      
      }   
      
   SetIndexBuffer(0, RelativeTrendIndex);
   SetIndexStyle(0, DRAW_LINE);
   SetIndexEmptyValue(0, EMPTY_VALUE);

   SetIndexBuffer(1, MA_RelativeTrendIndex);
   SetIndexStyle(1, DRAW_LINE);
   SetIndexEmptyValue(1, EMPTY_VALUE);
   
   SetIndexBuffer(2, OverboughtHist);
   SetIndexStyle(2, DRAW_LINE);
   SetIndexEmptyValue(2, EMPTY_VALUE);
   
   SetIndexBuffer(3, OversoldHist);
   SetIndexStyle(3, DRAW_LINE);
   SetIndexEmptyValue(3, EMPTY_VALUE);
      
   // Set indicator levels based on input values
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 0, ob);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 1, 50.0);
   IndicatorSetDouble(INDICATOR_LEVELVALUE, 2, os);
   
   Indiname = Indiname + IndiPrefix;
   
   newtime=0;
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
{
   // Only recalculate if we have new data or if this is the first run
   if(prev_calculated > 0 && time[0] == newtime) return(rates_total);
   
   // Limit the number of bars to process
   int bars_to_process;
   if(MaxBars <= 0) 
      bars_to_process = Bars-1;
   else
      bars_to_process = MathMin(rates_total, MaxBars);
   
   upper_index = (int) MathRound((double) trend_sensitivity_percentage / 100 * trend_data_count) - 1;
   lower_index = (int) MathRound((double) (100 - trend_sensitivity_percentage) / 100 * trend_data_count) - 1;

   myBars = MathMin(iBars(Symbol(), Period()), MaxBars);

   // Initialize arrays if needed
   if(prev_calculated == 0)
   {
      ArrayResize(upper_trend, bars_to_process);
      ArrayResize(lower_trend, bars_to_process);
      ArrayResize(UpperTrend, bars_to_process);
      ArrayResize(LowerTrend, bars_to_process);
      ArrayResize(RelativeTrendIndex, bars_to_process);
      ArrayResize(MA_RelativeTrendIndex, bars_to_process);
      ArrayResize(OverboughtHist, bars_to_process);
      ArrayResize(OversoldHist, bars_to_process);
      ArrayInitialize(RelativeTrendIndex, EMPTY_VALUE);
      ArrayInitialize(MA_RelativeTrendIndex, EMPTY_VALUE);
      ArrayInitialize(OverboughtHist, EMPTY_VALUE);
      ArrayInitialize(OversoldHist, EMPTY_VALUE);
   }

   fillSRC(src, myBars, Price);
   TV_STDV(src, stdv, std_dev_length);
   
   for (int i=myBars-1; i>=0; i--)
   {
      upper_trend[i] = src[i] + stdv[i];
      lower_trend[i] = src[i] - stdv[i];
   }

   // Find the first bar where both lines have valid values
   int firstValidBar = myBars-1;
   for(int i=myBars-1; i>=0; i--)
   {
      if(RelativeTrendIndex[i]!=EMPTY_VALUE && MA_RelativeTrendIndex[i]!=EMPTY_VALUE)
      {
         firstValidBar = i;
         break;
      }
   }
   
   for (int j=0; j<myBars-trend_data_count; j++)
   {
      ArrayCopy(upper_array, upper_trend, 0, j, trend_data_count);
      ArrayCopy(lower_array, lower_trend, 0, j, trend_data_count);
      
      ArraySort(upper_array, WHOLE_ARRAY, 0, MODE_ASCEND);
      ArraySort(lower_array, WHOLE_ARRAY, 0, MODE_ASCEND);
      
      UpperTrend[j] = upper_array[upper_index];
      LowerTrend[j] = lower_array[lower_index];
      
      if (UpperTrend[j]!=LowerTrend[j])
      {
         double rawValue = ((src[j] - LowerTrend[j]) / (UpperTrend[j] - LowerTrend[j]))*100;
         // Ensure the value stays within 0-100 range
         RelativeTrendIndex[j] = MathMax(0, MathMin(100, rawValue));
      }
      else
         RelativeTrendIndex[j] = 50; // Default to middle value if upper and lower trends are equal
   }
   
   TV_EMA(MA_RelativeTrendIndex, RelativeTrendIndex, signal_length);
   
   // Plot lines for overbought and oversold conditions
   for (int i=firstValidBar; i>=1; i--)
   {
      if (RelativeTrendIndex[i]>ob)
      {
         OverboughtHist[i] = RelativeTrendIndex[i];
         OversoldHist[i] = EMPTY_VALUE;
      }
      else if (RelativeTrendIndex[i]<os)
      {
         OversoldHist[i] = RelativeTrendIndex[i];
         OverboughtHist[i] = EMPTY_VALUE;
      }
      else
      {
         OverboughtHist[i] = EMPTY_VALUE;
         OversoldHist[i] = EMPTY_VALUE;
      }
   }
   
   if (RTIcrossOB && Time[0]>GlobalVariableGet(Indiname + "RTIcrossOB" + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + IndiPrefix)) 
   {
      if (RelativeTrendIndex[2]<ob && RelativeTrendIndex[1]>=ob) 
      {
         SendAlert("RTI crosses Overbought up for " + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + " @" + TimeToString(Time[0], TIME_DATE|TIME_SECONDS));
         GlobalVariableSet(Indiname + "RTIcrossOB" + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + IndiPrefix, Time[0]);
      }
      else if (RelativeTrendIndex[2]>=ob && RelativeTrendIndex[1]<ob) 
      {
         SendAlert("RTI crosses Overbought down for " + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + " @" + TimeToString(Time[0], TIME_DATE|TIME_SECONDS));
         GlobalVariableSet(Indiname + "RTIcrossOB" + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + IndiPrefix, Time[0]);
      }
   }   
   
   if (RTIcrossOS && Time[0]>GlobalVariableGet(Indiname + "RTIcrossOS" + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + IndiPrefix)) 
   {
      if (RelativeTrendIndex[2]<=os && RelativeTrendIndex[1]>os) 
      {
         SendAlert("RTI crosses Oversold up for " + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + " @" + TimeToString(Time[0], TIME_DATE|TIME_SECONDS));
         GlobalVariableSet(Indiname + "RTIcrossOS" + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + IndiPrefix, Time[0]);
      }
      else if (RelativeTrendIndex[2]>os && RelativeTrendIndex[1]<=os) 
      {
         SendAlert("RTI crosses Oversold down for " + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + " @" + TimeToString(Time[0], TIME_DATE|TIME_SECONDS));
         GlobalVariableSet(Indiname + "RTIcrossOS" + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + IndiPrefix, Time[0]);
      }
   }   
   
   if (RTIcrossMA && Time[0]>GlobalVariableGet(Indiname + "RTIcrossMA" + Symbol() + " " + Tf_as_string(PERIOD_CURRENT)+ IndiPrefix)) 
   {
      if (RelativeTrendIndex[2]<MA_RelativeTrendIndex[2] && RelativeTrendIndex[1]>=MA_RelativeTrendIndex[1]) 
      {
         SendAlert("RTI crosses MA RTI up for " + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + " @" + TimeToString(Time[0], TIME_DATE|TIME_SECONDS));
         GlobalVariableSet(Indiname + "RTIcrossMA" + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + IndiPrefix, Time[0]);
      }
      else if (RelativeTrendIndex[2]>=MA_RelativeTrendIndex[2] && RelativeTrendIndex[1]<MA_RelativeTrendIndex[1]) 
      {
         SendAlert("RTI crosses MA RTI down for " + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + " @" + TimeToString(Time[0], TIME_DATE|TIME_SECONDS));
         GlobalVariableSet(Indiname + "RTIcrossMA" + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + IndiPrefix, Time[0]);
      }
   }   
   
   newtime = time[0];
   return(rates_total);
}
//+------------------------------------------------------------------+
void SendAlert(string S)
{
   if (Alerts) Alert(S);
   if (Phone) SendNotification(S);
   if (Email) SendMail(S, S);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if (reason==1 || reason==4) 
      {
      GlobalVariablesDeleteAll(Indiname + "RTIcrossOB" + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + IndiPrefix);
      GlobalVariablesDeleteAll(Indiname + "RTIcrossOS" + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + IndiPrefix);
      GlobalVariablesDeleteAll(Indiname + "RTIcrossMA" + Symbol() + " " + Tf_as_string(PERIOD_CURRENT) + IndiPrefix);
   
      EventKillTimer();
      }
}
//+------------------------------------------------------------------+
void TV_STDV(double & stdv_src[], double & out[], int length)
{
   ArraySetAsSeries(stdv_src, true); ArraySetAsSeries(out, true);
   double avg[]; ArraySetAsSeries(avg, true); ArrayInitialize(out, 0);ArrayInitialize(avg, 0);
   ArrayResize(out, ArraySize(stdv_src)); ArrayResize(avg, ArraySize(stdv_src)); 
   
   TV_SMA(stdv_src, avg, length);
   
   for (int j=myBars-2-length; j>=0; j--)
      {
      double sumOfSquareDeviations = 0;
      for (int i=j; i<=j+length-1; i++)
         {
         sumOfSquareDeviations+= pow(stdv_src[i]-avg[j], 2); 
         }
         
      out[j] = sqrt(sumOfSquareDeviations / length);
      }
}
//+------------------------------------------------------------------+
void TV_SMA(double & sma_src[], double & out[], int length)
{
   int size = ArraySize(sma_src);
   ArrayResize(out, size); ArrayInitialize(out, 0.0); ArraySetAsSeries(out, true);

   for (int i=0; i<size-length; i++)
      {
      double sum=0;
      for (int j=i; j<i+length; j++)  sum+=sma_src[j];
      out[i] = sum/(double)length;
      }

   return;
}
//+------------------------------------------------------------------+
void TV_EMA(double &out[], double &ema_src[], int length)
{
   double alpha = 2.0 / (length + 1); 
   int size = ArraySize(ema_src);
   ArrayResize(out, size); ArrayInitialize(out, EMPTY_VALUE);
   
   // Initialize MA values to EMPTY_VALUE for bars where RTI is not calculated
   for(int i = 0; i < size; i++)
      if(ema_src[i] == EMPTY_VALUE)
         out[i] = EMPTY_VALUE;
   
   // Calculate initial SMA for the first valid point
   int firstValidBar = size-1;
   for(int i = size-1; i >= 0; i--)
      if(ema_src[i] != EMPTY_VALUE)
         {
         firstValidBar = i;
         break;
         }
   
   // Calculate initial SMA
   double sum = 0;
   int count = 0;
   for(int i = firstValidBar; i > firstValidBar - length && i >= 0; i--)
      {
      if(ema_src[i] != EMPTY_VALUE)
         {
         sum += ema_src[i];
         count++;
         }
      }
   if(count > 0)
      out[firstValidBar] = sum / count;
    
   // Calculate EMA for remaining bars
   for(int i = firstValidBar-1; i >= 0; i--)
      {
      if(ema_src[i] != EMPTY_VALUE)
         {
         double rawValue = alpha * ema_src[i] + (1.0 - alpha) * out[i+1];
         // Ensure the value stays within 0-100 range
         out[i] = MathMax(0, MathMin(100, rawValue));
         }
      }
}
//+------------------------------------------------------------------+
void fillSRC(double & out[], int size, source mysource1)
{
   ArrayResize(out, size); ArrayInitialize(out, 0); ArraySetAsSeries(out, true);

   if (mysource1==0) CopyClose(Symbol(), Period(), 0, size, out);
   else if (mysource1==s1) CopyOpen(Symbol(), Period(), 0, size, out);
   else if (mysource1==s2) CopyHigh(Symbol(), Period(), 0, size, out);
   else if (mysource1==s3) CopyLow(Symbol(), Period(), 0, size, out);

   else if (mysource1==s4)
      for (int i=0; i<size; i++) out[i] = (High[i] + Low[i])/2.0;

   else if (mysource1==s5)
      for (int i=0; i<size; i++) out[i] = (High[i] + Low[i] + Close[i])/3.0;

   else if (mysource1==s6)
      for (int i=0; i<size; i++) out[i] = (High[i] + Low[i] + Close[i]*2)/4.0;

   else if (mysource1==s7)
      for (int i=0; i<size; i++) out[i] = (High[i] + Low[i] + Close[i] + Open[i])/4.0;

   return;
}
//+------------------------------------------------------------------+
string   Tf_as_string(ENUM_TIMEFRAMES period){
   if(period == PERIOD_CURRENT)  period   = (ENUM_TIMEFRAMES) _Period;
   string   period_xxx  = EnumToString(period);                // PERIOD_XXX
   return StringSubstr(period_xxx, 7);                         // XXX
}
//+------------------------------------------------------------------+