//+------------------------------------------------------------------+
//|                                   QuantMasterLiquidityEvents.mq5 |
//|                                  Copyright 2025, Google Deepmind |
//|                                       Translated from Pine Script|
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Google Deepmind"
#property link      "https://www.google.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 7
#property indicator_plots   0

//--- Constants
#define PREFIX "QMLE_"

//--- Enums
enum ENUM_SRC_VOL {
   SRC_DELTA,  // Volume Delta
   SRC_VOLUME  // Total Volume
};

enum ENUM_DASH_POS {
   POS_TOP_RIGHT,    // Top Right
   POS_BOTTOM_RIGHT, // Bottom Right
   POS_BOTTOM_LEFT,  // Bottom Left
   POS_TOP_LEFT      // Top Left
};

enum ENUM_DASH_SIZE {
   SIZE_SMALL,  // Small
   SIZE_NORMAL, // Normal
   SIZE_LARGE,  // Large
   SIZE_HUGE    // Huge
};

enum ENUM_ALERT_MODE {
   ALERT_OFF,       // No Alerts
   ALERT_CURRENT,   // Alert on Current Bar
   ALERT_CLOSED     // Alert on Closed Bar
};

//--- Inputs
input group "Data Feeds"
input ENUM_SRC_VOL InpSrcVol = SRC_DELTA;      // Volume Source
input ENUM_TIMEFRAMES InpTFLower = PERIOD_M1;  // Intrabar Precision (Trend Timeframe)
input int InpLenStat = 60;                     // Statistical Lookback (20-...)

input group "Institutional Filters"
input double InpZVolThresh = 2.0;              // Min Volume Z-Score (sigma)
input double InpZPxThresh = 2.0;               // Min Price Z-Score (sigma)
input bool InpUseDiv = true;                   // Highlight CVD Divergences

input group "Visuals"
input bool InpShowBull = true;                 // Show Bullish Events
input bool InpShowBear = true;                 // Show Bearish Events
input bool InpScaleSize = true;                // Dynamic Bubble Size
input bool InpShowGhosts = true;               // Show Liquidity Memory (Ghost Lines)

input group "Dashboard"
input bool InpShowDash = true;                 // Show Info Dashboard
input ENUM_DASH_POS InpDashPos = POS_TOP_RIGHT;// Position
input ENUM_DASH_SIZE InpDashSize = SIZE_NORMAL;// Size

input group "Alerts"
input ENUM_ALERT_MODE InpAlertMode = ALERT_CURRENT; // Alert Mode

//--- Buffers
double ValBuffer[];    // Raw Value (Delta or Vol)
double ZVolBuffer[];   // Volume Z-Score
double ZPxBuffer[];    // Price Z-Score
double EventTypeBuffer[]; // 0=None, 1=Bull, 2=Bear, 3=AbsorbBuy, 4=AbsorbSell
double AvgBodyBuffer[]; // For Absorption logic

//--- Global Variables
// Structures for Dashboard Log
struct LogEvent {
   datetime time;
   int type;      // 1=Momentum Buy, 2=Momentum Sell, 3=Absorb Buy, 4=Absorb Sell
   double z_score;
   double price;
};
LogEvent g_event_log[]; // Dynamic array

// Ghost Lines Management
struct GhostLine {
   string name;
   datetime creation_time;
   double price;
};
GhostLine g_active_ghosts[];

// Colors
color col_bull      = C'0,230,118';   // #00E676
color col_bear      = C'255,23,68';   // #FF1744
color col_absorb_bu = C'41,98,255';   // #2962FF
color col_absorb_be = C'255,109,0';   // #FF6D00

// Alert State
datetime g_last_alert_time = 0;

// Forward Declarations
void UpdateDashboard(int rates_total, const datetime &time[], const double &close[]);

