//+------------------------------------------------------------------+
//| 62_Linear_Regression_Envelope_v1_01.mq5                         |
//| Rolling regression center and maximum-residual envelope.         |
//+------------------------------------------------------------------+
#property strict
#property version   "1.01"
#property indicator_chart_window
#property indicator_plots 3
#property indicator_buffers 3
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDeepSkyBlue
#property indicator_width1 2
#property indicator_label1 "Regression center"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrGold
#property indicator_width2 2
#property indicator_label2 "Upper envelope"
#property indicator_type3 DRAW_LINE
#property indicator_color3 clrGold
#property indicator_width3 2
#property indicator_label3 "Lower envelope"

input int InpPeriod = 30;

double Mid[];
double Upper[];
double Lower[];

int OnInit()
{
   if(InpPeriod < 3)
      return INIT_PARAMETERS_INCORRECT;

   SetIndexBuffer(0, Mid, INDICATOR_DATA);
   SetIndexBuffer(1, Upper, INDICATOR_DATA);
   SetIndexBuffer(2, Lower, INDICATOR_DATA);
   ArraySetAsSeries(Mid, true);
   ArraySetAsSeries(Upper, true);
   ArraySetAsSeries(Lower, true);
   PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
   PlotIndexSetInteger(0, PLOT_DRAW_BEGIN, InpPeriod - 1);
   PlotIndexSetInteger(1, PLOT_DRAW_BEGIN, InpPeriod - 1);
   PlotIndexSetInteger(2, PLOT_DRAW_BEGIN, InpPeriod - 1);
   IndicatorSetString(INDICATOR_SHORTNAME, "Linear regression envelope (30)");
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
}

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(rates_total < InpPeriod)
      return 0;

   ArraySetAsSeries(close, true);
   if(prev_calculated == 0)
   {
      ArrayInitialize(Mid, EMPTY_VALUE);
      ArrayInitialize(Upper, EMPTY_VALUE);
      ArrayInitialize(Lower, EMPTY_VALUE);
   }

   const int oldest_valid = rates_total - InpPeriod;
   const int limit = (prev_calculated == 0)
                     ? oldest_valid
                     : MathMin(oldest_valid, rates_total - prev_calculated + 1);

   double sum_x = 0.0;
   double sum_xx = 0.0;
   for(int j = 0; j < InpPeriod; ++j)
   {
      sum_x += j;
      sum_xx += j * j;
   }
   const double denominator = InpPeriod * sum_xx - sum_x * sum_x;
   if(denominator == 0.0)
      return 0;

   for(int i = limit; i >= 0; --i)
   {
      double sum_y = 0.0;
      double sum_xy = 0.0;
      bool valid = true;
      for(int j = 0; j < InpPeriod; ++j)
      {
         const double value = close[i + j];
         if(value <= 0.0 || !MathIsValidNumber(value))
         {
            valid = false;
            break;
         }
         sum_y += value;
         sum_xy += j * value;
      }

      if(!valid)
      {
         Mid[i] = EMPTY_VALUE;
         Upper[i] = EMPTY_VALUE;
         Lower[i] = EMPTY_VALUE;
         continue;
      }

      const double slope = (InpPeriod * sum_xy - sum_x * sum_y) / denominator;
      const double intercept = (sum_y - slope * sum_x) / InpPeriod;
      double width = 0.0;
      for(int j = 0; j < InpPeriod; ++j)
      {
         const double fitted = intercept + slope * j;
         width = MathMax(width, MathAbs(close[i + j] - fitted));
      }

      Mid[i] = intercept;
      Upper[i] = intercept + width;
      Lower[i] = intercept - width;
   }

   return rates_total;
}
//+------------------------------------------------------------------+
