//+------------------------------------------------------------------+
//|                                         PivotLiquidityLines.mq5  |
//|    Detect pivots, draw extended lines, style changes and labels  |
//+------------------------------------------------------------------+
#property copyright "ChatGPT"
#property version   "1.00"
#property strict

input int    LiquidityLength = 15;           // Pivot lookback length
input int    MaxLines = 7;                    // Maximum number of lines stored
input color  UpColor = clrYellow;             // Color for bullish pivots (support)
input color  DownColor = clrYellow;           // Color for bearish pivots (resistance)
input int    LineWidth = 2;
input int    RetestLabelSize = 10;            // Font size for retest labels

#define PREFIX_HIGH "LiquidityHigh_"
#define PREFIX_LOW  "LiquidityLow_"

struct LiquidityLine
  {
   ulong      ticket;          // Object ticket or ID
   double     price;           // Price level
   datetime   time_start;      // Time line was created
   bool       isBroken;        // Has price broken this line?
  };

//--- Arrays to hold lines info
LiquidityLine HighLines[];
LiquidityLine LowLines[];

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   ClearOldLines();
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ClearOldLines();
  }
//+------------------------------------------------------------------+
//| Clear previously drawn liquidity lines                           |
//+------------------------------------------------------------------+
void ClearOldLines()
  {
   // Delete any lines with our prefixes
   int total=ObjectsTotal(0,0,-1);
   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);
     }
   ArrayFree(HighLines);
   ArrayFree(LowLines);
  }

//+------------------------------------------------------------------+
//| Detect pivot high at bar 'index' with lookback window           |
//+------------------------------------------------------------------+
bool IsPivotHigh(int index, int length)
  {
   if(index < length || index > Bars()-length-1)
      return false;

   double piv_high=High[index];
   for(int i=index-length; i<=index+length; i++)
     {
      if(i==index) continue;
      if(High[i]>piv_high) return false;
     }
   return true;
  }

//+------------------------------------------------------------------+
//| Detect pivot low at bar 'index' with lookback window            |
//+------------------------------------------------------------------+
bool IsPivotLow(int index, int length)
  {
   if(index < length || index > Bars()-length-1)
      return false;

   double piv_low=Low[index];
   for(int i=index-length; i<=index+length; i++)
     {
      if(i==index) continue;
      if(Low[i]<piv_low) return false;
     }
   return true;
  }

//+------------------------------------------------------------------+
//| Create or update horizontal liquidity line                       |
//+------------------------------------------------------------------+
void CreateOrUpdateLine(string name, double price, datetime time_start, color clr, bool dashed)
  {
   if(!ObjectFind(0,name))
     {
      ObjectCreate(0,name,OBJ_HLINE,0,0,price);
      ObjectSetInteger(0,name,OBJPROP_BACK,true);
     }
   ObjectSetDouble(0,name,OBJPROP_PRICE,price);
   ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
   ObjectSetInteger(0,name,OBJPROP_WIDTH,LineWidth);
   ObjectSetInteger(0,name,OBJPROP_STYLE,dashed ? STYLE_DASH : STYLE_SOLID);

   // Extend line into the future - MT5 HLINE auto-extends entire chart
   // No need to set time ends for OBJ_HLINE
  }

//+------------------------------------------------------------------+
//| Place a retest label ("x") on the line at current time          |
//+------------------------------------------------------------------+
void PlaceRetestLabel(string prefix, string line_name, datetime time_pos, double price, color clr)
  {
   string lbl_name=line_name+"_label";
   if(ObjectFind(0,lbl_name))
      ObjectDelete(0,lbl_name);
   ObjectCreate(0,lbl_name,OBJ_TEXT,0,time_pos,price);
   ObjectSetString(0,lbl_name,OBJPROP_TEXT,"x");
   ObjectSetInteger(0,lbl_name,OBJPROP_COLOR,clr);
   ObjectSetInteger(0,lbl_name,OBJPROP_FONTSIZE,RetestLabelSize);
   ObjectSetInteger(0,lbl_name,OBJPROP_CORNER,0);
   ObjectSetInteger(0,lbl_name,OBJPROP_BACK,true);
  }

//+------------------------------------------------------------------+
//| Delete oldest lines if exceeding max count                      |
//+------------------------------------------------------------------+
void TrimLines()
  {
   while(ArraySize(HighLines)>MaxLines)
     {
      LiquidityLine line=HighLines[0];
      ObjectDelete(0,PREFIX_HIGH+IntegerToString(line.time_start));
      ArrayRemove(HighLines,0);
     }
   while(ArraySize(LowLines)>MaxLines)
     {
      LiquidityLine line=LowLines[0];
      ObjectDelete(0,PREFIX_LOW+IntegerToString(line.time_start));
      ArrayRemove(LowLines,0);
     }
  }