//+------------------------------------------------------------------+
//| Custom Indicator initialization function                         |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Custom Indicator initialization function                         |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Custom Indicator initialization function                         |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Custom Indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   //--- indicator buffers mapping
   SetIndexBuffer(0, ValBuffer, INDICATOR_CALCULATIONS);
   SetIndexBuffer(1, ZVolBuffer, INDICATOR_CALCULATIONS);
   SetIndexBuffer(2, ZPxBuffer, INDICATOR_CALCULATIONS);
   SetIndexBuffer(3, EventTypeBuffer, INDICATOR_CALCULATIONS);
   SetIndexBuffer(4, AvgBodyBuffer, INDICATOR_CALCULATIONS);

   // Validate Inputs
   if(InpLenStat < 2) {
      Alert("Statistical Lookback must be >= 2");
      return(INIT_FAILED);
   }

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Custom Indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, PREFIX);
   Comment("");
  }



//+------------------------------------------------------------------+
//| Helper: Format Volume                                            |
//+------------------------------------------------------------------+
string FormatVolume(double vol)
  {
   double abs_vol = MathAbs(vol);
   if(abs_vol >= 1e9) return StringFormat("%.2fB", abs_vol / 1e9);
   if(abs_vol >= 1e6) return StringFormat("%.2fM", abs_vol / 1e6);
   if(abs_vol >= 1e3) return StringFormat("%.0fK", abs_vol / 1e3);
   return StringFormat("%.0f", abs_vol);
  }

