//+------------------------------------------------------------------+
//|                                               MultiLookbackLines.mq5|
//|                                          Converted from EA by ChatGPT 2024  |
//+------------------------------------------------------------------+
#property copyright "ChatGPT"
#property version   "2.01"
#property indicator_chart_window
#property strict

//--- inputs
input ENUM_TIMEFRAMES InpTimeframe        = PERIOD_H1;    // Timeframe to look back on
input int            InpLookbackCandles   = 10;           // Base number of candles (>=1)
input double         InpMultiplier        = 7.0;          // Max multiplier (integer part used, >=1)
input bool           IncludeCurrentCandle = false;        // Include current forming candle in calculations

input double         InpMinimalDistancePoints = 20;          // Minimal distance between lines, in points (>=0)

input color          ColorHighLines_Base  = clrRed;       // Base color for high lines
input color          ColorLowLines_Base   = clrBlue;      // Base color for low lines
input uint           LineAlpha            = 150;          // Transparency 0-255 (0=fully transparent, 255=no transparency)

//--- defines for naming
#define PREFIX_HIGH "MultiHL_High_"
#define PREFIX_LOW  "MultiHL_Low_"

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
datetime lastBarTime = 0;   // last processed bar time on input timeframe

double MinimalDistancePoints = 20.0;  // working copy of minimal distance; modifiable
color  ColorHighLines;                 // working colors (with alpha)
color  ColorLowLines;

//+------------------------------------------------------------------+
//| Indicator initialization function                               |
//+------------------------------------------------------------------+
int OnInit()
  {
   // Input validation and assigning working variable
   if(InpLookbackCandles < 1)
     {
      Print("Error: InpLookbackCandles must be >= 1");
      return(INIT_FAILED);
     }
   if(InpMultiplier < 1.0)
     {
      Print("Warning: InpMultiplier < 1, setting to 1");
     }
   if(InpMinimalDistancePoints < 0)
     {
      Print("Warning: MinimalDistancePoints < 0, setting to 0");
      MinimalDistancePoints = 0;
     }
   else
      MinimalDistancePoints = InpMinimalDistancePoints;

   // Convert colors with alpha channel
   ColorHighLines = ColorModifyAlpha(ColorHighLines_Base, (uchar)LineAlpha);
   ColorLowLines  = ColorModifyAlpha(ColorLowLines_Base, (uchar)LineAlpha);

   ClearLines();

   UpdateLines();

   Print("MultiLookbackLines indicator initialized");
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Indicator deinitialization function                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ClearLines();
  }

//+------------------------------------------------------------------+
//| Custom indicator iteration function                             |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double& price[])
  {
   datetime currentBarTime = iTime(_Symbol, InpTimeframe, 0);
   if(currentBarTime == lastBarTime)
      return(prev_calculated);

   lastBarTime = currentBarTime;

   UpdateLines();

   return(rates_total);
  }

