//+------------------------------------------------------------------+
//|                                      Relative_Trend_Index_V0.mq4 |
//|                                  Copyright 2023, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property strict
#property indicator_separate_window

#property indicator_buffers 2
#property indicator_plots 2

#property indicator_color1 White
#property indicator_label1 "Relative Trend Index"
#property indicator_style1 STYLE_SOLID
#property indicator_width1 1

#property indicator_color2 SaddleBrown
#property indicator_label2 "MA Relative Trend Index"
#property indicator_style2 STYLE_SOLID
#property indicator_width2 1

enum source {
            s1,         //Close
            s2,         //Open
            s3,         //High
            s4,         //Low
            s5,         //HL2
            s6,         //HLC3
            s7,         //HLCC4
            s8          //OHLC
            };

input string IndiPrefix="111";                              // Indicator Prefix
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 color obcolor  = Blue;                        // Overbought color
input color oscolor  = OrangeRed;                        // Oversold color
input int obwidth=5;                                        // Overbought fill width
input int oswidth=5;                                        // Oversold fill width
input color LevelsColoru=C'96,96,96';                        // Level up color
input color LevelsColord=C'96,96,96';                        // Level down color
input color LevelsColorm=C'96,96,96';                        // Level mid color
input int Levelswu=1;                        // Level up width
input int Levelswd=1;                        // Level down width
input int Levelswm=1;                        // Level mid width
input ENUM_LINE_STYLE Levelssu=STYLE_DASH;                        // Level up style
input ENUM_LINE_STYLE Levelssd=STYLE_DASH;                        // Level down style
input ENUM_LINE_STYLE Levelssm=STYLE_DASH;                        // Level mid style

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;                                        // pop-up Alert
input bool Phone = false;                                         // Phone Alert 
input bool Email = false;                                         // e-mail Alert

