//+------------------------------------------------------------------+
//| FXP_LinearRegressionChannel.mq5                                  |
//| FXPIPS ONLINE - https://fxpips.online/mt5-indicators/linear-regression-channel/
//|                                                                  |
//| License: MIT. Copyright (c) 2026 FXPIPS ONLINE.                  |
//| Permission is granted to use, copy, modify and distribute this   |
//| software provided this notice is kept. The software is provided  |
//| "as is", without warranty of any kind.                           |
//|                                                                  |
//| Educational tool - not financial advice. Trading involves        |
//| substantial risk of loss. Test on a demo account first.          |
//+------------------------------------------------------------------+
#property copyright "FXPIPS ONLINE"
#property link      "https://fxpips.online/mt5-indicators/linear-regression-channel/"
#property version   "1.00"
#property description "Least-squares regression line over the last N bars with a"
#property description "standard-deviation or maximum-deviation channel. Redrawn each bar."

#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots   0

enum ENUM_FXP_DEV_MODE
{
   FXP_DEV_STDDEV = 0, // Standard deviation x multiplier
   FXP_DEV_MAX    = 1  // Largest distance from the line
};

input group "Channel"
input int                InpBars       = 100;            // Bars in the regression
input ENUM_APPLIED_PRICE InpPrice      = PRICE_CLOSE;    // Applied price
input ENUM_FXP_DEV_MODE  InpDevMode    = FXP_DEV_STDDEV; // Channel width method
input double             InpDeviation  = 2.0;            // Standard deviation multiplier
input bool               InpClosedBars = true;           // Use closed bars only (update once per bar)
input bool               InpRayRight   = true;           // Extend lines to the right
input group "Style"
input color InpMidColor     = clrGold;       // Regression line colour
input color InpChannelColor = clrDodgerBlue; // Channel colour
input int   InpWidth        = 1;             // Line width
input group "Alerts"
input bool  InpAlerts       = false;         // Popup alert when a closed bar closes outside the channel
input bool  InpPush         = false;         // Push notification when a closed bar closes outside the channel

string   gPrefix = "FXP_LRC_";
datetime gLastBarTime  = 0;
datetime gLastAlertBar = 0;
int      gBars = 100;

string TfName() { return StringSubstr(EnumToString(Period()), 7); }

void Notify(const string text, const datetime barTime)
{
   if(!InpAlerts && !InpPush) return;
   if(barTime == gLastAlertBar) return;
   gLastAlertBar = barTime;
   string msg = "FXP LR Channel " + _Symbol + " " + TfName() + ": " + text;
   if(InpAlerts) Alert(msg);
   if(InpPush)   SendNotification(msg);
}

double PriceOf(const int i, const double &o[], const double &h[], const double &l[], const double &c[])
{
   switch(InpPrice)
   {
      case PRICE_OPEN:     return(o[i]);
      case PRICE_HIGH:     return(h[i]);
      case PRICE_LOW:      return(l[i]);
      case PRICE_MEDIAN:   return((h[i] + l[i]) / 2.0);
      case PRICE_TYPICAL:  return((h[i] + l[i] + c[i]) / 3.0);
      case PRICE_WEIGHTED: return((h[i] + l[i] + 2.0 * c[i]) / 4.0);
      default:             return(c[i]);
   }
}

void DrawLine(const string id, const datetime t1, const double p1, const datetime t2, const double p2,
              const color clr, const ENUM_LINE_STYLE style)
{
   string name = gPrefix + id;
   if(ObjectFind(0, name) < 0)
   {
      ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, name, OBJPROP_BACK, true);
   }
   else
   {
      ObjectMove(0, name, 0, t1, p1);
      ObjectMove(0, name, 1, t2, p2);
   }
   ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, InpRayRight);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_STYLE, style);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, InpWidth);
}

int OnInit()
{
   gBars = MathMax(10, InpBars);
   IndicatorSetString(INDICATOR_SHORTNAME, StringFormat("FXP LR Channel (%d)", gBars));
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   ObjectsDeleteAll(0, gPrefix);
   ChartRedraw(0);
}

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 < gBars + 3) return(0);
   ArraySetAsSeries(time, false);
   ArraySetAsSeries(open, false);
   ArraySetAsSeries(high, false);
   ArraySetAsSeries(low, false);
   ArraySetAsSeries(close, false);

   datetime now = time[rates_total - 1];
   if(prev_calculated <= 0) gLastAlertBar = now;
   bool newBar = (now != gLastBarTime);
   if(InpClosedBars && !newBar && prev_calculated > 0) return(rates_total);   // nothing changed for closed bars
   gLastBarTime = now;

   int last  = InpClosedBars ? rates_total - 2 : rates_total - 1;
   int first = last - gBars + 1;
   double n = gBars;
   double sx = 0.0, sy = 0.0, sxx = 0.0, sxy = 0.0;
   for(int x = 0; x < gBars; x++)
   {
      double y = PriceOf(first + x, open, high, low, close);
      sx += x; sy += y; sxx += (double)x * x; sxy += x * y;
   }
   double denom = n * sxx - sx * sx;
   if(denom == 0.0) return(rates_total);
   double slope = (n * sxy - sx * sy) / denom;
   double icept = (sy - slope * sx) / n;

   double ss = 0.0, maxDev = 0.0;
   for(int x = 0; x < gBars; x++)
   {
      double r = PriceOf(first + x, open, high, low, close) - (icept + slope * x);
      ss += r * r;
      if(MathAbs(r) > maxDev) maxDev = MathAbs(r);
   }
   double dev = (InpDevMode == FXP_DEV_MAX) ? maxDev : InpDeviation * MathSqrt(ss / n);

   double p1 = icept, p2 = icept + slope * (gBars - 1);
   DrawLine("Mid", time[first], p1,       time[last], p2,       InpMidColor,     STYLE_SOLID);
   DrawLine("Up",  time[first], p1 + dev, time[last], p2 + dev, InpChannelColor, STYLE_DASH);
   DrawLine("Dn",  time[first], p1 - dev, time[last], p2 - dev, InpChannelColor, STYLE_DASH);
   ChartRedraw(0);

   if(newBar && prev_calculated > 0)
   {
      int b = rates_total - 2;
      double xb = b - first, xp = b - 1 - first;
      double upB = icept + slope * xb + dev, dnB = icept + slope * xb - dev;
      double upP = icept + slope * xp + dev, dnP = icept + slope * xp - dev;
      if(close[b] > upB && close[b - 1] <= upP) Notify("closed above the regression channel", now);
      if(close[b] < dnB && close[b - 1] >= dnP) Notify("closed below the regression channel", now);
   }
   return(rates_total);
}
//+------------------------------------------------------------------+