//+------------------------------------------------------------------+
//| Custom Indicator iteration function                              |
//+------------------------------------------------------------------+
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 < InpLenStat + 1) return(0);

   // Start index
   int start = prev_calculated - 1;
   if(start < 0) start = 0;

   // Main Loop
   for(int i = start; i < rates_total; i++)
     {
      // --- 1. Hybrid Data Aggregation ---
      double bar_delta = 0.0;
      double bar_vol   = 0.0;
      
      long vol_current = tick_volume[i]; // Default to tick volume

      // Use Lower Timeframe if requested and valid
      if(InpTFLower != PERIOD_CURRENT && PeriodSeconds(InpTFLower) < PeriodSeconds(Period()))
        {
         MqlRates rates[];
         datetime t_start = time[i];
         datetime t_end   = t_start + PeriodSeconds(Period());
         
         // Fetch data
         int count = CopyRates(_Symbol, InpTFLower, t_start, t_end, rates);
         if(count > 0)
           {
            for(int k=0; k<count; k++)
              {
               if(rates[k].time >= t_end) continue;
               double v = (double)rates[k].tick_volume;
               // Delta: Close >= Open ? Buy : Sell
               double d = (rates[k].close >= rates[k].open) ? v : -v;
               bar_delta += d;
               bar_vol += v;
              }
           }
         else
           {
            // Fallback: Estimation
            double mfm = (high[i]-low[i]) != 0 ? ((close[i]-low[i]) - (high[i]-close[i])) / (high[i]-low[i]) : 0;
            bar_delta = (double)vol_current * mfm;
            bar_vol = (double)vol_current;
           }
        }
      else
        {
         // Standard / Same Timeframe
         double mfm = (high[i]-low[i]) != 0 ? ((close[i]-low[i]) - (high[i]-close[i])) / (high[i]-low[i]) : 0;
         bar_delta = (double)vol_current * mfm;
         bar_vol = (double)vol_current;
        }

      double metric_raw = (InpSrcVol == SRC_DELTA) ? bar_delta : bar_vol;
      double metric_abs = MathAbs(metric_raw);
      ValBuffer[i] = metric_raw;

      // --- 2. Z-Score Matrix ---
      if(i < InpLenStat) 
        {
         ZVolBuffer[i] = 0.0;
         ZPxBuffer[i] = 0.0;
         continue; 
        }

      // Calculate Vol Stats (Mean & StdDev)
      double sum_vol = 0;
      double sum_sq_vol = 0;
      
      // Calculate Price Stats
      double sum_px = 0;
      double sum_sq_px = 0; 
      
      for(int k=0; k<InpLenStat; k++)
        {
         // Vol
         double v_hist = MathAbs(ValBuffer[i-k]);
         sum_vol += v_hist;
         sum_sq_vol += v_hist * v_hist;
         
         // Price (Close)
         double p_hist = close[i-k];
         sum_px += p_hist;
         sum_sq_px += p_hist * p_hist;
        }

      double mean_vol = sum_vol / InpLenStat;
      double var_vol  = (sum_sq_vol / InpLenStat) - (mean_vol * mean_vol);
      double std_vol  = (var_vol > 0) ? MathSqrt(var_vol) : 0;
      double z_vol    = (std_vol != 0) ? (metric_abs - mean_vol) / std_vol : 0.0;

      double mean_px = sum_px / InpLenStat;
      double var_px  = (sum_sq_px / InpLenStat) - (mean_px * mean_px);
      double std_px  = (var_px > 0) ? MathSqrt(var_px) : 0;
      double z_px    = (std_px != 0) ? (close[i] - mean_px) / std_px : 0.0;

      ZVolBuffer[i] = z_vol;
      ZPxBuffer[i]  = z_px;

      // --- 3. Logic Gates ---
      bool is_vol_event = z_vol >= InpZVolThresh;
      bool is_px_event  = MathAbs(z_px) >= InpZPxThresh;
      
      // Divergence Logic
      bool is_bull_div = (close[i] < open[i]) && (bar_delta > 0);
      bool is_bear_div = (close[i] > open[i]) && (bar_delta < 0);
      
      bool valid_trigger = is_vol_event && (is_px_event || (InpUseDiv && (is_bull_div || is_bear_div)));

      if(valid_trigger)
        {
         bool is_up = metric_raw > 0;
         int event_type = 0; 
         string tooltip = "";
         color bubble_col = clrNONE;

         // Determine Type
         if(is_bull_div) {
            bubble_col = col_absorb_bu;
            tooltip = "STOP ABSORPTION BUY (Iceberg)\nBias: Reversal Long";
            event_type = 3; // Absorb Buy
         } else if(is_bear_div) {
            bubble_col = col_absorb_be;
            tooltip = "STOP ABSORPTION SELL (Iceberg)\nBias: Reversal Short";
            event_type = 4; // Absorb Sell
         } else {
            bubble_col = is_up ? col_bull : col_bear;
            tooltip = is_up ? "MOMENTUM BUY (Aggression)" : "MOMENTUM SELL (Aggression)";
            event_type = is_up ? 1 : 2; // Mom Buy/Sell
         }

         EventTypeBuffer[i] = event_type;

         // --- Alerts ---
         if(InpAlertMode != ALERT_OFF && i > rates_total - 3)
           {
            bool is_current = (i == rates_total - 1);
            bool is_closed  = (i == rates_total - 2);

            if((InpAlertMode == ALERT_CURRENT && is_current) ||
               (InpAlertMode == ALERT_CLOSED && is_closed))
              {
               if(time[i] != g_last_alert_time)
                 {
                  if(prev_calculated > 0) // Suppress on init
                    {
                     string clean_tt = tooltip;
                     StringReplace(clean_tt, "\n", " - ");
                     Alert("QuantMaster: " + clean_tt + " on " + _Symbol);
                    }
                  g_last_alert_time = time[i];
                 }
              }
           }

         // Visuals
         if((event_type == 1 && InpShowBull) || (event_type == 2 && InpShowBear) || 
            (event_type > 2 && (InpShowBull || InpShowBear))) // Show absorptions if correlated direction enabled? Or always? Pine logic implies strict separation but input says "Show Bullish Events". Assuming Momentum Buy + Absorb Buy = Bullish Events.
           {
            // Simplify visibility
            bool draw_me = false;
            if(event_type == 1 && InpShowBull) draw_me = true; // Mom Buy
            if(event_type == 2 && InpShowBear) draw_me = true; // Mom Sell
            if(event_type == 3 && InpShowBull) draw_me = true; // Absorb Buy (Green-ish implication)
            if(event_type == 4 && InpShowBear) draw_me = true; // Absorb Sell (Red-ish implication)

            if(draw_me)
              {
                // Draw Bubble
                string name = PREFIX + "Bub_" + IntegerToString(time[i]);
                if(ObjectFind(0, name) < 0) 
                  {
                   ObjectCreate(0, name, OBJ_TEXT, 0, time[i], high[i]); // Anchored roughly
                   // Refine Anchor
                   double anchor_price = (high[i] + low[i] + close[i]) / 3.0;
                   ObjectSetDouble(0, name, OBJPROP_PRICE, anchor_price);
                   ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_CENTER);
                  }

                // Determine Size
                int f_size = 10;
                string dot_char = "l"; // Wingdings circle
                if(InpScaleSize) 
                  {
                   if(z_vol > InpZVolThresh + 2.0) f_size = 18; // Large
                   else if(z_vol > InpZVolThresh + 1.0) f_size = 14; // Medium
                  }

                ObjectSetString(0, name, OBJPROP_TEXT, dot_char);
                ObjectSetString(0, name, OBJPROP_FONT, "Wingdings");
                ObjectSetInteger(0, name, OBJPROP_FONTSIZE, f_size);
                ObjectSetInteger(0, name, OBJPROP_COLOR, bubble_col);
                
                // Tooltip
                bool is_high_res = (InpTFLower != PERIOD_CURRENT && PeriodSeconds(InpTFLower) < PeriodSeconds(Period()));
                string qual_str = is_high_res ? "(High Res)" : "(Standard)";
                string tt_str = tooltip + "\nVol Z: " + DoubleToString(z_vol, 2) + "\nVol Delta: " + FormatVolume(metric_raw) + "\nData Quality: " + qual_str;
                ObjectSetString(0, name, OBJPROP_TOOLTIP, tt_str);
                ObjectSetInteger(0, name, OBJPROP_SELECTABLE, true); // Ensure hover works
                
                // Text Label (Volume)
                string name_txt = PREFIX + "Txt_" + IntegerToString(time[i]);
                if(ObjectFind(0, name_txt) < 0) ObjectCreate(0, name_txt, OBJ_TEXT, 0, time[i], high[i]);
                ObjectSetDouble(0, name_txt, OBJPROP_PRICE, (high[i] + low[i] + close[i]) / 3.0);
                ObjectSetInteger(0, name_txt, OBJPROP_ANCHOR, ANCHOR_CENTER);
                ObjectSetString(0, name_txt, OBJPROP_TEXT, FormatVolume(metric_raw));
                ObjectSetInteger(0, name_txt, OBJPROP_FONTSIZE, 7);
                ObjectSetInteger(0, name_txt, OBJPROP_COLOR, clrWhite);
                
                // Apply SAME Tooltip to the Text Label because it overlays the Bubble
                ObjectSetString(0, name_txt, OBJPROP_TOOLTIP, tt_str);
                ObjectSetInteger(0, name_txt, OBJPROP_SELECTABLE, true);
                
                // Add to Ghosts
                // Pine: if show_ghosts and is_significant (Med or Large)
                bool is_sig = (f_size >= 14);
                if(InpShowGhosts && is_sig)
                  {
                   double def_level = is_up ? low[i] : high[i];
                   // Logic: Create a trendline that extends
                   // In standard MT5, we can't easily auto-shift line ends without recomp.
                   // We'll just draw a line of fixed length or "Ray Right" if that's the intent.
                   // Pine draws `line.new(bar_index, defense_level, bar_index + 50)`
                   
                   string l_name = PREFIX + "Ghost_" + IntegerToString(time[i]);
                   ObjectCreate(0, l_name, OBJ_TREND, 0, time[i], def_level, time[i]+PeriodSeconds()*50, def_level);
                   ObjectSetInteger(0, l_name, OBJPROP_COLOR, (color)bubble_col);
                   ObjectSetInteger(0, l_name, OBJPROP_STYLE, STYLE_DOT);
                   ObjectSetInteger(0, l_name, OBJPROP_WIDTH, 1);
                   ObjectSetInteger(0, l_name, OBJPROP_RAY_RIGHT, false);
                   
                   // Manage Ghost Limit (Max 10 active lines) works better with a proper queue system.
                   // For now, let's just create them. Cleanup is handled by new creates deleting old? 
                   // Pine matches specific array logic. We'll simplify: just create.
                   // Cleanup oldest if too many?
                   // A proper object management system is complex here.
                  }
                  
                // Store in Dashboard Log
                // Not ideal to resize array in OnCalculate every tick. 
                // We'll use a circular buffer or just update the last few.
                // Since this runs on history too, we need to be careful with "Log".
                // Log only on the last bar? Or build log from all history?
                // Dashboard usually shows "Last 5 signals".
                // We can just rebuild the log from the EventTypeBuffer at the end of OnCalculcate.
              }
           }
        }
     }

   // --- Dashboard Rendering ---
   if(InpShowDash) UpdateDashboard(rates_total, time, close);

   return(rates_total);
  }

