//+--------------------------------------------------------------------+
//|                        VolumeGapsImbalances_Zeiierman.mq4          |
//|                        Copyright 2025, Zeiierman / Antigravity     |
//|                        https://www.tradingview.com/script/Q7YQQq7g/|
//+--------------------------------------------------------------------+
#property copyright "Copyright © 2025, Zeiierman / Antigravity"
#property link      "https://www.tradingview.com/script/Q7YQQq7g/"
#property version   "1.00"
#property strict
#property indicator_chart_window

//--- Enums
enum EnumPriceType {
    PRICE_CLOSE_ = 0, // Close
    PRICE_HLC3_  = 1, // HLC3
    PRICE_OHLC4_ = 2, // OHLC4
    PRICE_HL2_   = 3  // HL2
};

//--- Inputs
input string             GRP_PROFILE     = "=== Profile Settings ===";
input int                InpLookback     = 200;          // Lookback (Bars)
input int                InpRows         = 50;           // Rows (Bins)
input EnumPriceType      InpSrc          = PRICE_HLC3_;  // Price Source

input string             GRP_STYLE       = "=== Profile Styling ===";
input int                InpWidth        = 100;          // Profile Width (Bars)
input color              InpBullColor    = C'30,144,255';// Bull Color (DodgerBlue)
input color              InpBearColor    = C'255,140,0'; // Bear Color (DarkOrange)
input color              InpZoneColor    = C'0,0,128';   // Zero-Volume Zone (Navy)

input string             GRP_DELTA       = "=== Delta Summary ===";
input int                InpSumSections  = 20;           // Summary Sections
input int                InpSumPanelW    = 40;           // Summary Width (Bars)
input int                InpSumGapX      = 4;            // Gap From Profile (Bars)
input bool               InpShowLabel    = true;         // Show Delta Text

input string             GRP_DELTA_STY   = "=== Delta Styling ===";
input color              InpDeltaPosCol  = C'50,205,50'; // Delta Buy Color (Lime)
input color              InpDeltaNegCol  = C'255,69,0';  // Delta Sell Color (Red-Orange)
input color              InpDeltaNeuBg   = C'211,211,211';// Delta Neutral BG (LightGray)
input color              InpDeltaTxtCol  = clrWhite;     // Delta Text Color
input double             InpDeltaMinFrac = 0.2;          // Delta Min Size (0.0-1.0)

//Forex-Station button template start
input string             button_note1          = "------------------------------"; // ------------------------------
input int                btn_Subwindow         = 0;                 // What window to put the button on
input ENUM_BASE_CORNER   btn_corner            = CORNER_LEFT_UPPER; // chart btn_corner for anchoring
input string             btn_text              = "VGI";             // Display id
input string             btn_Font              = "Arial";           // button font name
input int                btn_FontSize          = 9;                 // btn__font size
input color              btn_text_ON_color     = clrLime;           // ON color when the button is turned on
input color              btn_text_OFF_color    = clrRed;            // OFF color when the button is turned off
input color              btn_background_color  = clrDimGray;        // background color of the button
input color              btn_border_color      = clrBlack;          // border color the button
input int                button_x              = 20;                // Horizontal location
input int                button_y              = 95;                // Vertical location
input int                btn_Width             = 80;                // btn__width
input int                btn_Height            = 20;                // btn__height
input string             UniqueButtonID        = "vgi_btn";         // Unique ID for each button        
input string             button_note2          = "------------------------------"; // ------------------------------

bool show_data, recalc=false;
string IndicatorObjPrefix, buttonId;
//Forex-Station button template end

