//+------------------------------------------------------------------+
//|                                             MultiLookbackLines.mq5|
//+------------------------------------------------------------------+
#property copyright "ChatGPT"
#property version   "1.48"
#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;

   // Arrays to hold accepted line prices and their multiplier index
   // For convenience, we’ll store arrays: prices and multiplier indices 
   double filteredHighsPrices[];
   int    filteredHighsMultipliers[];

   double filteredLowsPrices[];
   int    filteredLowsMultipliers[];

   // Clear previous lines before drawing updated ones
   ClearLines();

   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;
        }

      double highest = -DBL_MAX;
      double lowest  =  DBL_MAX;

      for(int i=0; i<copied; i++)
        {
         if(rates[i].high > highest)
            highest = rates[i].high;
         if(rates[i].low < lowest)
            lowest = rates[i].low;
        }

      // --- Filter Highs with replacement logic ---
      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;
           }
        }

      string highLineName = PREFIX_HIGH + IntegerToString(m);

      if(!highCloseFound)
        {
         // No close high found, add new high line
         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): High=%.5f - Drawn", m, barsToLookBack, highest);
        }
      else
        {
         // Close high found - check if new high is higher - replace if yes
         if(highest > filteredHighsPrices[highCloseIndex])
           {
            // Replace value and update line named by multiplier at that index
            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 value High=%.5f", replacedMultiplier, highest);
           }
         else
           {
            // Skip drawing lower or equal high close to existing line
            PrintFormat("High line Multiplier %d skipped due to proximity or lower value (High=%.5f)", m, highest);
           }
        }

      // --- Filter Lows with replacement logic ---
      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;
           }
        }

      string lowLineName = PREFIX_LOW + IntegerToString(m);

      if(!lowCloseFound)
        {
         // No close low found, add new low line
         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): Low=%.5f - Drawn", m, barsToLookBack, lowest);
        }
      else
        {
         // Close low found - replace if the new low is lower than stored one
         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 value Low=%.5f", replacedMultiplier, lowest);
           }
         else
           {
            PrintFormat("Low line  Multiplier %d skipped due to proximity or higher value (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 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);
     }
  }
//+------------------------------------------------------------------+