//+------------------------------------------------------------------+
//| Helper: Get Dashboard Coordinates                                |
//+------------------------------------------------------------------+
void GetDashCoords(int &x, int &y, int width, int height)
{
   int chart_width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
   int chart_height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS);
   
   int pad = 10;
   
   switch(InpDashPos)
   {
      case POS_TOP_RIGHT:
         x = chart_width - width - pad;
         y = pad;
         break;
      case POS_BOTTOM_RIGHT:
         x = chart_width - width - pad;
         y = chart_height - height - pad;
         break;
      case POS_BOTTOM_LEFT:
         x = pad;
         y = chart_height - height - pad;
         break;
      case POS_TOP_LEFT:
         x = pad;
         y = pad;
         break;
   }
}

//+------------------------------------------------------------------+
//| Helper: Draw Dashboard Cell                                      |
//+------------------------------------------------------------------+
void DrawDashCell(string name, int x, int y, int w, int h, color bg_col, string text, color txt_col, int f_size=8, int align=ALIGN_CENTER)
{
   if(ObjectFind(0, name) < 0) ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, name, OBJPROP_XSIZE, w);
   ObjectSetInteger(0, name, OBJPROP_YSIZE, h);
   ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg_col);
   ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   
   string lbl_name = name + "_L";
   if(ObjectFind(0, lbl_name) < 0) ObjectCreate(0, lbl_name, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, lbl_name, OBJPROP_XDISTANCE, x + (align==ALIGN_CENTER ? w/2 : 5));
   ObjectSetInteger(0, lbl_name, OBJPROP_YDISTANCE, y + h/2 - f_size + 1);
   ObjectSetInteger(0, lbl_name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetString(0, lbl_name, OBJPROP_TEXT, text);
   ObjectSetString(0, lbl_name, OBJPROP_FONT, "Arial");
   ObjectSetInteger(0, lbl_name, OBJPROP_FONTSIZE, f_size);
   ObjectSetInteger(0, lbl_name, OBJPROP_COLOR, txt_col);
   ObjectSetInteger(0, lbl_name, OBJPROP_ANCHOR, align==ALIGN_CENTER ? ANCHOR_CENTER : ANCHOR_LEFT_UPPER);
}