//--- Global buffers/variables
string PREFIX = "VGI_";
double volume_profile[][2]; // [row][0]=bull, [row][1]=bear
double level_prices[];

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
{
   // Validate inputs
   if(InpRows < 1) return(INIT_PARAMETERS_INCORRECT);
   
   IndicatorDigits(Digits);
   
   IndicatorObjPrefix = "__" + btn_text + "__";
   
   // The leading "_" gives buttonId a *unique* prefix
   buttonId = "_" + UniqueButtonID + IndicatorObjPrefix + "_BT_";
   
   // Load saved button state (default to ON if not found)
   string stateVarName = "VGI_ButtonState_" + Symbol();
   bool savedState = true; // Default ON
   if(GlobalVariableCheck(stateVarName))
   {
      savedState = (GlobalVariableGet(stateVarName) > 0.5);
   }
   
   if (ObjectFind(buttonId)<0) 
      createButton(buttonId, btn_text, btn_Width, btn_Height, btn_Font, btn_FontSize, btn_background_color, btn_border_color, btn_text_ON_color);
   
   ObjectSetInteger(0, buttonId, OBJPROP_YDISTANCE, button_y);
   ObjectSetInteger(0, buttonId, OBJPROP_XDISTANCE, button_x);
   
   // Restore button state
   ObjectSetInteger(0, buttonId, OBJPROP_STATE, savedState);
   if(savedState)
   {
      ObjectSetInteger(0, buttonId, OBJPROP_COLOR, btn_text_ON_color);
   }
   else
   {
      ObjectSetInteger(0, buttonId, OBJPROP_COLOR, btn_text_OFF_color);
      // Clear all objects when OFF
      ObjectsDeleteAll(0, PREFIX);
   }
   
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Create Button                                                    |
//+------------------------------------------------------------------+
void createButton(string buttonID,string buttonText,int width2,int height,string font,int fontSize,color bgColor,color borderColor,color txtColor)
{
      ObjectDelete    (0,buttonID);
      ObjectCreate    (0,buttonID,OBJ_BUTTON,btn_Subwindow,0,0);
      ObjectSetInteger(0,buttonID,OBJPROP_COLOR,txtColor);
      ObjectSetInteger(0,buttonID,OBJPROP_BGCOLOR,bgColor);
      ObjectSetInteger(0,buttonID,OBJPROP_BORDER_COLOR,borderColor);
      ObjectSetInteger(0,buttonID,OBJPROP_BORDER_TYPE,BORDER_RAISED);
      ObjectSetInteger(0,buttonID,OBJPROP_XSIZE,width2);
      ObjectSetInteger(0,buttonID,OBJPROP_YSIZE,height);
      ObjectSetString (0,buttonID,OBJPROP_FONT,font);
      ObjectSetString (0,buttonID,OBJPROP_TEXT,buttonText);
      ObjectSetInteger(0,buttonID,OBJPROP_FONTSIZE,fontSize);
      ObjectSetInteger(0,buttonID,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,buttonID,OBJPROP_CORNER,btn_corner);
      ObjectSetInteger(0,buttonID,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,buttonID,OBJPROP_XDISTANCE,9999);
      ObjectSetInteger(0,buttonID,OBJPROP_YDISTANCE,9999);
      // Upon creation, set the initial state to "true" which is "on"
      ObjectSetInteger(0, buttonId, OBJPROP_STATE, true);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   switch(reason)
   {
      case REASON_PARAMETERS  :
      case REASON_CHARTCHANGE :
      case REASON_RECOMPILE   :
      case REASON_CLOSE       : break;
      default                 : ObjectDelete(buttonId);
   }
   
   ObjectsDeleteAll(0, PREFIX);
}

//+------------------------------------------------------------------+
//| Get Button State                                                 |
//+------------------------------------------------------------------+
string GetButtonState(string whichbutton)
{
      bool selected = ObjectGetInteger(ChartID(),whichbutton,OBJPROP_STATE);
      if (selected)
           { return ("on"); } 
      else { return ("off");}
}

//+------------------------------------------------------------------+
//| Chart Event Handler                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
{
   // Skip unnecessary events
   if(id==CHARTEVENT_OBJECT_CREATE || id==CHARTEVENT_OBJECT_DELETE) return;
   if(id==CHARTEVENT_MOUSE_MOVE    || id==CHARTEVENT_MOUSE_WHEEL)   return;

   static string prevState ="";
   if (id==CHARTEVENT_OBJECT_CLICK && sparam==buttonId)
   {
      string newState = GetButtonState(buttonId);
      string stateVarName = "VGI_ButtonState_" + Symbol();
      
         if (newState!=prevState)
         if (newState=="off")
                  { 
                    ObjectSetInteger(0,buttonId,OBJPROP_COLOR,btn_text_OFF_color);
                    ObjectsDeleteAll(0, PREFIX); // Clear all drawings
                    GlobalVariableSet(stateVarName, 0); // Save OFF state
                    prevState=newState; 
                  }
            else  { 
                    ObjectSetInteger(0,buttonId,OBJPROP_COLOR,btn_text_ON_color);
                    GlobalVariableSet(stateVarName, 1); // Save ON state
                    prevState=newState; 
                  }
            ObjectSetString(ChartID(),buttonId,OBJPROP_TEXT,btn_text);
   }
}

//+------------------------------------------------------------------+
//| 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[])
{
   // Check button state - only draw if ON
   if(GetButtonState(buttonId) == "off") return(rates_total);
   
   if(rates_total < InpLookback) return(0);

   // Ensure arrays are indexed as series (0 = newest)
   ArraySetAsSeries(time, true);
   ArraySetAsSeries(open, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(tick_volume, true);
   
   ObjectsDeleteAll(0, PREFIX);
   
   // 1. Calculate Range (Highest High, Lowest Low over Lookback)
   int start_idx = 0; // Current bar
   int end_idx   = InpLookback;
   
   if(end_idx >= rates_total) end_idx = rates_total - 1;
   
   double hi = -DBL_MAX;
   double lo = DBL_MAX;
   
   for(int i=0; i<InpLookback; i++) {
      if(high[i] > hi) hi = high[i];
      if(low[i] < lo)  lo = low[i];
   }
   
   if(hi == lo) return(rates_total); // Flat line, avoid zero divide
   
   // 2. Build Levels
   ArrayResize(level_prices, InpRows + 1);
   ArrayResize(volume_profile, InpRows);
   ArrayInitialize(volume_profile, 0.0);
   
   double step = (hi - lo) / (double)InpRows;
   for(int i=0; i<=InpRows; i++) {
      level_prices[i] = lo + i * step;
   }
   
   // 3. Accumulate Volume
   for(int i=0; i<InpLookback; i++) {
      double price = 0;
      switch(InpSrc) {
         case PRICE_CLOSE_: price = close[i]; break;
         case PRICE_HLC3_:  price = (high[i]+low[i]+close[i])/3.0; break;
         case PRICE_OHLC4_: price = (open[i]+high[i]+low[i]+close[i])/4.0; break;
         case PRICE_HL2_:   price = (high[i]+low[i])/2.0; break;
         default: price = close[i];
      }
      
      bool is_bull = (close[i] > open[i]);
      double vol = (double)tick_volume[i];
      
      // Find bin
      int bin = (int)((price - lo) / step);
      if(bin < 0) bin = 0;
      if(bin >= InpRows) bin = InpRows - 1;
      
      if(is_bull) volume_profile[bin][0] += vol;
      else        volume_profile[bin][1] += vol;
   }
   
   // 4. Calculate Max Volume
   double max_vol = 0;
   for(int i=0; i<InpRows; i++) {
      double total = volume_profile[i][0] + volume_profile[i][1];
      if(total > max_vol) max_vol = total;
   }
   
   // 5. Draw Profile
   datetime time_current = time[0];
   int period_seconds = PeriodSeconds();
   
   for(int i=0; i<InpRows; i++) {
      double bull = volume_profile[i][0];
      double bear = volume_profile[i][1];
      double total = bull + bear;
      
      if(total == 0) continue;
      
      double norm_v_bars = (max_vol > 0) ? (total / max_vol) * InpWidth : 0;
      if(norm_v_bars <= 0) continue;
      
      double norm_bull_bars = (total > 0) ? (bull / total) * norm_v_bars : 0;
      double norm_bear_bars = norm_v_bars - norm_bull_bars;
      
      datetime t_far_right = time_current + (int)(InpWidth * period_seconds);
      datetime t_start     = t_far_right - (int)(norm_v_bars * period_seconds);
      datetime t_split     = t_start + (int)(norm_bull_bars * period_seconds);
      
      double price_bot = level_prices[i];
      double price_top = level_prices[i+1];
      
      double gap = (price_top - price_bot) * 0.05; 
      price_bot += gap;
      price_top -= gap;
      
      // Draw Bull
      if(norm_bull_bars > 0) {
         CreateRect(PREFIX+"Bull_"+IntegerToString(i), t_start, price_top, t_split, price_bot, InpBullColor, InpBullColor);
      }
      
      // Draw Bear
      if(norm_bear_bars > 0) {
         CreateRect(PREFIX+"Bear_"+IntegerToString(i), t_split, price_top, t_far_right, price_bot, InpBearColor, InpBearColor);
      }
   }
   
   // 6. Zero-Volume Gaps
   int gap_start_idx = -1;
   
   for(int i=0; i<InpRows; i++) {
      double total = volume_profile[i][0] + volume_profile[i][1];
      
      if(total == 0) {
         if(gap_start_idx == -1) gap_start_idx = i;
      } else {
         if(gap_start_idx != -1) {
            DrawGap(gap_start_idx, i-1, time_current, period_seconds, hi, lo, step);
            gap_start_idx = -1;
         }
      }
   }
   if(gap_start_idx != -1) {
      DrawGap(gap_start_idx, InpRows-1, time_current, period_seconds, hi, lo, step);
   }

   // 7. Delta Panel
   int rows_per_sec = InpRows / InpSumSections;
   if(rows_per_sec < 1) rows_per_sec = 1;
   
   datetime t_delta_base = time_current + (int)((InpWidth + InpSumGapX) * period_seconds);
   datetime t_delta_end  = t_delta_base + (int)(InpSumPanelW * period_seconds);
   
   for(int s=0; s<InpSumSections; s++) {
      int start_r = s * rows_per_sec;
      int end_r   = (s+1) * rows_per_sec - 1;
      if(end_r >= InpRows) end_r = InpRows - 1;
      
      double secBull = 0;
      double secBear = 0;
      for(int k=start_r; k<=end_r; k++) {
         secBull += volume_profile[k][0];
         secBear += volume_profile[k][1];
      }
      
      double secTot = secBull + secBear;
      if(secTot <= 0) continue;
      
      double segTop = level_prices[end_r+1];
      double segBot = level_prices[start_r];
      
      double d_gap = (segTop - segBot) * 0.02;
      segTop -= d_gap;
      segBot += d_gap;
      
      // Draw Neutral BG
      CreateRect(PREFIX+"D_BG_"+IntegerToString(s), t_delta_base, segTop, t_delta_end, segBot, InpDeltaNeuBg, InpDeltaNeuBg);
      
      // Delta Calculation
      double deltaSq = (secBull - secBear);
      double deltaPct = deltaSq / secTot * 100.0;
      
      double barLenFrac = 0;
      if(deltaPct != 0) {
          double norm = MathAbs(deltaPct) / 100.0;
          barLenFrac = MathMax(InpDeltaMinFrac, MathMin(1.0, norm));
      }
      
      double barLenBars = InpSumPanelW * barLenFrac;
      datetime t_bar_right = t_delta_base + (int)(barLenBars * period_seconds);
      
      color colDelta = InpDeltaNeuBg;
      if(deltaPct > 0) colDelta = InpDeltaPosCol;
      else if(deltaPct < 0) colDelta = InpDeltaNegCol;
      
      CreateRect(PREFIX+"D_Bar_"+IntegerToString(s), t_delta_base, segTop, t_bar_right, segBot, colDelta, colDelta);
      
      // Text
      if(InpShowLabel) {
         string txt = "D " + DoubleToString(deltaPct, 1) + "%";
         datetime t_text = t_delta_base + (int)((barLenBars/2.0) * period_seconds);
         double p_text = (segTop + segBot) / 2.0;
         
         CreateLabel(PREFIX+"D_Txt_"+IntegerToString(s), t_text, p_text, txt, InpDeltaTxtCol);
      }
   }

   return(rates_total);
}

//+------------------------------------------------------------------+
//| Helper: Draw Gap (Merged)                                        |
//+------------------------------------------------------------------+
void DrawGap(int row_start, int row_end, datetime t_curr, int sec_per_bar, double high_val, double low_val, double step)
{
   double top_p = level_prices[row_end+1];
   double bot_p = level_prices[row_start];
   
   datetime t_l = iTime(NULL, 0, InpLookback); 
   if(t_l == 0) t_l = t_curr - (InpLookback * sec_per_bar);
   
   datetime t_r = t_curr; 
   
   string name = PREFIX + "Gap_" + IntegerToString(row_start);
   CreateRect(name, t_l, top_p, t_r, bot_p, InpZoneColor, InpZoneColor);
}


//+------------------------------------------------------------------+
//| Helper: Create Rectangle                                         |
//+------------------------------------------------------------------+
void CreateRect(string name, datetime t1, double p1, datetime t2, double p2, color bg_clr, color border_clr)
{
   if(ObjectFind(0, name) < 0) {
      ObjectCreate(0, name, OBJ_RECTANGLE, 0, 0, 0, 0, 0);
      ObjectSetInteger(0, name, OBJPROP_BACK, true);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   }
   
   ObjectSetInteger(0, name, OBJPROP_TIME1, t1);
   ObjectSetDouble(0, name, OBJPROP_PRICE1, p1);
   ObjectSetInteger(0, name, OBJPROP_TIME2, t2);
   ObjectSetDouble(0, name, OBJPROP_PRICE2, p2);
   
   ObjectSetInteger(0, name, OBJPROP_COLOR, border_clr);
   ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg_clr); 
   ObjectSetInteger(0, name, OBJPROP_FILL, true);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
}

//+------------------------------------------------------------------+
//| Helper: Create Label                                             |
//+------------------------------------------------------------------+
void CreateLabel(string name, datetime t, double p, string text, color clr)
{
   if(ObjectFind(0, name) < 0) {
      ObjectCreate(0, name, OBJ_TEXT, 0, 0, 0);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_CENTER);
   }
   
   ObjectSetInteger(0, name, OBJPROP_TIME1, t);
   ObjectSetDouble(0, name, OBJPROP_PRICE1, p);
   ObjectSetString(0, name, OBJPROP_TEXT, text);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 8);
}