//+------------------------------------------------------------------+
//| Core function to calculate and manage horizontal lines          |
//+------------------------------------------------------------------+
void UpdateLines()
  {
   int maxMult = (int)MathFloor(InpMultiplier);
   if(maxMult < 1)
      maxMult = 1;

   int startPos = IncludeCurrentCandle ? 0 : 1;

   double filteredHighsPrices[];
   int    filteredHighsMultipliers[];
   double filteredLowsPrices[];
   int    filteredLowsMultipliers[];

   int totalObjects = ObjectsTotal(0);
   for(int i=totalObjects-1; i>=0; i--)
     {
      string objName = ObjectName(0, i);

      if(StringFind(objName, PREFIX_HIGH) == 0)
        {
         double price = ObjectGetDouble(0, objName, OBJPROP_PRICE);
         string suffix = StringSubstr(objName, StringLen(PREFIX_HIGH));
         int mult_index = (int)StringToInteger(suffix);
         if(mult_index <= 0)
            continue;

         if(mult_index > maxMult)
           {
            ObjectDelete(0, objName);
            continue;
           }

         ArrayResize(filteredHighsPrices, ArraySize(filteredHighsPrices) + 1);
         ArrayResize(filteredHighsMultipliers, ArraySize(filteredHighsMultipliers) + 1);
         filteredHighsPrices[ArraySize(filteredHighsPrices) - 1] = price;
         filteredHighsMultipliers[ArraySize(filteredHighsMultipliers) - 1] = mult_index;
        }
      else if(StringFind(objName, PREFIX_LOW) == 0)
        {
         double price = ObjectGetDouble(0, objName, OBJPROP_PRICE);
         string suffix = StringSubstr(objName, StringLen(PREFIX_LOW));
         int mult_index = (int)StringToInteger(suffix);
         if(mult_index <= 0)
            continue;

         if(mult_index > maxMult)
           {
            ObjectDelete(0, objName);
            continue;
           }

         ArrayResize(filteredLowsPrices, ArraySize(filteredLowsPrices) + 1);
         ArrayResize(filteredLowsMultipliers, ArraySize(filteredLowsMultipliers) + 1);
         filteredLowsPrices[ArraySize(filteredLowsPrices) - 1] = price;
         filteredLowsMultipliers[ArraySize(filteredLowsMultipliers) - 1] = mult_index;
        }
     }

   for(int m = 1; m <= maxMult; m++)
     {
      int barsToLookBack = InpLookbackCandles * m;

      MqlRates rates[];
      int copied = CopyRates(_Symbol, InpTimeframe, startPos, barsToLookBack, rates);

      if(copied < 3)
        {
         PrintFormat("Not enough bars copied (%d) for lookback %d (Multiplier %d)", copied, barsToLookBack, m);
         ObjectDelete(0, PREFIX_HIGH + IntegerToString(m));
         ObjectDelete(0, PREFIX_LOW + IntegerToString(m));
         continue;
        }

      double highestLocalHigh = -DBL_MAX;
      int    highestLocalHighIndex = -1;
      double lowestLocalLow = DBL_MAX;
      int    lowestLocalLowIndex = -1;

      for(int i = 1; i < copied - 1; i++)
        {
         if(rates[i].high > rates[i - 1].high && rates[i].high > rates[i + 1].high)
           {
            if(rates[i].high > highestLocalHigh)
              {
               highestLocalHigh = rates[i].high;
               highestLocalHighIndex = i;
              }
           }

         if(rates[i].low < rates[i - 1].low && rates[i].low < rates[i + 1].low)
           {
            if(rates[i].low < lowestLocalLow)
              {
               lowestLocalLow = rates[i].low;
               lowestLocalLowIndex = i;
              }
           }
        }

      string highLineName = PREFIX_HIGH + IntegerToString(m);
      string lowLineName  = PREFIX_LOW + IntegerToString(m);

      // High line process
      if(highestLocalHigh == -DBL_MAX)
        {
         PrintFormat("No valid local high found for multiplier %d (bars %d)", m, barsToLookBack);
         ObjectDelete(0, highLineName);
        }
      else
        {
         double highest = highestLocalHigh;
         bool highCloseFound = false;
         int highCloseIndex = -1;

         for(int i = 0; i < ArraySize(filteredHighsPrices); i++)
           {
            double distPoints = MathAbs(highest - filteredHighsPrices[i]) / _Point;
            if(distPoints < MinimalDistancePoints)
              {
               highCloseFound = true;
               highCloseIndex = i;
               break;
              }
           }

         if(!highCloseFound)
           {
            ArrayResize(filteredHighsPrices, ArraySize(filteredHighsPrices) + 1);
            ArrayResize(filteredHighsMultipliers, ArraySize(filteredHighsMultipliers) + 1);
            filteredHighsPrices[ArraySize(filteredHighsPrices) - 1] = highest;
            filteredHighsMultipliers[ArraySize(filteredHighsMultipliers) - 1] = m;

            DrawOrUpdateLine(highLineName, highest, ColorHighLines);
            PrintFormat("High line Multiplier %d (%d bars): Local High=%.5f - Drawn", m, barsToLookBack, highest);
           }
         else
           {
            if(highest > filteredHighsPrices[highCloseIndex])
              {
               filteredHighsPrices[highCloseIndex] = highest;
               int replacedMultiplier = filteredHighsMultipliers[highCloseIndex];
               string replacedLineName = PREFIX_HIGH + IntegerToString(replacedMultiplier);

               DrawOrUpdateLine(replacedLineName, highest, ColorHighLines);
               PrintFormat("High line Multiplier %d replaced with higher local high=%.5f", replacedMultiplier, highest);
              }
            else
              {
               if(ObjectFind(0, highLineName) == -1)
                  DrawOrUpdateLine(highLineName, filteredHighsPrices[highCloseIndex], ColorHighLines);
               PrintFormat("High line Multiplier %d skipped due to proximity or lower local high (Local High=%.5f)", m, highest);
              }
           }
        }

      // Low line process
      if(lowestLocalLow == DBL_MAX)
        {
         PrintFormat("No valid local low found for multiplier %d (bars %d)", m, barsToLookBack);
         ObjectDelete(0, lowLineName);
        }
      else
        {
         double lowest = lowestLocalLow;
         bool lowCloseFound = false;
         int lowCloseIndex = -1;

         for(int i = 0; i < ArraySize(filteredLowsPrices); i++)
           {
            double distPoints = MathAbs(lowest - filteredLowsPrices[i]) / _Point;
            if(distPoints < MinimalDistancePoints)
              {
               lowCloseFound = true;
               lowCloseIndex = i;
               break;
              }
           }

         if(!lowCloseFound)
           {
            ArrayResize(filteredLowsPrices, ArraySize(filteredLowsPrices) + 1);
            ArrayResize(filteredLowsMultipliers, ArraySize(filteredLowsMultipliers) + 1);
            filteredLowsPrices[ArraySize(filteredLowsPrices) - 1] = lowest;
            filteredLowsMultipliers[ArraySize(filteredLowsMultipliers) - 1] = m;

            DrawOrUpdateLine(lowLineName, lowest, ColorLowLines);
            PrintFormat("Low line  Multiplier %d (%d bars): Local Low=%.5f - Drawn", m, barsToLookBack, lowest);
           }
         else
           {
            if(lowest < filteredLowsPrices[lowCloseIndex])
              {
               filteredLowsPrices[lowCloseIndex] = lowest;
               int replacedMultiplier = filteredLowsMultipliers[lowCloseIndex];
               string replacedLineName = PREFIX_LOW + IntegerToString(replacedMultiplier);

               DrawOrUpdateLine(replacedLineName, lowest, ColorLowLines);
               PrintFormat("Low line  Multiplier %d replaced with lower local low=%.5f", replacedMultiplier, lowest);
              }
            else
              {
               if(ObjectFind(0, lowLineName) == -1)
                  DrawOrUpdateLine(lowLineName, filteredLowsPrices[lowCloseIndex], ColorLowLines);
               PrintFormat("Low line  Multiplier %d skipped due to proximity or higher local low (Local Low=%.5f)", m, lowest);
              }
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Draw or update horizontal line                                  |
//+------------------------------------------------------------------+
void DrawOrUpdateLine(string name, double price, color clr)
  {
   if(ObjectFind(0, name) == -1)
     {
      if(!ObjectCreate(0, name, OBJ_HLINE, 0, 0, price))
        {
         Print("Failed to create line: " + name);
         return;
        }
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT);
      ObjectSetInteger(0, name, OBJPROP_BACK, true);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
     }
   else
     {
      ObjectSetDouble(0, name, OBJPROP_PRICE, price);
     }
  }

//+------------------------------------------------------------------+
//| Clear lines with prefixes                                       |
//+------------------------------------------------------------------+
void ClearLines()
  {
   int total = ObjectsTotal(0);
   for(int i = total - 1; i >= 0; i--)
     {
      string name = ObjectName(0, i);
      if(StringFind(name, PREFIX_HIGH) == 0 || StringFind(name, PREFIX_LOW) == 0)
         ObjectDelete(0, name);
     }
  }

//+------------------------------------------------------------------+
//| Modify the alpha transparency of a color                        |
//+------------------------------------------------------------------+
color ColorModifyAlpha(color c, uchar alpha)
  {
   uchar r = (uchar)ColorGetR(c);
   uchar g = (uchar)ColorGetG(c);
   uchar b = (uchar)ColorGetB(c);
   return(ColorMake(r, g, b, alpha));
  }
//+------------------------------------------------------------------+