//+------------------------------------------------------------------+
//| Dashboard Logic                                                  |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Dashboard Logic                                                  |
//+------------------------------------------------------------------+
void UpdateDashboard(int rates_total, const datetime &time[], const double &close[])
{
   if(!InpShowDash) return;
   
   // Collect Last 5 Signals
   int found = 0;
   LogEvent logs[5];
   
   datetime cutoff_time = TimeCurrent() - 86400; // 24 hours ago
   
   for(int i = rates_total - 1; i >= 0 && found < 5; i--)
   {
      // Optimization: Stop if we go back too far (assuming time is sorted)
      if(time[i] < cutoff_time) break; 
      
      if(EventTypeBuffer[i] > 0)
      {
         logs[found].time = time[i];
         logs[found].type = (int)EventTypeBuffer[i];
         logs[found].z_score = ZVolBuffer[i];
         logs[found].price = close[i];
         found++;
      }
   }
   
   // Dimensions & Style
   int row_h = (InpDashSize == SIZE_SMALL) ? 15 : (InpDashSize == SIZE_NORMAL) ? 20 : 25;
   int f_size = (InpDashSize == SIZE_SMALL) ? 7 : (InpDashSize == SIZE_NORMAL) ? 9 : 11;
   int col_w_1 = (InpDashSize == SIZE_SMALL) ? 100 : 130; // Slightly wider for text
   int col_w_2 = (InpDashSize == SIZE_SMALL) ? 180 : 250; // Wider for detailed descriptions
   int width = col_w_1 + col_w_2;
   
   // Calculate total coordinates
   int start_x = 0, start_y = 0;
   // We effectively need ~15 rows? 
   // Header(1) + Legend(4) + SizeHeader(1) + SizeRows(3) + LogHeader(1) + Logs(5) = 15
   int total_rows = 15;
   int height = total_rows * row_h;
   
   GetDashCoords(start_x, start_y, width, height);
   
   color bg_main   = C'30,30,30'; // Dark background
   color bg_header = C'0,229,255'; // #00E5FF
   
   // --- Row 0: HEADER ---
   // Pine: text_color=white, bgcolor=#00E5FF (80 transp? MQL solid)
   DrawDashCell(PREFIX + "H", start_x, start_y, width, row_h, bg_header, "QUANT LOGIC MATRIX", clrWhite, f_size+1);
   
   int y = start_y + row_h;
   
   // --- Row 1-4: COLOR LEGEND ---
   // Row 1: Bull
   DrawDashCell(PREFIX + "L1_C", start_x, y, col_w_1, row_h, col_bull, "🟢 M. BUY", clrWhite, f_size, ALIGN_LEFT);
   DrawDashCell(PREFIX + "L1_T", start_x + col_w_1, y, col_w_2, row_h, bg_main, "Aggressive market buys pushing price UP.", clrWhite, f_size, ALIGN_LEFT);
   
   y += row_h;
   // Row 2: Bear
   DrawDashCell(PREFIX + "L2_C", start_x, y, col_w_1, row_h, col_bear, "🔴 M. SELL", clrWhite, f_size, ALIGN_LEFT);
   DrawDashCell(PREFIX + "L2_T", start_x + col_w_1, y, col_w_2, row_h, bg_main, "Aggressive market sells pushing price DOWN.", clrWhite, f_size, ALIGN_LEFT);
   
   y += row_h;
   // Row 3: Absorb Buy
   DrawDashCell(PREFIX + "L3_C", start_x, y, col_w_1, row_h, col_absorb_bu, "🔵 ABS BUY", clrWhite, f_size, ALIGN_LEFT);
   DrawDashCell(PREFIX + "L3_T", start_x + col_w_1, y, col_w_2, row_h, bg_main, "Price dropping but buying detected (Reversal).", clrWhite, f_size, ALIGN_LEFT);
   
   y += row_h;
   // Row 4: Absorb Sell
   DrawDashCell(PREFIX + "L4_C", start_x, y, col_w_1, row_h, col_absorb_be, "🟠 ABS SELL", clrWhite, f_size, ALIGN_LEFT);
   DrawDashCell(PREFIX + "L4_T", start_x + col_w_1, y, col_w_2, row_h, bg_main, "Price rising but selling detected (Reversal).", clrWhite, f_size, ALIGN_LEFT);
   
   // --- Row 5: SIZE LEGEND HEADER ---
   y += row_h;
   DrawDashCell(PREFIX + "SH", start_x, y, width, row_h, C'128,128,128', "SIZE & INTENSITY (Z-SCORE)", clrWhite, f_size);
   
   // --- Row 6-8: SIZE LEGEND ---
   y += row_h;
   DrawDashCell(PREFIX + "S1_C", start_x, y, col_w_1, row_h, bg_main, "• Standard", clrWhite, f_size);
   DrawDashCell(PREFIX + "S1_T", start_x + col_w_1, y, col_w_2, row_h, bg_main, "2.0σ - 3.0σ (Small Event)", C'128,128,128', f_size, ALIGN_LEFT);
   
   y += row_h;
   DrawDashCell(PREFIX + "S2_C", start_x, y, col_w_1, row_h, bg_main, "● Notice", clrWhite, f_size);
   DrawDashCell(PREFIX + "S2_T", start_x + col_w_1, y, col_w_2, row_h, bg_main, "3.0σ - 4.0σ (Take Notice)", C'0,229,255', f_size, ALIGN_LEFT);
   
   y += row_h;
   DrawDashCell(PREFIX + "S3_C", start_x, y, col_w_1, row_h, bg_main, "⬤ LARGE", clrWhite, f_size);
   DrawDashCell(PREFIX + "S3_T", start_x + col_w_1, y, col_w_2, row_h, bg_main, "> 4.0σ (Black Swan / Rare)", C'255,0,204', f_size, ALIGN_LEFT);
   
   // --- Row 9: LOGS HEADER ---
   y += row_h;
   DrawDashCell(PREFIX + "LH", start_x, y, width, row_h, C'20,20,20', "LAST 5 SIGNALS (24H)", clrYellow, f_size);
   
   // --- Row 10-14: LOG ENTRIES ---
   for(int k=0; k<5; k++)
   {
      y += row_h;
      if(k < found)
      {
         string t_str = TimeToString(logs[k].time, TIME_DATE|TIME_MINUTES);
         // Pine uses specialized formatting, we try to match logic
          // 24H Filter is now applied during collection


         string type_str = "";
         color type_col = clrGray;
         int type_e = logs[k].type;
         
         if(type_e == 1) { 
            type_str = "MOMENTUM BUY"; 
            type_col = col_bull; 
         }
         else if(type_e == 2) { 
            type_str = "MOMENTUM SELL"; 
            type_col = col_bear; 
         }
         else if(type_e == 3) { 
            type_str = "ABSORPTION BUY"; 
            type_col = col_absorb_bu; 
         }
         else if(type_e == 4) { 
            type_str = "ABSORPTION SELL"; 
            type_col = col_absorb_be; 
         }
         
         string val_str = type_str + " (" + DoubleToString(logs[k].z_score, 1) + "σ)";
         
         // Left col: Time
         DrawDashCell(PREFIX + "LogT_" + IntegerToString(k), start_x, y, col_w_1, row_h, C'40,40,40', t_str, clrWhite, f_size-1);
         // Right col: Event
         DrawDashCell(PREFIX + "LogV_" + IntegerToString(k), start_x + col_w_1, y, col_w_2, row_h, type_col, val_str, clrWhite, f_size-1, ALIGN_LEFT);
      }
      else
      {
         // Empty Row
         DrawDashCell(PREFIX + "LogT_" + IntegerToString(k), start_x, y, col_w_1, row_h, bg_main, "", clrWhite, f_size);
         DrawDashCell(PREFIX + "LogV_" + IntegerToString(k), start_x + col_w_1, y, col_w_2, row_h, bg_main, (k==0 && found==0) ? "No recent whale activity..." : "", clrGray, f_size, ALIGN_LEFT);
      }
   }
   
   ChartRedraw();
}


//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