double upper_trend[], lower_trend[], src[], stdv[], upper_array[], lower_array[], RelativeTrendIndex[];// upper_index[], lower_index[];
double UpperTrend[], LowerTrend[], MA_RelativeTrendIndex[];
int myBars, upper_index, lower_index, width=0, mywindow;
datetime newtime=0;
color BGColor;
string Indiname="Relative_Trend_Index", OBJName="";
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   IndicatorShortName(Indiname + "_" + Symbol() + IndiPrefix);
   mywindow = WindowFind(Indiname + "_" + Symbol() + IndiPrefix);
   //Print("Windiw: " ,mywindow);
      //--- 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(upper_index, true); ArraySetAsSeries(lower_index, 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());      
      }   
   
   width = (int) ChartGetInteger(ChartID(), CHART_SCALE)+1; 
   width=5;
   BGColor = (color) ChartGetInteger(ChartID(), CHART_COLOR_BACKGROUND);
   
   SetIndexBuffer(0, RelativeTrendIndex);
   SetIndexStyle(0, DRAW_LINE);
   SetIndexEmptyValue(0, EMPTY_VALUE);

   SetIndexBuffer(1, MA_RelativeTrendIndex);
   SetIndexStyle(1, DRAW_LINE);
   SetIndexEmptyValue(1, EMPTY_VALUE);
   
   Indiname = Indiname + IndiPrefix;
   
   OBJName = Indiname + "ob_Level";
   ObjectCreate(ChartID(), OBJName, OBJ_HLINE, mywindow, 0, 0); //Print("error: ", GetLastError());
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_TIME, 0, Time[0]);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_COLOR, LevelsColoru);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_WIDTH, Levelswu);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_STYLE, Levelssu);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_BACK, true);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_SELECTABLE, false);
   ObjectSetDouble(ChartID(), OBJName, OBJPROP_PRICE, 0, ob);
   
   OBJName = Indiname + "ob_Level_text";
   ObjectCreate(ChartID(), OBJName, OBJ_TEXT, mywindow, 0, 0);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_TIME, 0, Time[0]);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_COLOR, LevelsColoru);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_FONTSIZE, 8);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_BACK, false);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_SELECTABLE, false);
   ObjectSetString(ChartID(), OBJName, OBJPROP_TEXT, DoubleToString(ob,1));
   ObjectSetDouble(ChartID(), OBJName, OBJPROP_PRICE, 0, ob);
   
   OBJName = Indiname + "os_Level";
   ObjectCreate(ChartID(), OBJName, OBJ_HLINE, mywindow, 0, 0);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_TIME, 0, Time[0]);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_COLOR, LevelsColord);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_WIDTH, Levelswd);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_STYLE, Levelssd);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_BACK, true);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_SELECTABLE, false);
   ObjectSetDouble(ChartID(), OBJName, OBJPROP_PRICE, 0, os);
   
   OBJName = Indiname + "os_Level_text";
   ObjectCreate(ChartID(), OBJName, OBJ_TEXT, mywindow, 0, 0);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_TIME, 0, Time[0]);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_COLOR, LevelsColord);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_FONTSIZE, 8);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_BACK, true);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_SELECTABLE, false);
   ObjectSetString(ChartID(), OBJName, OBJPROP_TEXT, DoubleToString(os,1));
   ObjectSetDouble(ChartID(), OBJName, OBJPROP_PRICE, 0, os);
   
   OBJName = Indiname + "mid_Level";
   ObjectCreate(ChartID(), OBJName, OBJ_HLINE, mywindow, 0, 0);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_TIME, 0, Time[0]);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_COLOR, LevelsColorm);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_WIDTH, Levelswm);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_STYLE, Levelssm);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_BACK, true);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_SELECTABLE, false);
   ObjectSetDouble(ChartID(), OBJName, OBJPROP_PRICE, 0, 50);
   
   OBJName = Indiname + "mid_Level_text";
   ObjectCreate(ChartID(), OBJName, OBJ_TEXT, mywindow, 0, 0);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_TIME, 0, Time[0]);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_COLOR, LevelsColorm);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_FONTSIZE, 8);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_ANCHOR, ANCHOR_RIGHT_LOWER);   
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_BACK, true);
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_SELECTABLE, false);
   ObjectSetString(ChartID(), OBJName, OBJPROP_TEXT, "50.0");
   ObjectSetDouble(ChartID(), OBJName, OBJPROP_PRICE, 0, 50);

   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[])
{
   if (iTime(Symbol(), Period(), 0) == newtime) return(rates_total);
   
   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 = iBars(Symbol(), Period());

   fillSRC(src, myBars, 0);
   TV_STDV(src, stdv, std_dev_length);

   ArrayResize(upper_trend, myBars); ArrayResize(lower_trend, myBars); 
   //ArrayResize(upper_trend, trend_data_count); ArrayResize(lower_trend, trend_data_count); 
   
   for (int i=myBars-1; i>=0; i--)
      {
      upper_trend[i] = src[i] + stdv[i];
      lower_trend[i] = src[i] - stdv[i];
      }

   ArrayResize(UpperTrend, myBars); ArrayResize(LowerTrend, myBars); 
   ArrayResize(RelativeTrendIndex, myBars); ArrayResize(MA_RelativeTrendIndex, myBars); 
   ArrayInitialize(RelativeTrendIndex, EMPTY_VALUE);ArrayInitialize(MA_RelativeTrendIndex, EMPTY_VALUE);
   
   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])
         RelativeTrendIndex[j] = ((src[j] - LowerTrend[j]) / (UpperTrend[j] - LowerTrend[j]))*100;
      }
   
   TV_EMA(MA_RelativeTrendIndex, RelativeTrendIndex, signal_length);
   ObjectsDeleteAll(ChartID(), Indiname, mywindow, OBJ_TREND);
   width = (int) ChartGetInteger(ChartID(), CHART_SCALE)+2; 
   
   for (int i=Bars-1; i>=1; i--)
      {
      if (RelativeTrendIndex[i]>ob)
         {
         OBJName = Indiname + "ob"+IntegerToString(i);
         ObjectCreate(ChartID(), OBJName, OBJ_TREND, mywindow, 0, 0);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_TIME, 0, Time[i]);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_TIME, 1, Time[i]);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_COLOR, obcolor);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_RAY, false);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_WIDTH, obwidth);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_BACK, true);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_SELECTABLE, false);
         ObjectSetDouble(ChartID(), OBJName, OBJPROP_PRICE, 0, RelativeTrendIndex[i]);
         ObjectSetDouble(ChartID(), OBJName, OBJPROP_PRICE, 1, ob);
         }
      else if (RelativeTrendIndex[i]<os)
         {
         OBJName = Indiname + "os"+IntegerToString(i);
         ObjectCreate(ChartID(), OBJName, OBJ_TREND, mywindow, 0, 0);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_TIME, 0, Time[i]);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_TIME, 1, Time[i]);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_COLOR, oscolor);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_RAY, false);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_WIDTH, oswidth);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_BACK, true);
         ObjectSetInteger(ChartID(), OBJName, OBJPROP_SELECTABLE, false);
         ObjectSetDouble(ChartID(), OBJName, OBJPROP_PRICE, 0, RelativeTrendIndex[i]);
         ObjectSetDouble(ChartID(), OBJName, OBJPROP_PRICE, 1, os);
         }
      }
   
   if (RTIcrossOB && Time[0]>GlobalVariableGet(Indiname + "RTIcrossOB" + Symbol() + " M" + Period() + IndiPrefix)) 
      {
      if (RelativeTrendIndex[2]<ob && RelativeTrendIndex[1]>=ob) 
         {
         SendAlert("RTI crosses Overbought up for " + Symbol() + " M" + Period() + " @" + TimeToString(Time[0], TIME_DATE|TIME_SECONDS));
         GlobalVariableSet(Indiname + "RTIcrossOB" + Symbol() + " M" + Period() + IndiPrefix, Time[0]);
         }
      else if (RelativeTrendIndex[2]>=ob && RelativeTrendIndex[1]<ob) 
         {
         SendAlert("RTI crosses Overbought down for " + Symbol() + " M" + Period() + " @" + TimeToString(Time[0], TIME_DATE|TIME_SECONDS));
         GlobalVariableSet(Indiname + "RTIcrossOB" + Symbol() + " M" + Period() + IndiPrefix, Time[0]);
         }
      }   
   
   if (RTIcrossOS && Time[0]>GlobalVariableGet(Indiname + "RTIcrossOS" + Symbol() + " M" + Period() + IndiPrefix)) 
      {
      if (RelativeTrendIndex[2]<=os && RelativeTrendIndex[1]>os) 
         {
         SendAlert("RTI crosses Oversold up for " + Symbol() + " M" + Period() + " @" + TimeToString(Time[0], TIME_DATE|TIME_SECONDS));
         GlobalVariableSet(Indiname + "RTIcrossOS" + Symbol() + " M" + Period() + IndiPrefix, Time[0]);
         }
      else if (RelativeTrendIndex[2]>os && RelativeTrendIndex[1]<=os) 
         {
         SendAlert("RTI crosses Oversold down for " + Symbol() + " M" + Period() + " @" + TimeToString(Time[0], TIME_DATE|TIME_SECONDS));
         GlobalVariableSet(Indiname + "RTIcrossOS" + Symbol() + " M" + Period() + IndiPrefix, Time[0]);
         }
      }   
   
   if (RTIcrossMA && Time[0]>GlobalVariableGet(Indiname + "RTIcrossMA" + Symbol() + " M" + Period()+ IndiPrefix)) 
      {
      if (RelativeTrendIndex[2]<MA_RelativeTrendIndex[2] && RelativeTrendIndex[1]>=MA_RelativeTrendIndex[1]) 
         {
         SendAlert("RTI crosses MA RTI up for " + Symbol() + " M" + Period() + " @" + TimeToString(Time[0], TIME_DATE|TIME_SECONDS));
         GlobalVariableSet(Indiname + "RTIcrossMA" + Symbol() + " M" + Period() + IndiPrefix, Time[0]);
         }
      else if (RelativeTrendIndex[2]>=MA_RelativeTrendIndex[2] && RelativeTrendIndex[1]<MA_RelativeTrendIndex[1]) 
         {
         SendAlert("RTI crosses MA RTI down for " + Symbol() + " M" + Period() + " @" + TimeToString(Time[0], TIME_DATE|TIME_SECONDS));
         GlobalVariableSet(Indiname + "RTIcrossMA" + Symbol() + " M" + Period() + IndiPrefix, Time[0]);
         }
      }   
      
   
   newtime = iTime(Symbol(), Period(), 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) 
      {
      ObjectsDeleteAll(ChartID(), Indiname, mywindow, -1);
      GlobalVariablesDeleteAll(Indiname + "RTIcrossOB" + Symbol() + " M" + Period() + IndiPrefix);
      GlobalVariablesDeleteAll(Indiname + "RTIcrossOS" + Symbol() + " M" + Period() + IndiPrefix);
      GlobalVariablesDeleteAll(Indiname + "RTIcrossMA" + Symbol() + " M" + Period() + 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, 0);
   //Print("out: " , size," ", ArraySize(out));
   for (int i=size-length; i<=size-1; i++) out[size-1] += (ema_src[i]/length);
    
   for (int i =size-2; i>=0; i--)
      out[i] = alpha * ema_src[i] + (1.0 - alpha) * out[i+1];
}
//+------------------------------------------------------------------+
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==1) CopyOpen(Symbol(), Period(), 0, size, out);
   else if (mysource1==2) CopyHigh(Symbol(), Period(), 0, size, out);
   else if (mysource1==3) CopyLow(Symbol(), Period(), 0, size, out);

   else if (mysource1==4)
      for (int i=0; i<size; i++) out[i] = (High[i] + Low[i])/2.0;

   else if (mysource1==5)
      for (int i=0; i<size; i++) out[i] = (High[i] + Low[i] + Close[i])/3.0;

   else if (mysource1==6)
      for (int i=0; i<size; i++) out[i] = (High[i] + Low[i] + Close[i]*2)/4.0;

   else if (mysource1==7)
      for (int i=0; i<size; i++) out[i] = (High[i] + Low[i] + Close[i] + Open[i])/4.0;

   return;
}
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
{
   int window=0;
   datetime lastbar = 0;
   double price;

   ChartXYToTimePrice(ChartID(), (int) ChartGetInteger(ChartID(), CHART_WIDTH_IN_PIXELS, mywindow)-5, 0, window, lastbar, price);
   //Print("OrigWindow ", window," ",mywindow);
   OBJName = Indiname + "ob_Level_text";
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_TIME, lastbar);
   OBJName = Indiname + "os_Level_text";
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_TIME, lastbar);
   OBJName = Indiname + "mid_Level_text";
   ObjectSetInteger(ChartID(), OBJName, OBJPROP_TIME, lastbar);
}