//+------------------------------------------------------------------+
//|                                             MultiLookbackLines.mq5|
//+------------------------------------------------------------------+
#property copyright "ChatGPT"
#property version   "1.50"
#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         MinimalDistancePoints = 20;          // Minimal distance between lines, in points

// Optional color inputs
input color          ColorHighLines       = clrRed;
input color          ColorLowLines        = clrBlue;

#define PREFIX_HIGH "MultiHL_High_"
#define PREFIX_LOW  "MultiHL_Low_"

//--- globals
datetime lastBarTime = 0;
double   multiplier  = 1.0;  // working multiplier variable

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   if(InpLookbackCandles < 1)
     {
      Print("Error: InpLookbackCandles must be >= 1");
      return(INIT_FAILED);
     }

   multiplier = InpMultiplier;
   if(multiplier < 1.0)
     {
      Print("Warning: InpMultiplier < 1, setting to 1");
      multiplier = 1.0;
     }

   ClearLines();

   // Draw lines immediately once on init
   UpdateLines();

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

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   // Get time of the latest completed bar on the input timeframe
   datetime currentBarTime = iTime(_Symbol, InpTimeframe, 0);
   if(currentBarTime == lastBarTime)
      return; // no new bar yet on InpTimeframe

   lastBarTime = currentBarTime;

   UpdateLines();
  }

//+------------------------------------------------------------------+
//| Core function to draw or update all lines with filtering        |
//+------------------------------------------------------------------+
void UpdateLines()
  {
   int maxMult = (int)MathFloor(multiplier);
   if(maxMult < 1)
      maxMult = 1;

   int startPos = IncludeCurrentCandle ? 0 : 1;

   //--- Load existing High lines ---
   double filteredHighsPrices[];
   int    filteredHighsMultipliers[];

   //--- Load existing Low lines ---
   double filteredLowsPrices[];
   int    filteredLowsMultipliers[];

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

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

         ArrayResize(filteredHighsPrices, ArraySize(filteredHighsPrices)+1);
         ArrayResize(filteredHighsMultipliers, ArraySize(filteredHighsMultipliers)+1);

         filteredHighsPrices[ArraySize(filteredHighsPrices)-1] = price;
         filteredHighsMultipliers[ArraySize(filteredHighsMultipliers)-1] = mult_index;

         if(mult_index > maxMult)
           ObjectDelete(0, name);
        }
      else if(StringFind(name, PREFIX_LOW) == 0)
        {
         double price = ObjectGetDouble(0, name, OBJPROP_PRICE);
         int mult_index = (int)StringToInteger(StringSubstr(name, StringLen(PREFIX_LOW)));
         if(mult_index <= 0)
            continue;

         ArrayResize(filteredLowsPrices, ArraySize(filteredLowsPrices)+1);
         ArrayResize(filteredLowsMultipliers, ArraySize(filteredLowsMultipliers)+1);

         filteredLowsPrices[ArraySize(filteredLowsPrices)-1] = price;
         filteredLowsMultipliers[ArraySize(filteredLowsMultipliers)-1] = mult_index;

         if(mult_index > maxMult)
           ObjectDelete(0, name);
        }
     }

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

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

      if(copied <= 0)
        {
         PrintFormat("CopyRates failed for %d bars on timeframe %s", barsToLookBack, EnumToString(InpTimeframe));
         continue;
        }

      bool validHighFound = false;
      bool validLowFound = false;

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

      double lowestLocalLow = DBL_MAX;
      int    lowestLocalLowIndex = -1;

      // Loop from 1 to copied-2 to have previous and next bars for check
      for(int i=1; i<copied-1; i++)
        {
         // Validate local high: current high > previous and next highs
         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;
               validHighFound = true;
              }
           }

         // Validate local low: current low < previous and next lows
         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;
               validLowFound = true;
              }
           }
        }

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

      // Process High line
      if(!validHighFound)
        {
         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
              {
               PrintFormat("High line Multiplier %d skipped due to proximity or lower value (Local High=%.5f)", m, highest);
               if(ObjectFind(0, highLineName) == -1)
                  DrawOrUpdateLine(highLineName, filteredHighsPrices[highCloseIndex], ColorHighLines);
              }
           }
        }

      // Process Low line
      if(!validLowFound)
        {
         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
              {
               PrintFormat("Low line  Multiplier %d skipped due to proximity or higher value (Local Low=%.5f)", m, lowest);
               if(ObjectFind(0, lowLineName) == -1)
                  DrawOrUpdateLine(lowLineName, filteredLowsPrices[lowCloseIndex], ColorLowLines);
              }
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| 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 defined 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);
     }
  }
//+------------------------------------------------------------------+