//+------------------------------------------------------------------+
//| Check line breaks and update style/labels                        |
//+------------------------------------------------------------------+
void CheckLineBreaks()
  {
   double bid_price=SymbolInfoDouble(_Symbol,SYMBOL_BID);

   // Check highs (resistance)
   for(int i=ArraySize(HighLines)-1; i>=0; i--)
     {
      LiquidityLine &ln=HighLines[i];
      // Check if price broke above line
      if(!ln.isBroken && bid_price > ln.price)
        {
         ln.isBroken=true;
         string ln_name=PREFIX_HIGH+IntegerToString(ln.time_start);
         // Change line to dashed
         CreateOrUpdateLine(ln_name, ln.price, ln.time_start, DownColor, true);
        }
      // After break, check for retest (price near line)
      else if(ln.isBroken && MathAbs(bid_price - ln.price) <= _Point*3)
        {
         string ln_name=PREFIX_HIGH+IntegerToString(ln.time_start);
         // Place label on current bar close time
         PlaceRetestLabel(PREFIX_HIGH, ln_name, TimeCurrent(), ln.price, DownColor);
        }
      else
        {
         string ln_name=PREFIX_HIGH+IntegerToString(ln.time_start);
         CreateOrUpdateLine(ln_name, ln.price, ln.time_start, DownColor, ln.isBroken);
        }
     }

   // Check lows (support)
   for(int i=ArraySize(LowLines)-1; i>=0; i--)
     {
      LiquidityLine &ln=LowLines[i];
      // Check if price broke below line
      if(!ln.isBroken && bid_price < ln.price)
        {
         ln.isBroken=true;
         string ln_name=PREFIX_LOW+IntegerToString(ln.time_start);
         // Change line to dashed
         CreateOrUpdateLine(ln_name, ln.price, ln.time_start, UpColor, true);
        }
      // After break, check for retest (price near line)
      else if(ln.isBroken && MathAbs(bid_price - ln.price) <= _Point*3)
        {
         string ln_name=PREFIX_LOW+IntegerToString(ln.time_start);
         PlaceRetestLabel(PREFIX_LOW, ln_name, TimeCurrent(), ln.price, UpColor);
        }
      else
        {
         string ln_name=PREFIX_LOW+IntegerToString(ln.time_start);
         CreateOrUpdateLine(ln_name, ln.price, ln.time_start, UpColor, ln.isBroken);
        }
     }
  }

//+------------------------------------------------------------------+
//| OnTick main loop: detect pivots, create lines and manage them   |
//+------------------------------------------------------------------+
void OnTick()
  {
   static int lastProcessedBar = -1;

   int currentBar = Bars() - 1;

   // Process each new bar once
   if(currentBar == lastProcessedBar) return;

   lastProcessedBar = currentBar;

   // Detect pivot highs and pivot lows at current bar
   if(IsPivotHigh(currentBar, LiquidityLength))
     {
      string name=PREFIX_HIGH+IntegerToString(Time[currentBar]);
      bool exists = ObjectFind(0, name);
      if(exists == false)
        {
         LiquidityLine ln;
         ln.price=High[currentBar];
         ln.time_start=Time[currentBar];
         ln.isBroken=false;
         ln.ticket=0;
         ArrayResize(HighLines, ArraySize(HighLines)+1);
         HighLines[ArraySize(HighLines)-1]=ln;
         // Draw line solid initially
         CreateOrUpdateLine(name, ln.price, ln.time_start, DownColor, false);
         TrimLines();
        }
     }

   if(IsPivotLow(currentBar, LiquidityLength))
     {
      string name=PREFIX_LOW+IntegerToString(Time[currentBar]);
      bool exists = ObjectFind(0, name);
      if(exists == false)
        {
         LiquidityLine ln;
         ln.price=Low[currentBar];
         ln.time_start=Time[currentBar];
         ln.isBroken=false;
         ln.ticket=0;
         ArrayResize(LowLines, ArraySize(LowLines)+1);
         LowLines[ArraySize(LowLines)-1]=ln;
         CreateOrUpdateLine(name, ln.price, ln.time_start, UpColor, false);
         TrimLines();
        }
     }

   // Check for breaks and update accordingly
   CheckLineBreaks();
  }
//+------------------------------------------------------------------+