//+------------------------------------------------------------------+
//|                          MarketProfile_Ultimate_Premium_PRO.mq5  |
//|                                       Copyright 2026, gala_aka_47|
//|                         https://www.mql5.com/en/users/gala_aka_47|
//+------------------------------------------------------------------+
#property copyright   "Copyright 2026, gala_aka_47"
#property link        "https://www.mql5.com/en/users/gala_aka_47"
#property version     "4.00"
#property description "Market Profile Ultimate PRO — neon design + 3 breakout mechanics:"
#property description "1) Bimodal VA + Multiple POCs  2) Delta Profile (order flow)  3) Composite Memory + Naked POCs"
#property strict

#property indicator_chart_window
#property indicator_plots   6
#property indicator_buffers 6

//--- Developing POC
#property indicator_label1  "Dev POC"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrGold
#property indicator_style1  STYLE_SOLID
#property indicator_width1  2

#property indicator_label2  "Dev POC (2)"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGold
#property indicator_style2  STYLE_SOLID
#property indicator_width2  2

#property indicator_label3  "Dev VAH"
#property indicator_type3   DRAW_LINE
#property indicator_color3  C'0,229,255'
#property indicator_style3  STYLE_DOT
#property indicator_width3  1

#property indicator_label4  "Dev VAH (2)"
#property indicator_type4   DRAW_LINE
#property indicator_color4  C'0,229,255'
#property indicator_style4  STYLE_DOT
#property indicator_width4  1

#property indicator_label5  "Dev VAL"
#property indicator_type5   DRAW_LINE
#property indicator_color5  C'255,64,129'
#property indicator_style5  STYLE_DOT
#property indicator_width5  1

#property indicator_label6  "Dev VAL (2)"
#property indicator_type6   DRAW_LINE
#property indicator_color6  C'255,64,129'
#property indicator_style6  STYLE_DOT
#property indicator_width6  1

//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
enum ENUM_SESSION_TYPE
  {
   SESSION_DAILY,
   SESSION_WEEKLY,
   SESSION_MONTHLY,
   SESSION_INTRADAY
  };

enum ENUM_PROFILE_MODE
  {
   PROFILE_TPO,
   PROFILE_VOLUME,
   PROFILE_HYBRID
  };

enum ENUM_THEME
  {
   THEME_NEON_DARK,
   THEME_SUNSET,
   THEME_OCEAN,
   THEME_MATRIX,
   THEME_ROYAL,
   THEME_CUSTOM
  };

//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input group "=== 🎨 Theme ==="
input ENUM_THEME          InpTheme             = THEME_NEON_DARK;
input bool                InpGlowEffect        = true;
input int                 InpGlowIntensity     = 3;
input bool                InpShowInfoPanel     = false;
input int                 InpPanelCorner       = CORNER_RIGHT_UPPER;
input int                 InpPanelX            = 20;
input int                 InpPanelY            = 40;

input group "=== Profile Settings ==="
input ENUM_SESSION_TYPE   InpSession           = SESSION_DAILY;
input ENUM_PROFILE_MODE   InpProfileMode       = PROFILE_HYBRID;
input int                 InpIntradayHours     = 4;
input int                 InpSessionsToShow    = 5;
input double              InpValueAreaPercent  = 70.0;
input int                 InpProfileWidth      = 35;

//+------------------------------------------------------------------+
//| Bimodal VA Detection inputs                                      |
//+------------------------------------------------------------------+
input group "=== 🔥 Bimodal VA Detection (Idea #1) ==="
input bool                InpEnableBimodal     = true;              // Detect split/bimodal distributions
input double              InpBimodalDipRatio   = 0.55;              // Valley must be <= N * peak height (0.4-0.7)
input int                 InpMinPeakDistance   = 8;                 // Min levels between peaks
input double              InpSecondaryPOCMinPct= 35.0;              // Secondary POC min % of primary

//+------------------------------------------------------------------+
//| Delta Profile inputs                                             |
//+------------------------------------------------------------------+
input group "=== 🔥 Delta Profile (Idea #2) ==="
input bool                InpEnableDelta       = true;              // Tint bars by order flow delta
input bool                InpUseRealTicks      = true;              // Use CopyTicks (exchange data)
input double              InpAbsorptionThresh  = 0.4;               // Absorption detection sensitivity

//+------------------------------------------------------------------+
//| Composite Memory inputs                                          |
//+------------------------------------------------------------------+
input group "=== 🔥 Composite Memory (Idea #3) ==="
input bool                InpEnableComposite   = true;              // Multi-session composite profile
input int                 InpCompositeSessions = 20;                // Sessions in composite memory
input double              InpDecayFactor       = 0.92;              // Exponential decay (0.8-0.98)
input bool                InpShowHVN_LVN       = true;              // Mark High/Low Volume Nodes
input bool                InpShowNakedPOCs     = true;              // Untested past POCs
input int                 InpMaxNakedPOCs      = 10;                // Max naked POCs shown

//+------------------------------------------------------------------+
//| Price Resolution inputs                                          |
//+------------------------------------------------------------------+
input group "=== Price Resolution ==="
input int                 InpPointMultiplier   = 0;
input int                 InpMaxPriceLevels    = 300;

//+------------------------------------------------------------------+
//| Custom Colors inputs (only for THEME_CUSTOM)                     |
//+------------------------------------------------------------------+
input group "=== 🎨 Custom Colors (only for THEME_CUSTOM) ==="
input color               InpCustomProfile     = clrSteelBlue;
input color               InpCustomVA          = clrDarkOrange;
input color               InpCustomPOC         = clrGold;
input color               InpCustomSP          = clrMagenta;
input color               InpCustomBG          = C'15,20,35';

//+------------------------------------------------------------------+
//| Visualization inputs                                             |
//+------------------------------------------------------------------+
input group "=== Visualization ==="
input bool                InpShowSinglePrints  = true;
input bool                InpShowPOCLine       = true;
input bool                InpShowVALines       = true;
input bool                InpShowProfileLabels = true;
input bool                InpShowSessionDivider= true;

//+------------------------------------------------------------------+
//| Developing Levels inputs                                         |
//+------------------------------------------------------------------+
input group "=== Developing Levels ==="
input bool                InpEnableDevPOC      = true;
input bool                InpEnableDevVA       = true;

//+------------------------------------------------------------------+
//| Rays inputs                                                      |
//+------------------------------------------------------------------+
input group "=== Rays ==="
input bool                InpShowPOCRays       = true;
input bool                InpShowVARays        = false;
input bool                InpStopRaysOnCross   = true;

//+------------------------------------------------------------------+
//| Alerts inputs                                                    |
//+------------------------------------------------------------------+
input group "=== Alerts ==="
input bool                InpAlertOnPOCCross   = false;
input bool                InpAlertOnVACross    = false;
input bool                InpAlertOnNakedTest  = false;              // Alert on naked POC test
input bool                InpAlertOnAbsorption = false;              // Alert on absorption detected
input bool                InpAlertPopup        = true;
input bool                InpAlertPush         = false;
input bool                InpAlertEmail        = false;

//+------------------------------------------------------------------+
//| Performance inputs                                               |
//+------------------------------------------------------------------+
input group "=== Performance ==="
input int                 InpThrottleMs        = 500;
input bool                InpDisableOnWrongTF  = false;

//+------------------------------------------------------------------+
//| Indicator buffers                                                |
//+------------------------------------------------------------------+
double DevPOC_1[], DevPOC_2[];
double DevVAH_1[], DevVAH_2[];
double DevVAL_1[], DevVAL_2[];

//+------------------------------------------------------------------+
//| Theme palette structure                                          |
//+------------------------------------------------------------------+
struct ThemePalette
  {
   color             profile_base;
   color             profile_accent;
   color             va_color;
   color             va_accent;
   color             poc_color;
   color             poc_glow;
   color             single_print;
   color             panel_bg;
   color             panel_border;
   color             panel_text;
   color             panel_accent;
   color             divider;
   color             label_shadow;
   //--- Extended palette for new features
   color             secondary_poc;
   color             dead_zone;
   color             delta_buy;
   color             delta_sell;
   color             absorption;
   color             hvn_color;
   color             lvn_color;
   color             naked_poc;
   color             composite_bg;
  };

ThemePalette g_palette;

//+------------------------------------------------------------------+
//| Naked POC record                                                 |
//+------------------------------------------------------------------+
struct NakedPOC
  {
   double            price;
   datetime          created;
   int               age_sessions;
   bool              tested;
  };

//+------------------------------------------------------------------+
//| Session profile container                                        |
//+------------------------------------------------------------------+
struct SessionProfile
  {
   datetime          start_time;
   datetime          end_time;
   int               start_bar;
   int               end_bar;
   double            high;
   double            low;
   double            poc_price;
   double            secondary_poc;     // Bimodal: secondary POC (0 if unimodal)
   double            dead_zone_top;     // Bimodal: dead zone bounds
   double            dead_zone_bot;
   bool              is_bimodal;
   double            vah_price;
   double            val_price;
   double            vah2_price;        // Bimodal: second VA
   double            val2_price;
   double            total_activity;
   double            delta_total;       // Delta: net buy-sell
   double            delta_at_poc;      // Delta: delta at POC price
   bool              absorption_poc;    // Delta: absorption detected at POC
  };

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
string   g_prefix         = "MPU_";
string   g_suffix         = "";
double   g_tick_size      = 0;
int      g_point_mult     = 1;
bool     g_init_failed    = false;
datetime g_last_redraw    = 0;
int      g_sessions_calc  = 0;
double   g_last_poc       = 0;
double   g_last_vah       = 0;
double   g_last_val       = 0;
datetime g_last_alert_bar = 0;

// Composite memory
double   g_composite_prices[];
double   g_composite_activity[];
int      g_composite_levels   = 0;
double   g_composite_low      = 0;
double   g_composite_high     = 0;
int      g_composite_sessions = 1;
bool     g_show_naked_pocs    = false;

// Naked POCs tracking
NakedPOC g_naked_pocs[];
int      g_naked_count = 0;

//+------------------------------------------------------------------+
//| OnInit                                                           |
//+------------------------------------------------------------------+
int OnInit()
  {
   g_init_failed = false;
   g_sessions_calc = MathMax(1, InpSessionsToShow);
   g_composite_sessions = MathMax(1, InpCompositeSessions);
   g_show_naked_pocs = InpShowNakedPOCs;

   ApplyTheme();

   if(!ValidateTimeframe() && InpDisableOnWrongTF)
     {
      g_init_failed = true;
      return(INIT_FAILED);
     }

   switch(InpSession)
     {
      case SESSION_DAILY:
         g_suffix = "D";
         break;
      case SESSION_WEEKLY:
         g_suffix = "W";
         break;
      case SESSION_MONTHLY:
         g_suffix = "M";
         break;
      case SESSION_INTRADAY:
         g_suffix = "I";
         break;
     }

   CalculatePointMultiplier();
   g_tick_size = _Point * g_point_mult;

   SetIndexBuffer(0, DevPOC_1, INDICATOR_DATA);
   SetIndexBuffer(1, DevPOC_2, INDICATOR_DATA);
   SetIndexBuffer(2, DevVAH_1, INDICATOR_DATA);
   SetIndexBuffer(3, DevVAH_2, INDICATOR_DATA);
   SetIndexBuffer(4, DevVAL_1, INDICATOR_DATA);
   SetIndexBuffer(5, DevVAL_2, INDICATOR_DATA);

   for(int i = 0; i < 6; i++)
     {
      PlotIndexSetDouble(i, PLOT_EMPTY_VALUE, EMPTY_VALUE);
      PlotIndexSetInteger(i, PLOT_DRAW_BEGIN, 0);
     }

   PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, g_palette.poc_color);
   PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, g_palette.poc_color);
   PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, g_palette.va_accent);
   PlotIndexSetInteger(3, PLOT_LINE_COLOR, 0, g_palette.va_accent);
   PlotIndexSetInteger(4, PLOT_LINE_COLOR, 0, g_palette.va_color);
   PlotIndexSetInteger(5, PLOT_LINE_COLOR, 0, g_palette.va_color);

   if(!InpEnableDevPOC)
     {
      PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, clrNONE);
      PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0, clrNONE);
     }
   if(!InpEnableDevVA)
     {
      PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0, clrNONE);
      PlotIndexSetInteger(3, PLOT_LINE_COLOR, 0, clrNONE);
      PlotIndexSetInteger(4, PLOT_LINE_COLOR, 0, clrNONE);
      PlotIndexSetInteger(5, PLOT_LINE_COLOR, 0, clrNONE);
     }

   string features = "";
   if(InpEnableBimodal)
      features += " · Bimodal";
   if(InpEnableDelta)
      features += " · Delta";
   if(InpEnableComposite)
      features += " · Memory";

   string short_name = StringFormat("◆ MP Ultimate PRO [%s · %.0f%% VA%s]",
                                    SessionName(InpSession),
                                    InpValueAreaPercent,
                                    features);
   IndicatorSetString(INDICATOR_SHORTNAME, short_name);
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

   if(InpShowInfoPanel)
      CreateInfoPanel();

   ArrayResize(g_naked_pocs, 0);
   g_naked_count = 0;

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, g_prefix);
   ChartRedraw(0);
  }

//+------------------------------------------------------------------+
//| Apply selected theme                                             |
//+------------------------------------------------------------------+
void ApplyTheme()
  {
   switch(InpTheme)
     {
      case THEME_NEON_DARK:
         g_palette.profile_base   = C'72,96,140';
         g_palette.profile_accent = C'120,160,220';
         g_palette.va_color       = C'0,188,212';
         g_palette.va_accent      = C'0,229,255';
         g_palette.poc_color      = C'255,193,7';
         g_palette.poc_glow       = C'255,152,0';
         g_palette.single_print   = C'255,64,129';
         g_palette.panel_bg       = C'18,24,38';
         g_palette.panel_border   = C'0,229,255';
         g_palette.panel_text     = C'220,230,240';
         g_palette.panel_accent   = C'255,193,7';
         g_palette.divider        = C'40,60,90';
         g_palette.label_shadow   = C'5,10,20';
         g_palette.secondary_poc  = C'255,140,0';
         g_palette.dead_zone      = C'60,30,80';
         g_palette.delta_buy      = C'0,230,118';
         g_palette.delta_sell     = C'255,82,82';
         g_palette.absorption     = C'255,64,129';
         g_palette.hvn_color      = C'124,77,255';
         g_palette.lvn_color      = C'255,241,118';
         g_palette.naked_poc      = C'255,109,176';
         g_palette.composite_bg   = C'30,40,65';
         break;

      case THEME_SUNSET:
         g_palette.profile_base   = C'180,80,120';
         g_palette.profile_accent = C'240,120,80';
         g_palette.va_color       = C'255,140,60';
         g_palette.va_accent      = C'255,200,80';
         g_palette.poc_color      = C'255,235,130';
         g_palette.poc_glow       = C'255,180,60';
         g_palette.single_print   = C'220,80,180';
         g_palette.panel_bg       = C'40,15,30';
         g_palette.panel_border   = C'255,140,60';
         g_palette.panel_text     = C'255,220,200';
         g_palette.panel_accent   = C'255,235,130';
         g_palette.divider        = C'120,40,60';
         g_palette.label_shadow   = C'20,5,15';
         g_palette.secondary_poc  = C'240,120,80';
         g_palette.dead_zone      = C'80,30,40';
         g_palette.delta_buy      = C'180,220,100';
         g_palette.delta_sell     = C'220,60,80';
         g_palette.absorption     = C'255,80,180';
         g_palette.hvn_color      = C'200,100,160';
         g_palette.lvn_color      = C'255,220,140';
         g_palette.naked_poc      = C'255,180,60';
         g_palette.composite_bg   = C'60,25,40';
         break;

      case THEME_OCEAN:
         g_palette.profile_base   = C'40,80,120';
         g_palette.profile_accent = C'70,140,200';
         g_palette.va_color       = C'0,150,180';
         g_palette.va_accent      = C'80,220,220';
         g_palette.poc_color      = C'255,255,255';
         g_palette.poc_glow       = C'180,240,255';
         g_palette.single_print   = C'255,100,100';
         g_palette.panel_bg       = C'10,25,45';
         g_palette.panel_border   = C'80,220,220';
         g_palette.panel_text     = C'200,230,240';
         g_palette.panel_accent   = C'255,255,255';
         g_palette.divider        = C'30,60,90';
         g_palette.label_shadow   = C'5,15,25';
         g_palette.secondary_poc  = C'180,240,255';
         g_palette.dead_zone      = C'15,45,70';
         g_palette.delta_buy      = C'0,220,180';
         g_palette.delta_sell     = C'255,120,100';
         g_palette.absorption     = C'255,180,100';
         g_palette.hvn_color      = C'140,180,255';
         g_palette.lvn_color      = C'200,220,240';
         g_palette.naked_poc      = C'150,240,240';
         g_palette.composite_bg   = C'15,40,65';
         break;

      case THEME_MATRIX:
         g_palette.profile_base   = C'20,80,40';
         g_palette.profile_accent = C'60,180,80';
         g_palette.va_color       = C'0,200,100';
         g_palette.va_accent      = C'80,255,140';
         g_palette.poc_color      = C'180,255,180';
         g_palette.poc_glow       = C'100,255,100';
         g_palette.single_print   = C'255,200,60';
         g_palette.panel_bg       = C'5,20,10';
         g_palette.panel_border   = C'80,255,140';
         g_palette.panel_text     = C'180,255,200';
         g_palette.panel_accent   = C'255,255,255';
         g_palette.divider        = C'20,60,30';
         g_palette.label_shadow   = C'0,10,5';
         g_palette.secondary_poc  = C'120,255,100';
         g_palette.dead_zone      = C'10,30,15';
         g_palette.delta_buy      = C'100,255,140';
         g_palette.delta_sell     = C'255,80,80';
         g_palette.absorption     = C'255,200,60';
         g_palette.hvn_color      = C'60,220,120';
         g_palette.lvn_color      = C'200,255,200';
         g_palette.naked_poc      = C'180,255,100';
         g_palette.composite_bg   = C'10,35,20';
         break;

      case THEME_ROYAL:
         g_palette.profile_base   = C'80,50,140';
         g_palette.profile_accent = C'140,90,220';
         g_palette.va_color       = C'180,140,255';
         g_palette.va_accent      = C'220,180,255';
         g_palette.poc_color      = C'255,215,0';
         g_palette.poc_glow       = C'255,180,60';
         g_palette.single_print   = C'255,100,200';
         g_palette.panel_bg       = C'25,15,40';
         g_palette.panel_border   = C'255,215,0';
         g_palette.panel_text     = C'230,220,255';
         g_palette.panel_accent   = C'255,215,0';
         g_palette.divider        = C'60,40,100';
         g_palette.label_shadow   = C'10,5,20';
         g_palette.secondary_poc  = C'220,150,60';
         g_palette.dead_zone      = C'40,25,65';
         g_palette.delta_buy      = C'160,220,80';
         g_palette.delta_sell     = C'220,80,140';
         g_palette.absorption     = C'255,100,200';
         g_palette.hvn_color      = C'200,150,255';
         g_palette.lvn_color      = C'255,220,180';
         g_palette.naked_poc      = C'255,180,100';
         g_palette.composite_bg   = C'35,20,60';
         break;

      case THEME_CUSTOM:
      default:
         g_palette.profile_base   = InpCustomProfile;
         g_palette.profile_accent = LightenColor(InpCustomProfile, 1.4);
         g_palette.va_color       = InpCustomVA;
         g_palette.va_accent      = LightenColor(InpCustomVA, 1.3);
         g_palette.poc_color      = InpCustomPOC;
         g_palette.poc_glow       = LightenColor(InpCustomPOC, 0.7);
         g_palette.single_print   = InpCustomSP;
         g_palette.panel_bg       = InpCustomBG;
         g_palette.panel_border   = InpCustomPOC;
         g_palette.panel_text     = C'220,230,240';
         g_palette.panel_accent   = InpCustomPOC;
         g_palette.divider        = C'60,60,80';
         g_palette.label_shadow   = C'0,0,0';
         g_palette.secondary_poc  = LightenColor(InpCustomPOC, 0.8);
         g_palette.dead_zone      = C'40,40,55';
         g_palette.delta_buy      = C'0,220,120';
         g_palette.delta_sell     = C'240,80,80';
         g_palette.absorption     = InpCustomSP;
         g_palette.hvn_color      = C'140,100,220';
         g_palette.lvn_color      = C'240,220,140';
         g_palette.naked_poc      = C'240,120,180';
         g_palette.composite_bg   = C'30,35,55';
         break;
     }
  }

//+------------------------------------------------------------------+
//| Timeframe validation                                             |
//+------------------------------------------------------------------+
bool ValidateTimeframe()
  {
   int tf = PeriodSeconds();
   bool ok = false;
   string msg = "";

   switch(InpSession)
     {
      case SESSION_DAILY:
         ok = (tf >= PeriodSeconds(PERIOD_M1) && tf <= PeriodSeconds(PERIOD_M30));
         msg = "Daily requires M1-M30";
         break;
      case SESSION_WEEKLY:
         ok = (tf >= PeriodSeconds(PERIOD_M15) && tf <= PeriodSeconds(PERIOD_H4));
         msg = "Weekly requires M15-H4";
         break;
      case SESSION_MONTHLY:
         ok = (tf >= PeriodSeconds(PERIOD_H1) && tf <= PeriodSeconds(PERIOD_D1));
         msg = "Monthly requires H1-D1";
         break;
      case SESSION_INTRADAY:
         ok = (tf >= PeriodSeconds(PERIOD_M1) && tf <= PeriodSeconds(PERIOD_M15));
         msg = "Intraday requires M1-M15";
         break;
     }

   if(!ok)
      Print("⚠ ", msg, ". Current: ", EnumToString(_Period));
   return ok;
  }

//+------------------------------------------------------------------+
//| Calculate adaptive point multiplier                              |
//+------------------------------------------------------------------+
void CalculatePointMultiplier()
  {
   if(InpPointMultiplier > 0)
     {
      g_point_mult = InpPointMultiplier;
      return;
     }

   double atr = 0;
   int h = iATR(_Symbol, _Period, 14);
   if(h != INVALID_HANDLE)
     {
      double buf[1];
      if(CopyBuffer(h, 0, 0, 1, buf) == 1)
         atr = buf[0];
      IndicatorRelease(h);
     }
   if(atr <= 0)
      atr = SymbolInfoDouble(_Symbol, SYMBOL_BID) * 0.005;

   double raw = atr / _Point;
   g_point_mult = (int)MathMax(1, MathRound(raw / 150.0));
  }

//+------------------------------------------------------------------+
//| OnCalculate                                                      |
//+------------------------------------------------------------------+
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(g_init_failed)
      return 0;
   if(rates_total < 10)
      return 0;

   if(prev_calculated > 0 && rates_total == prev_calculated)
     {
      uint now = GetTickCount();
      if(now - g_last_redraw < (uint)InpThrottleMs)
         return rates_total;
      g_last_redraw = now;
     }

   ArraySetAsSeries(time, true);
   ArraySetAsSeries(high, true);
   ArraySetAsSeries(low, true);
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(tick_volume, true);

   bool full_rebuild = (prev_calculated == 0 || rates_total - prev_calculated > 1);
   if(full_rebuild)
     {
      DeleteProfileObjects();
      ArrayInitialize(DevPOC_1, EMPTY_VALUE);
      ArrayInitialize(DevPOC_2, EMPTY_VALUE);
      ArrayInitialize(DevVAH_1, EMPTY_VALUE);
      ArrayInitialize(DevVAH_2, EMPTY_VALUE);
      ArrayInitialize(DevVAL_1, EMPTY_VALUE);
      ArrayInitialize(DevVAL_2, EMPTY_VALUE);
     }

   int session_end_bar = 0;
   int session_start_bar = 0;

// Allocate extra sessions for composite memory
   int total_sessions_to_compute = g_sessions_calc;
   if(InpEnableComposite || g_show_naked_pocs)
      total_sessions_to_compute = MathMax(g_sessions_calc, g_composite_sessions);

   SessionProfile profiles[];
   ArrayResize(profiles, total_sessions_to_compute);

   int actual_built = 0;
   for(int s = 0; s < total_sessions_to_compute; s++)
     {
      if(!FindSessionBounds(time, rates_total, session_end_bar, session_start_bar))
         break;

      profiles[s].start_bar  = session_start_bar;
      profiles[s].end_bar    = session_end_bar;
      profiles[s].start_time = time[session_start_bar];
      profiles[s].end_time   = (session_end_bar > 0) ? time[session_end_bar - 1] : time[0];

      // Only draw the visible sessions; all sessions contribute to composite
      bool draw_visible = (s < g_sessions_calc);
      BuildSessionProfile(profiles[s], high, low, close, tick_volume, time, s, draw_visible);

      if(InpShowSessionDivider && s > 0 && draw_visible)
         DrawSessionDivider(profiles[s].start_time, s);

      actual_built++;
      session_end_bar = session_start_bar + 1;
      if(session_end_bar >= rates_total)
         break;
     }

   if((InpEnableDevPOC || InpEnableDevVA) && g_sessions_calc > 0 && actual_built > 0)
      CalculateDevelopingLevels(profiles[0], high, low, close, tick_volume, time, rates_total);

//=== IDEA #3: Composite Memory ===
   if(actual_built > 1)
     {
      if(InpEnableComposite)
        {
         BuildCompositeProfile(profiles, actual_built);
         if(InpShowHVN_LVN)
            DrawHVN_LVN(profiles[0]);
        }

      if(g_show_naked_pocs)
         TrackAndDrawNakedPOCs(profiles, actual_built, high, low, close, time);
     }

   if(actual_built > 0)
     {
      CheckAlerts(profiles[0], close[0], time[0]);
      if(InpShowInfoPanel)
         UpdateInfoPanel(profiles[0], close[0]);
     }

   ChartRedraw(0);
   return rates_total;
  }

//+------------------------------------------------------------------+
//| Delete profile objects but keep panel                            |
//+------------------------------------------------------------------+
void DeleteProfileObjects()
  {
   int total = ObjectsTotal(0, 0, -1);
   for(int i = total - 1; i >= 0; i--)
     {
      string name = ObjectName(0, i, 0, -1);
      if(StringFind(name, g_prefix) == 0 && StringFind(name, g_prefix + "PANEL") != 0)
         ObjectDelete(0, name);
     }
  }

//+------------------------------------------------------------------+
//| Find session bounds                                              |
//+------------------------------------------------------------------+
bool FindSessionBounds(const datetime &time[], const int rates_total,
                       const int search_from_bar, int &session_start_bar)
  {
   if(search_from_bar >= rates_total)
      return false;

   datetime t_ref = time[search_from_bar];
   MqlDateTime dt_ref;
   TimeToStruct(t_ref, dt_ref);

   for(int bar = search_from_bar; bar < rates_total - 1; bar++)
     {
      datetime t_cur = time[bar];
      datetime t_prev = time[bar + 1];
      MqlDateTime dt_cur, dt_prev;
      TimeToStruct(t_cur, dt_cur);
      TimeToStruct(t_prev, dt_prev);

      bool boundary = false;
      switch(InpSession)
        {
         case SESSION_DAILY:
            boundary = (dt_cur.day != dt_prev.day ||
                        dt_cur.mon != dt_prev.mon ||
                        dt_cur.year != dt_prev.year);
            if(bar > search_from_bar && boundary)
              {
               session_start_bar = bar;
               return true;
              }
            break;
         case SESSION_WEEKLY:
            if(bar > search_from_bar && dt_cur.day_of_week < dt_prev.day_of_week)
              {
               session_start_bar = bar;
               return true;
              }
            break;
         case SESSION_MONTHLY:
            if(bar > search_from_bar &&
               (dt_cur.mon != dt_prev.mon || dt_cur.year != dt_prev.year))
              {
               session_start_bar = bar;
               return true;
              }
            break;
         case SESSION_INTRADAY:
           {
            int hours_diff = (int)((t_ref - t_cur) / 3600);
            if(hours_diff >= InpIntradayHours)
              {
               session_start_bar = bar;
               return true;
              }
            break;
           }
        }
     }

   session_start_bar = rates_total - 1;
   return true;
  }

//+------------------------------------------------------------------+
//| Build session profile                                            |
//+------------------------------------------------------------------+
void BuildSessionProfile(SessionProfile &prof,
                         const double &high[], const double &low[], const double &close[],
                         const long &tick_volume[], const datetime &time[],
                         const int session_index, const bool draw_visuals)
  {
   double s_high = -DBL_MAX, s_low = DBL_MAX;
   for(int b = prof.end_bar; b <= prof.start_bar; b++)
     {
      if(high[b] > s_high)
         s_high = high[b];
      if(low[b]  < s_low)
         s_low  = low[b];
     }
   prof.high = s_high;
   prof.low  = s_low;
   if(s_high <= s_low)
      return;

   s_high = MathCeil(s_high / g_tick_size) * g_tick_size;
   s_low  = MathFloor(s_low / g_tick_size) * g_tick_size;

   int levels = (int)MathRound((s_high - s_low) / g_tick_size) + 1;
   if(levels > InpMaxPriceLevels)
      levels = InpMaxPriceLevels;
   if(levels < 2)
      return;

   double activity[], prices[];
   ArrayResize(activity, levels);
   ArrayResize(prices, levels);
   ArrayInitialize(activity, 0.0);

   for(int i = 0; i < levels; i++)
      prices[i] = s_low + i * g_tick_size;

   double total = 0;
   for(int b = prof.end_bar; b <= prof.start_bar; b++)
     {
      double weight = 1.0;
      if(InpProfileMode == PROFILE_VOLUME)
         weight = (double)tick_volume[b];
      else
         if(InpProfileMode == PROFILE_HYBRID)
            weight = 1.0 + MathLog(1.0 + (double)tick_volume[b]);

      int lo_idx = (int)MathMax(0, MathRound((low[b] - s_low) / g_tick_size));
      int hi_idx = (int)MathMin(levels - 1, MathRound((high[b] - s_low) / g_tick_size));
      for(int i = lo_idx; i <= hi_idx; i++)
        {
         activity[i] += weight;
         total += weight;
        }
     }
   prof.total_activity = total;
   if(total <= 0)
      return;

// Primary POC
   int poc_idx = 0;
   double poc_val = activity[0];
   for(int i = 1; i < levels; i++)
      if(activity[i] > poc_val)
        {
         poc_val = activity[i];
         poc_idx = i;
        }
   prof.poc_price = prices[poc_idx];

// Value Area (pairwise expansion)
   double target = total * (InpValueAreaPercent / 100.0);
   double va_sum = activity[poc_idx];
   int va_hi = poc_idx, va_lo = poc_idx;

   while(va_sum < target && (va_hi < levels - 1 || va_lo > 0))
     {
      double up_pair = 0, dn_pair = 0;
      if(va_hi + 1 < levels)
         up_pair += activity[va_hi + 1];
      if(va_hi + 2 < levels)
         up_pair += activity[va_hi + 2];
      if(va_lo - 1 >= 0)
         dn_pair += activity[va_lo - 1];
      if(va_lo - 2 >= 0)
         dn_pair += activity[va_lo - 2];

      if(up_pair >= dn_pair && va_hi < levels - 1)
        {
         va_hi++;
         va_sum += activity[va_hi];
         if(va_hi < levels - 1 && va_sum < target)
           { va_hi++; va_sum += activity[va_hi]; }
        }
      else
         if(va_lo > 0)
           {
            va_lo--;
            va_sum += activity[va_lo];
            if(va_lo > 0 && va_sum < target)
              { va_lo--; va_sum += activity[va_lo]; }
           }
         else
            break;
     }
   prof.vah_price = prices[va_hi];
   prof.val_price = prices[va_lo];

//=== IDEA #1: Bimodal Detection ===
   prof.is_bimodal = false;
   prof.secondary_poc = 0;
   prof.dead_zone_top = 0;
   prof.dead_zone_bot = 0;
   prof.vah2_price = 0;
   prof.val2_price = 0;

   if(InpEnableBimodal)
      DetectBimodal(prof, prices, activity, levels, poc_idx, poc_val, total);

//=== IDEA #2: Delta Profile — compute only, render later ===
   prof.delta_total = 0;
   prof.delta_at_poc = 0;
   prof.absorption_poc = false;

   double delta_arr[];
   bool have_delta = false;

   if(InpEnableDelta && draw_visuals)
     {
      ComputeDeltaProfile(prof, prices, levels, delta_arr, tick_volume, time);

      if(ArraySize(delta_arr) == levels)
        {
         have_delta = true;
         for(int i = 0; i < levels; i++)
            prof.delta_total += delta_arr[i];

         prof.delta_at_poc = delta_arr[poc_idx];

         double delta_magnitude = MathAbs(prof.delta_at_poc);
         double activity_here = activity[poc_idx];
         if(activity_here > 0 && delta_magnitude / activity_here > InpAbsorptionThresh)
            prof.absorption_poc = true;
        }
     }

// Standard visuals
   if(draw_visuals)
     {
      // 1. Main histogram first — creates bar objects
      DrawPremiumHistogram(prof, prices, activity, levels, poc_val, va_hi, va_lo,
                           time, session_index);

      // 2. Overlay delta tint on those bars — modifies colors in place
      if(have_delta)
         ApplyDeltaTint(prof, prices, delta_arr, levels, session_index);

      if(prof.is_bimodal)
         DrawBimodalFeatures(prof, session_index);

      if(InpShowPOCLine)
         DrawGlowLine(prof, prof.poc_price, "POC", g_palette.poc_color,
                      g_palette.poc_glow, STYLE_SOLID, 2, session_index, true);
      if(InpShowVALines)
        {
         DrawGlowLine(prof, prof.vah_price, "VAH", g_palette.va_accent,
                      g_palette.va_color, STYLE_DASH, 1, session_index, false);
         DrawGlowLine(prof, prof.val_price, "VAL", g_palette.va_accent,
                      g_palette.va_color, STYLE_DASH, 1, session_index, false);
        }

      if(InpShowSinglePrints)
         DrawSinglePrints(prof, prices, activity, levels, time, session_index);

      if(session_index == 0)
        {
         if(InpShowPOCRays)
            DrawRay(prof, prof.poc_price, "POC_RAY", g_palette.poc_color, time);
         if(InpShowVARays)
           {
            DrawRay(prof, prof.vah_price, "VAH_RAY", g_palette.va_accent, time);
            DrawRay(prof, prof.val_price, "VAL_RAY", g_palette.va_color, time);
           }
        }

      if(InpShowProfileLabels)
         DrawPremiumLabels(prof, session_index);
     }
  }

//+------------------------------------------------------------------+
//| IDEA #1: Bimodal detection via peak finding                      |
//+------------------------------------------------------------------+
void DetectBimodal(SessionProfile &prof, const double &prices[],
                   const double &activity[], const int levels,
                   const int primary_poc_idx, const double primary_poc_val,
                   const double total)
  {
// Find all local maxima with prominence >= threshold
   int peaks[];
   double peak_vals[];
   ArrayResize(peaks, 0);
   ArrayResize(peak_vals, 0);

   double min_peak_height = primary_poc_val * (InpSecondaryPOCMinPct / 100.0);

   for(int i = 1; i < levels - 1; i++)
     {
      // Local max check with a wider neighborhood
      bool is_peak = true;
      int check_range = MathMax(2, InpMinPeakDistance / 2);
      for(int d = 1; d <= check_range; d++)
        {
         int left = i - d;
         int right = i + d;
         if(left >= 0 && activity[left] >= activity[i])
           {
            is_peak = false;
            break;
           }
         if(right < levels && activity[right] > activity[i])
           {
            is_peak = false;
            break;
           }
        }
      if(is_peak && activity[i] >= min_peak_height)
        {
         int sz = ArraySize(peaks);
         ArrayResize(peaks, sz + 1);
         ArrayResize(peak_vals, sz + 1);
         peaks[sz] = i;
         peak_vals[sz] = activity[i];
        }
     }

   int n_peaks = ArraySize(peaks);
   if(n_peaks < 2)
      return;

// Find best secondary peak (highest, far enough from primary)
   int secondary_idx = -1;
   double secondary_val = 0;
   for(int p = 0; p < n_peaks; p++)
     {
      if(MathAbs(peaks[p] - primary_poc_idx) < InpMinPeakDistance)
         continue;
      if(peak_vals[p] > secondary_val)
        {
         secondary_val = peak_vals[p];
         secondary_idx = peaks[p];
        }
     }

   if(secondary_idx < 0)
      return;

// Check valley depth between peaks
   int lo = MathMin(primary_poc_idx, secondary_idx);
   int hi = MathMax(primary_poc_idx, secondary_idx);

   double valley_min = DBL_MAX;
   int valley_idx = lo;
   for(int i = lo + 1; i < hi; i++)
     {
      if(activity[i] < valley_min)
        {
         valley_min = activity[i];
         valley_idx = i;
        }
     }

   double min_peak = MathMin(activity[primary_poc_idx], secondary_val);
   if(valley_min > min_peak * InpBimodalDipRatio)
      return; // Not deep enough

// Bimodal confirmed
   prof.is_bimodal = true;
   prof.secondary_poc = prices[secondary_idx];

// Dead zone: where activity < 50% of valley-to-peak midpoint
   double dz_threshold = valley_min + (min_peak - valley_min) * 0.25;
   int dz_lo = valley_idx, dz_hi = valley_idx;
   while(dz_lo > lo && activity[dz_lo] < dz_threshold)
      dz_lo--;
   while(dz_hi < hi && activity[dz_hi] < dz_threshold)
      dz_hi++;

   prof.dead_zone_bot = prices[dz_lo];
   prof.dead_zone_top = prices[dz_hi];

// Compute secondary VA (mini VA around secondary POC within its cluster)
   double sec_target = secondary_val * 1.5;  // heuristic: capture secondary cluster
   double sec_sum = activity[secondary_idx];
   int sec_hi = secondary_idx, sec_lo = secondary_idx;

// Expand secondary VA but stop at dead zone boundary
   int sec_bound_lo = (secondary_idx > primary_poc_idx) ? dz_hi : 0;
   int sec_bound_hi = (secondary_idx > primary_poc_idx) ? levels - 1 : dz_lo;

   while(sec_sum < sec_target && (sec_hi < sec_bound_hi || sec_lo > sec_bound_lo))
     {
      double up = (sec_hi + 1 <= sec_bound_hi) ? activity[sec_hi + 1] : 0;
      double dn = (sec_lo - 1 >= sec_bound_lo) ? activity[sec_lo - 1] : 0;
      if(up >= dn && sec_hi < sec_bound_hi)
        {
         sec_hi++;
         sec_sum += activity[sec_hi];
        }
      else
         if(sec_lo > sec_bound_lo)
           {
            sec_lo--;
            sec_sum += activity[sec_lo];
           }
         else
            break;
     }

   prof.vah2_price = prices[sec_hi];
   prof.val2_price = prices[sec_lo];
  }

//+------------------------------------------------------------------+
//| Draw bimodal features (secondary POC, dead zone)                 |
//+------------------------------------------------------------------+
void DrawBimodalFeatures(const SessionProfile &prof, const int session_index)
  {
   if(!prof.is_bimodal)
      return;

// Dead zone rectangle
   string dz_name = StringFormat("%sDEADZONE_%s_%d", g_prefix, g_suffix, session_index);
   if(ObjectCreate(0, dz_name, OBJ_RECTANGLE, 0,
                   prof.start_time, prof.dead_zone_top,
                   prof.end_time, prof.dead_zone_bot))
     {
      ObjectSetInteger(0, dz_name, OBJPROP_COLOR, g_palette.dead_zone);
      ObjectSetInteger(0, dz_name, OBJPROP_FILL, true);
      ObjectSetInteger(0, dz_name, OBJPROP_BACK, true);
      ObjectSetInteger(0, dz_name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, dz_name, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, dz_name, OBJPROP_WIDTH, 1);
     }

// Secondary POC line
   string sp_name = StringFormat("%sPOC2_%s_%d", g_prefix, g_suffix, session_index);
   if(ObjectCreate(0, sp_name, OBJ_TREND, 0,
                   prof.start_time, prof.secondary_poc,
                   prof.end_time, prof.secondary_poc))
     {
      ObjectSetInteger(0, sp_name, OBJPROP_COLOR, g_palette.secondary_poc);
      ObjectSetInteger(0, sp_name, OBJPROP_STYLE, STYLE_DASH);
      ObjectSetInteger(0, sp_name, OBJPROP_WIDTH, 2);
      ObjectSetInteger(0, sp_name, OBJPROP_RAY_RIGHT, false);
      ObjectSetInteger(0, sp_name, OBJPROP_BACK, false);
      ObjectSetInteger(0, sp_name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, sp_name, OBJPROP_HIDDEN, true);
     }

// Label for secondary POC
   if(InpShowProfileLabels && session_index == 0)
     {
      string lbl_name = StringFormat("%sLBL_POC2_%s_%d", g_prefix, g_suffix, session_index);
      if(ObjectCreate(0, lbl_name, OBJ_TEXT, 0, prof.end_time, prof.secondary_poc))
        {
         ObjectSetString(0, lbl_name, OBJPROP_TEXT,
                         " ◇ POC2 " + DoubleToString(prof.secondary_poc, _Digits));
         ObjectSetInteger(0, lbl_name, OBJPROP_COLOR, g_palette.secondary_poc);
         ObjectSetInteger(0, lbl_name, OBJPROP_FONTSIZE, 9);
         ObjectSetString(0, lbl_name, OBJPROP_FONT, "Consolas");
         ObjectSetInteger(0, lbl_name, OBJPROP_ANCHOR, ANCHOR_LEFT);
         ObjectSetInteger(0, lbl_name, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(0, lbl_name, OBJPROP_HIDDEN, true);
        }
     }
  }

//+------------------------------------------------------------------+
//| IDEA #2: Compute delta profile per level                         |
//+------------------------------------------------------------------+
void ComputeDeltaProfile(const SessionProfile &prof, const double &prices[],
                         const int levels, double &delta_out[],
                         const long &tick_volume[], const datetime &time[])
  {
   ArrayResize(delta_out, levels);
   ArrayInitialize(delta_out, 0.0);

   double s_low = prices[0];

// Try real tick data first (works best for exchange instruments)
   if(InpUseRealTicks)
     {
      MqlTick ticks[];
      int copied = CopyTicksRange(_Symbol, ticks, COPY_TICKS_TRADE,
                                  (ulong)prof.start_time * 1000,
                                  (ulong)prof.end_time * 1000);
      if(copied > 0)
        {
         for(int i = 0; i < copied; i++)
           {
            double p = ticks[i].last;
            if(p <= 0)
               p = (ticks[i].bid + ticks[i].ask) / 2.0;
            if(p <= 0)
               continue;

            int idx = (int)MathRound((p - s_low) / g_tick_size);
            if(idx < 0 || idx >= levels)
               continue;

            double vol = (ticks[i].volume_real > 0) ? ticks[i].volume_real : (double)ticks[i].volume;
            if(vol <= 0)
               vol = 1.0;

            // TICK_FLAG_BUY / TICK_FLAG_SELL determine aggressor side
            if((ticks[i].flags & TICK_FLAG_BUY) != 0)
               delta_out[idx] += vol;
            else
               if((ticks[i].flags & TICK_FLAG_SELL) != 0)
                  delta_out[idx] -= vol;
           }
         return;
        }
     }

// Fallback: use bar close direction as aggressor proxy
// (works for FX where real aggressor data is unavailable)
   for(int b = prof.end_bar; b <= prof.start_bar; b++)
     {
      double o = iOpen(_Symbol, _Period, b);
      double c = iClose(_Symbol, _Period, b);
      double h = iHigh(_Symbol, _Period, b);
      double l = iLow(_Symbol, _Period, b);
      double vol = (double)tick_volume[b];
      if(vol <= 0)
         continue;

      // Split bar into 3 zones: lower (sell-biased), middle (neutral), upper (buy-biased)
      // If close > open: bias buy; else bias sell
      double bias = (c > o) ? 0.65 : (c < o) ? -0.65 : 0.0;

      int lo_idx = (int)MathMax(0, MathRound((l - s_low) / g_tick_size));
      int hi_idx = (int)MathMin(levels - 1, MathRound((h - s_low) / g_tick_size));
      int span = hi_idx - lo_idx + 1;
      if(span <= 0)
         continue;

      double per_level = vol / span;
      for(int i = lo_idx; i <= hi_idx; i++)
        {
         // Proximity to close also matters — levels near close get more of the bias
         double dist_ratio = (h > l) ? (prices[i] - l) / (h - l) : 0.5;
         double close_ratio = (h > l) ? (c - l) / (h - l) : 0.5;
         double proximity = 1.0 - MathAbs(dist_ratio - close_ratio);
         double level_bias = bias * proximity;

         delta_out[i] += per_level * level_bias;
        }
     }
  }

//+------------------------------------------------------------------+
//| Apply delta tint to existing profile bars (no side panel)        |
//+------------------------------------------------------------------+
void ApplyDeltaTint(const SessionProfile &prof, const double &prices[],
                    const double &delta[], const int levels,
                    const int session_index)
  {
// Find max absolute delta for normalization
   double max_abs = 0;
   for(int i = 0; i < levels; i++)
      if(MathAbs(delta[i]) > max_abs)
         max_abs = MathAbs(delta[i]);
   if(max_abs <= 0)
      return;

// Re-color existing profile bars based on delta sign/magnitude
   for(int i = 0; i < levels; i++)
     {
      if(MathAbs(delta[i]) < 1e-9)
         continue;

      string name = StringFormat("%sBAR_%s_%d_%d",
                                 g_prefix, g_suffix, session_index, i);
      if(ObjectFind(0, name) < 0)
         continue;

      double normalized = delta[i] / max_abs;     // -1..+1
      double intensity = MathAbs(normalized);     // 0..1

      // Skip weak deltas (keep original color)
      if(intensity < 0.2)
         continue;

      color existing_clr = (color)ObjectGetInteger(0, name, OBJPROP_COLOR);
      color tint_target = (delta[i] > 0) ? g_palette.delta_buy : g_palette.delta_sell;

      // Blend existing color with delta color proportionally to intensity
      double blend_factor = MathMin(0.65, intensity * 0.7);
      color blended = BlendColors(existing_clr, tint_target, blend_factor);

      // Dim for older sessions
      if(session_index > 0)
         blended = DimColor(blended, MathMax(0.5, 1.0 - session_index * 0.15));

      ObjectSetInteger(0, name, OBJPROP_COLOR, blended);
     }

// Compact absorption marker — attached to POC label, not a separate row
   if(prof.absorption_poc && session_index == 0)
     {
      string abs_name = StringFormat("%sABSORB_%s", g_prefix, g_suffix);
      // Place just above the POC line, inside the profile area
      datetime abs_time = (datetime)(prof.end_time - PeriodSeconds() * 3);
      if(ObjectCreate(0, abs_name, OBJ_TEXT, 0, abs_time, prof.poc_price))
        {
         ObjectSetString(0, abs_name, OBJPROP_TEXT, "⚡");
         ObjectSetInteger(0, abs_name, OBJPROP_COLOR, g_palette.absorption);
         ObjectSetInteger(0, abs_name, OBJPROP_FONTSIZE, 14);
         ObjectSetString(0, abs_name, OBJPROP_FONT, "Consolas Bold");
         ObjectSetInteger(0, abs_name, OBJPROP_ANCHOR, ANCHOR_RIGHT);
         ObjectSetInteger(0, abs_name, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(0, abs_name, OBJPROP_HIDDEN, true);
         ObjectSetInteger(0, abs_name, OBJPROP_BACK, false);
        }
     }
  }

//+------------------------------------------------------------------+
//| IDEA #3: Build composite profile from multiple sessions          |
//+------------------------------------------------------------------+
void BuildCompositeProfile(SessionProfile &profiles[], const int n_sessions)
  {
// Determine composite price range from all sessions
   double c_high = -DBL_MAX, c_low = DBL_MAX;
   int sessions_to_use = MathMin(n_sessions, g_composite_sessions);

   for(int s = 0; s < sessions_to_use; s++)
     {
      if(profiles[s].high > c_high)
         c_high = profiles[s].high;
      if(profiles[s].low  < c_low)
         c_low  = profiles[s].low;
     }
   if(c_high <= c_low)
      return;

   c_high = MathCeil(c_high / g_tick_size) * g_tick_size;
   c_low  = MathFloor(c_low / g_tick_size) * g_tick_size;

   int c_levels = (int)MathRound((c_high - c_low) / g_tick_size) + 1;
   if(c_levels > InpMaxPriceLevels * 3)
      c_levels = InpMaxPriceLevels * 3;
   if(c_levels < 10)
      return;

   ArrayResize(g_composite_prices, c_levels);
   ArrayResize(g_composite_activity, c_levels);
   ArrayInitialize(g_composite_activity, 0.0);
   g_composite_levels = c_levels;
   g_composite_low = c_low;
   g_composite_high = c_high;

   for(int i = 0; i < c_levels; i++)
      g_composite_prices[i] = c_low + i * g_tick_size;

// Accumulate with exponential decay (newer = heavier weight)
   for(int s = 0; s < sessions_to_use; s++)
     {
      double decay_weight = MathPow(InpDecayFactor, s);
      double s_high = profiles[s].high;
      double s_low = profiles[s].low;
      if(s_high <= s_low)
         continue;

      // Approximate session activity by constructing a triangular distribution
      // centered on POC with VA as the base — avoids re-reading all bars
      double poc = profiles[s].poc_price;
      double vah = profiles[s].vah_price;
      double val = profiles[s].val_price;
      double total = profiles[s].total_activity;
      if(total <= 0)
         continue;

      for(int i = 0; i < c_levels; i++)
        {
         double p = g_composite_prices[i];
         if(p < s_low || p > s_high)
            continue;

         double contribution;
         if(p >= val && p <= vah)
           {
            // Inside VA: peak at POC, linear falloff
            double dist_from_poc = MathAbs(p - poc);
            double va_half = MathMax(vah - poc, poc - val);
            if(va_half <= 0)
               va_half = g_tick_size;
            contribution = total * 0.7 * (1.0 - dist_from_poc / (va_half * 2.0));
           }
         else
           {
            // Outside VA: small contribution
            double range = s_high - s_low;
            contribution = total * 0.3 / MathMax(range / g_tick_size, 1.0);
           }

         if(contribution < 0)
            contribution = 0;
         g_composite_activity[i] += contribution * decay_weight;
        }
     }
  }

//+------------------------------------------------------------------+
//| IDEA #3: Draw HVN (High Volume Nodes) and LVN (Low Volume Nodes) |
//+------------------------------------------------------------------+
void DrawHVN_LVN(const SessionProfile &current_prof)
  {
   if(g_composite_levels < 10)
      return;

// Find peaks and valleys in composite
   double max_act = 0;
   for(int i = 0; i < g_composite_levels; i++)
      if(g_composite_activity[i] > max_act)
         max_act = g_composite_activity[i];
   if(max_act <= 0)
      return;

   double hvn_threshold = max_act * 0.70;
   double lvn_threshold = max_act * 0.15;

// Draw lines to the right of the current session
   datetime t_end = current_prof.end_time;
   datetime t_line_end = (datetime)(TimeCurrent() + PeriodSeconds() * 80);

   int hvn_count = 0, lvn_count = 0;

   for(int i = 1; i < g_composite_levels - 1; i++)
     {
      bool is_local_max = (g_composite_activity[i] > g_composite_activity[i - 1] &&
                           g_composite_activity[i] > g_composite_activity[i + 1]);
      bool is_local_min = (g_composite_activity[i] < g_composite_activity[i - 1] &&
                           g_composite_activity[i] < g_composite_activity[i + 1]);

      if(is_local_max && g_composite_activity[i] >= hvn_threshold && hvn_count < 6)
        {
         double p = g_composite_prices[i];
         string name = StringFormat("%sHVN_%d", g_prefix, i);
         if(ObjectCreate(0, name, OBJ_TREND, 0, t_end, p, t_line_end, p))
           {
            ObjectSetInteger(0, name, OBJPROP_COLOR, g_palette.hvn_color);
            ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
            ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
            ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
            ObjectSetInteger(0, name, OBJPROP_BACK, false);
            ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
           }
         // HVN label
         string lbl = name + "_L";
         if(ObjectCreate(0, lbl, OBJ_TEXT, 0, t_line_end, p))
           {
            ObjectSetString(0, lbl, OBJPROP_TEXT, " HVN");
            ObjectSetInteger(0, lbl, OBJPROP_COLOR, g_palette.hvn_color);
            ObjectSetInteger(0, lbl, OBJPROP_FONTSIZE, 8);
            ObjectSetString(0, lbl, OBJPROP_FONT, "Consolas Bold");
            ObjectSetInteger(0, lbl, OBJPROP_ANCHOR, ANCHOR_LEFT);
            ObjectSetInteger(0, lbl, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, lbl, OBJPROP_HIDDEN, true);
           }
         hvn_count++;
        }

      if(is_local_min && g_composite_activity[i] <= lvn_threshold && lvn_count < 6)
        {
         double p = g_composite_prices[i];
         string name = StringFormat("%sLVN_%d", g_prefix, i);
         if(ObjectCreate(0, name, OBJ_TREND, 0, t_end, p, t_line_end, p))
           {
            ObjectSetInteger(0, name, OBJPROP_COLOR, g_palette.lvn_color);
            ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DASH);
            ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
            ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
            ObjectSetInteger(0, name, OBJPROP_BACK, false);
            ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
           }
         string lbl = name + "_L";
         if(ObjectCreate(0, lbl, OBJ_TEXT, 0, t_line_end, p))
           {
            ObjectSetString(0, lbl, OBJPROP_TEXT, " LVN");
            ObjectSetInteger(0, lbl, OBJPROP_COLOR, g_palette.lvn_color);
            ObjectSetInteger(0, lbl, OBJPROP_FONTSIZE, 8);
            ObjectSetString(0, lbl, OBJPROP_FONT, "Consolas");
            ObjectSetInteger(0, lbl, OBJPROP_ANCHOR, ANCHOR_LEFT);
            ObjectSetInteger(0, lbl, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, lbl, OBJPROP_HIDDEN, true);
           }
         lvn_count++;
        }
     }
  }

//+------------------------------------------------------------------+
//| IDEA #3: Track and draw naked POCs (untested past POCs)          |
//+------------------------------------------------------------------+
void TrackAndDrawNakedPOCs(SessionProfile &profiles[], const int n_sessions,
                           const double &high[], const double &low[],
                           const double &close[], const datetime &time[])
  {
// A POC is "naked" if price hasn't traded back to it since the session ended
// Walk past sessions (skip current session[0]) and check if any bar after has touched POC

   int sessions_to_check = MathMin(n_sessions, g_composite_sessions);
   int naked_drawn = 0;
   g_naked_count = 0;

   for(int s = 1; s < sessions_to_check && naked_drawn < InpMaxNakedPOCs; s++)
     {
      double poc = profiles[s].poc_price;
      int session_end_bar = profiles[s].end_bar;

      // Check all bars from session_end back to bar 0 (current)
      bool tested = false;
      for(int b = session_end_bar - 1; b >= 0; b--)
        {
         if(poc >= low[b] && poc <= high[b])
           {
            tested = true;
            break;
           }
        }

      if(!tested)
        {
         datetime line_start = profiles[s].end_time;
         datetime line_end = (datetime)(TimeCurrent() + PeriodSeconds() * 60);

         string name = StringFormat("%sNAKED_%d", g_prefix, s);
         if(ObjectCreate(0, name, OBJ_TREND, 0, line_start, poc, line_end, poc))
           {
            ObjectSetInteger(0, name, OBJPROP_COLOR, g_palette.naked_poc);
            ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DASHDOT);
            ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
            ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
            ObjectSetInteger(0, name, OBJPROP_BACK, false);
            ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
           }

         string lbl = name + "_L";
         if(ObjectCreate(0, lbl, OBJ_TEXT, 0, line_end, poc))
           {
            ObjectSetString(0, lbl, OBJPROP_TEXT, StringFormat(" nPOC %dd", s));
            ObjectSetInteger(0, lbl, OBJPROP_COLOR, g_palette.naked_poc);
            ObjectSetInteger(0, lbl, OBJPROP_FONTSIZE, 8);
            ObjectSetString(0, lbl, OBJPROP_FONT, "Consolas Bold");
            ObjectSetInteger(0, lbl, OBJPROP_ANCHOR, ANCHOR_LEFT);
            ObjectSetInteger(0, lbl, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, lbl, OBJPROP_HIDDEN, true);
           }

         naked_drawn++;
         g_naked_count = naked_drawn;
        }
     }
  }

//+------------------------------------------------------------------+
//| Draw premium histogram                                           |
//+------------------------------------------------------------------+
void DrawPremiumHistogram(const SessionProfile &prof,
                          const double &prices[], const double &activity[],
                          const int levels, const double poc_val,
                          const int va_hi, const int va_lo,
                          const datetime &time[], const int session_index)
  {
   double max_act = poc_val;
   if(max_act <= 0)
      return;

   datetime t_start = prof.start_time;
   datetime t_end = prof.end_time;
   if(t_end <= t_start)
      t_end = (datetime)(t_start + PeriodSeconds());

   long span_sec = (long)(t_end - t_start);
   long max_bar_width_sec = (long)(span_sec * InpProfileWidth / 100.0);
   if(max_bar_width_sec < PeriodSeconds())
      max_bar_width_sec = PeriodSeconds();

   double age_dim = (session_index == 0) ? 1.0 : MathMax(0.55, 1.0 - session_index * 0.12);

   for(int i = 0; i < levels; i++)
     {
      if(activity[i] <= 0)
         continue;

      double ratio = activity[i] / max_act;
      long bar_width_sec = (long)(max_bar_width_sec * ratio);
      if(bar_width_sec < PeriodSeconds() / 2)
         continue;

      datetime bar_end = (datetime)(t_start + bar_width_sec);

      bool in_va = (i >= va_lo && i <= va_hi);
      bool is_poc = (MathAbs(prices[i] - prof.poc_price) < g_tick_size / 2.0);

      color base_clr;
      if(is_poc)
         base_clr = g_palette.poc_color;
      else
         if(in_va)
            base_clr = BlendColors(g_palette.va_color, g_palette.va_accent, ratio);
         else
            base_clr = BlendColors(g_palette.profile_base, g_palette.profile_accent, ratio);

      if(age_dim < 1.0)
         base_clr = DimColor(base_clr, age_dim);

      if(is_poc && InpGlowEffect && session_index == 0)
        {
         for(int g = InpGlowIntensity; g > 0; g--)
           {
            double glow_width_frac = 1.0 + g * 0.05;
            long glow_width = (long)(bar_width_sec * glow_width_frac);
            datetime glow_end = (datetime)(t_start + glow_width);
            color glow_clr = DimColor(g_palette.poc_glow, 0.3 + 0.2 * (InpGlowIntensity - g));

            string gname = StringFormat("%sGLOW_%d_%d_%d",
                                        g_prefix, session_index, i, g);
            CreateRect(gname, t_start, prices[i] - g_tick_size * (0.5 + g * 0.15),
                       glow_end, prices[i] + g_tick_size * (0.5 + g * 0.15),
                       glow_clr, true, true);
           }
        }

      string name = StringFormat("%sBAR_%s_%d_%d",
                                 g_prefix, g_suffix, session_index, i);
      CreateRect(name, t_start, prices[i] - g_tick_size * 0.48,
                 bar_end, prices[i] + g_tick_size * 0.48,
                 base_clr, true, true);
     }
  }

//+------------------------------------------------------------------+
//| Draw glow line                                                   |
//+------------------------------------------------------------------+
void DrawGlowLine(const SessionProfile &prof, const double price,
                  const string tag, const color line_clr, const color glow_clr,
                  const ENUM_LINE_STYLE style, const int width,
                  const int session_index, const bool is_poc)
  {
   if(InpGlowEffect && session_index == 0)
     {
      string gname = StringFormat("%sGLINE_%s_%s_%d",
                                  g_prefix, tag, g_suffix, session_index);
      if(ObjectCreate(0, gname, OBJ_TREND, 0,
                      prof.start_time, price, prof.end_time, price))
        {
         ObjectSetInteger(0, gname, OBJPROP_COLOR, glow_clr);
         ObjectSetInteger(0, gname, OBJPROP_STYLE, STYLE_SOLID);
         ObjectSetInteger(0, gname, OBJPROP_WIDTH, width + 3);
         ObjectSetInteger(0, gname, OBJPROP_RAY_RIGHT, false);
         ObjectSetInteger(0, gname, OBJPROP_BACK, true);
         ObjectSetInteger(0, gname, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(0, gname, OBJPROP_HIDDEN, true);
        }
     }

   string name = StringFormat("%s%s_%s_%d", g_prefix, tag, g_suffix, session_index);
   if(ObjectCreate(0, name, OBJ_TREND, 0,
                   prof.start_time, price, prof.end_time, price))
     {
      ObjectSetInteger(0, name, OBJPROP_COLOR, line_clr);
      ObjectSetInteger(0, name, OBJPROP_STYLE, style);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
      ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
      ObjectSetInteger(0, name, OBJPROP_BACK, false);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
     }
  }

//+------------------------------------------------------------------+
//| Draw single prints                                               |
//+------------------------------------------------------------------+
void DrawSinglePrints(const SessionProfile &prof,
                      const double &prices[], const double &activity[],
                      const int levels, const datetime &time[],
                      const int session_index)
  {
   double threshold = 1.5;
   for(int i = 1; i < levels - 1; i++)
     {
      if(activity[i] > 0 && activity[i] <= threshold &&
         activity[i-1] > threshold && activity[i+1] > threshold)
        {
         string name = StringFormat("%sSP_%s_%d_%d",
                                    g_prefix, g_suffix, session_index, i);
         if(ObjectCreate(0, name, OBJ_TREND, 0,
                         prof.start_time, prices[i], prof.end_time, prices[i]))
           {
            ObjectSetInteger(0, name, OBJPROP_COLOR, g_palette.single_print);
            ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DASHDOTDOT);
            ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
            ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false);
            ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
            ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
           }
        }
     }
  }

//+------------------------------------------------------------------+
//| Draw ray                                                         |
//+------------------------------------------------------------------+
void DrawRay(const SessionProfile &prof, const double price,
             const string tag, const color clr, const datetime &time[])
  {
   string name = StringFormat("%s%s_%s", g_prefix, tag, g_suffix);
   datetime ray_end = (datetime)(TimeCurrent() + PeriodSeconds() * 100);

   if(InpStopRaysOnCross)
     {
      datetime cross_time = FindRayIntersection(prof, price, time);
      if(cross_time > 0)
         ray_end = cross_time;
     }

   if(ObjectFind(0, name) < 0)
      ObjectCreate(0, name, OBJ_TREND, 0, prof.end_time, price, ray_end, price);
   else
     {
      ObjectMove(0, name, 0, prof.end_time, price);
      ObjectMove(0, name, 1, ray_end, price);
     }

   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DASHDOT);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
   ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, !InpStopRaysOnCross);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
  }

//+------------------------------------------------------------------+
//| Find ray intersection                                            |
//+------------------------------------------------------------------+
datetime FindRayIntersection(const SessionProfile &prof, const double price,
                             const datetime &time[])
  {
   for(int b = prof.end_bar - 1; b >= 0; b--)
     {
      double bar_hi = iHigh(_Symbol, _Period, b);
      double bar_lo = iLow(_Symbol, _Period, b);
      if(price >= bar_lo && price <= bar_hi)
         return iTime(_Symbol, _Period, b);
     }
   return 0;
  }

//+------------------------------------------------------------------+
//| Draw premium labels beside profile                               |
//+------------------------------------------------------------------+
void DrawPremiumLabels(const SessionProfile &prof, const int session_index)
  {
   string tags[3]   = {"POC", "VAH", "VAL"};
   double vals[3]   = {prof.poc_price, prof.vah_price, prof.val_price};
   color  clrs[3]   = {g_palette.poc_color, g_palette.va_accent, g_palette.va_color};
   string icons[3]  = {"◆", "▲", "▼"};

   for(int i = 0; i < 3; i++)
     {
      string sname = StringFormat("%sLBLSH_%s_%s_%d",
                                  g_prefix, tags[i], g_suffix, session_index);
      datetime shadow_time = (datetime)(prof.end_time + PeriodSeconds() / 4);
      double shadow_price = vals[i] - g_tick_size * 0.1;
      if(ObjectCreate(0, sname, OBJ_TEXT, 0, shadow_time, shadow_price))
        {
         ObjectSetString(0, sname, OBJPROP_TEXT,
                         " " + icons[i] + " " + tags[i] + " " +
                         DoubleToString(vals[i], _Digits));
         ObjectSetInteger(0, sname, OBJPROP_COLOR, g_palette.label_shadow);
         ObjectSetInteger(0, sname, OBJPROP_FONTSIZE, 9);
         ObjectSetString(0, sname, OBJPROP_FONT, "Consolas");
         ObjectSetInteger(0, sname, OBJPROP_ANCHOR, ANCHOR_LEFT);
         ObjectSetInteger(0, sname, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(0, sname, OBJPROP_HIDDEN, true);
         ObjectSetInteger(0, sname, OBJPROP_BACK, false);
        }

      string name = StringFormat("%sLBL_%s_%s_%d",
                                 g_prefix, tags[i], g_suffix, session_index);
      if(ObjectCreate(0, name, OBJ_TEXT, 0, prof.end_time, vals[i]))
        {
         ObjectSetString(0, name, OBJPROP_TEXT,
                         " " + icons[i] + " " + tags[i] + " " +
                         DoubleToString(vals[i], _Digits));
         ObjectSetInteger(0, name, OBJPROP_COLOR, clrs[i]);
         ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9);
         ObjectSetString(0, name, OBJPROP_FONT, "Consolas");
         ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT);
         ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
         ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
         ObjectSetInteger(0, name, OBJPROP_BACK, false);
        }
     }
  }

//+------------------------------------------------------------------+
//| Session divider                                                  |
//+------------------------------------------------------------------+
void DrawSessionDivider(const datetime t, const int session_index)
  {
   string name = StringFormat("%sDIV_%d", g_prefix, session_index);
   if(ObjectCreate(0, name, OBJ_VLINE, 0, t, 0))
     {
      ObjectSetInteger(0, name, OBJPROP_COLOR, g_palette.divider);
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, name, OBJPROP_BACK, true);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
     }
  }

//+------------------------------------------------------------------+
//| Developing levels                                                |
//+------------------------------------------------------------------+
void CalculateDevelopingLevels(const SessionProfile &prof,
                               const double &high[], const double &low[],
                               const double &close[], const long &tick_volume[],
                               const datetime &time[], const int rates_total)
  {
   int bar_count = prof.start_bar - prof.end_bar + 1;
   if(bar_count < 2)
      return;

   double last_poc = EMPTY_VALUE, last_vah = EMPTY_VALUE, last_val = EMPTY_VALUE;

   for(int current = prof.start_bar; current >= prof.end_bar; current--)
     {
      double s_high = -DBL_MAX, s_low = DBL_MAX;
      for(int b = current; b <= prof.start_bar; b++)
        {
         if(high[b] > s_high)
            s_high = high[b];
         if(low[b]  < s_low)
            s_low  = low[b];
        }
      if(s_high <= s_low)
         continue;

      s_high = MathCeil(s_high / g_tick_size) * g_tick_size;
      s_low  = MathFloor(s_low / g_tick_size) * g_tick_size;

      int levels = (int)MathRound((s_high - s_low) / g_tick_size) + 1;
      if(levels > InpMaxPriceLevels)
         levels = InpMaxPriceLevels;
      if(levels < 2)
         continue;

      double act[];
      ArrayResize(act, levels);
      ArrayInitialize(act, 0.0);

      double total = 0;
      for(int b = current; b <= prof.start_bar; b++)
        {
         double w = (InpProfileMode == PROFILE_VOLUME) ? (double)tick_volume[b] : 1.0;
         int lo_i = (int)MathMax(0, MathRound((low[b] - s_low) / g_tick_size));
         int hi_i = (int)MathMin(levels - 1, MathRound((high[b] - s_low) / g_tick_size));
         for(int i = lo_i; i <= hi_i; i++)
           {
            act[i] += w;
            total += w;
           }
        }
      if(total <= 0)
         continue;

      int poc_i = 0;
      double poc_v = act[0];
      for(int i = 1; i < levels; i++)
         if(act[i] > poc_v)
           {
            poc_v = act[i];
            poc_i = i;
           }
      double dev_poc = s_low + poc_i * g_tick_size;

      double target = total * (InpValueAreaPercent / 100.0);
      double sum = act[poc_i];
      int hi = poc_i, lo = poc_i;
      while(sum < target && (hi < levels - 1 || lo > 0))
        {
         double up = (hi + 1 < levels) ? act[hi + 1] : 0;
         double dn = (lo - 1 >= 0) ? act[lo - 1] : 0;
         if(up >= dn && hi < levels - 1)
           {
            hi++;
            sum += act[hi];
           }
         else
            if(lo > 0)
              {
               lo--;
               sum += act[lo];
              }
            else
               break;
        }
      double dev_vah = s_low + hi * g_tick_size;
      double dev_val = s_low + lo * g_tick_size;

      if(InpEnableDevPOC)
         WriteDevLine(DevPOC_1, DevPOC_2, current, rates_total, dev_poc, last_poc);
      if(InpEnableDevVA)
        {
         WriteDevLine(DevVAH_1, DevVAH_2, current, rates_total, dev_vah, last_vah);
         WriteDevLine(DevVAL_1, DevVAL_2, current, rates_total, dev_val, last_val);
        }

      last_poc = dev_poc;
      last_vah = dev_vah;
      last_val = dev_val;
     }

   g_last_poc = last_poc;
   g_last_vah = last_vah;
   g_last_val = last_val;
  }

//+------------------------------------------------------------------+
//| Write developing line to buffers                                 |
//+------------------------------------------------------------------+
void WriteDevLine(double &buf1[], double &buf2[], const int series_bar,
                  const int rates_total, const double value, const double prev_value)
  {
   int idx = rates_total - 1 - series_bar;
   if(idx < 0 || idx >= rates_total)
      return;

   bool jumped = (prev_value != EMPTY_VALUE &&
                  MathAbs(value - prev_value) > g_tick_size * 2.0);

   if(jumped)
     {
      buf2[idx] = value;
      if(idx > 0)
         buf2[idx - 1] = prev_value;
     }
   else
      buf1[idx] = value;
  }

//+------------------------------------------------------------------+
//| Create info panel                                                |
//+------------------------------------------------------------------+
void CreateInfoPanel()
  {
   int panel_w = 280;
   int panel_h = 326;

   string shadow = g_prefix + "PANEL_SHADOW";
   if(ObjectCreate(0, shadow, OBJ_RECTANGLE_LABEL, 0, 0, 0))
     {
      ObjectSetInteger(0, shadow, OBJPROP_CORNER, InpPanelCorner);
      ObjectSetInteger(0, shadow, OBJPROP_XDISTANCE, InpPanelX - 4);
      ObjectSetInteger(0, shadow, OBJPROP_YDISTANCE, InpPanelY + 6);
      ObjectSetInteger(0, shadow, OBJPROP_XSIZE, panel_w);
      ObjectSetInteger(0, shadow, OBJPROP_YSIZE, panel_h);
      ObjectSetInteger(0, shadow, OBJPROP_BGCOLOR, C'0,0,0');
      ObjectSetInteger(0, shadow, OBJPROP_BORDER_TYPE, BORDER_FLAT);
      ObjectSetInteger(0, shadow, OBJPROP_COLOR, C'0,0,0');
      ObjectSetInteger(0, shadow, OBJPROP_BACK, false);
      ObjectSetInteger(0, shadow, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, shadow, OBJPROP_HIDDEN, true);
     }

   string bg = g_prefix + "PANEL_BG";
   if(ObjectCreate(0, bg, OBJ_RECTANGLE_LABEL, 0, 0, 0))
     {
      ObjectSetInteger(0, bg, OBJPROP_CORNER, InpPanelCorner);
      ObjectSetInteger(0, bg, OBJPROP_XDISTANCE, InpPanelX);
      ObjectSetInteger(0, bg, OBJPROP_YDISTANCE, InpPanelY);
      ObjectSetInteger(0, bg, OBJPROP_XSIZE, panel_w);
      ObjectSetInteger(0, bg, OBJPROP_YSIZE, panel_h);
      ObjectSetInteger(0, bg, OBJPROP_BGCOLOR, g_palette.panel_bg);
      ObjectSetInteger(0, bg, OBJPROP_BORDER_TYPE, BORDER_FLAT);
      ObjectSetInteger(0, bg, OBJPROP_COLOR, g_palette.panel_border);
      ObjectSetInteger(0, bg, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, bg, OBJPROP_BACK, false);
      ObjectSetInteger(0, bg, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, bg, OBJPROP_HIDDEN, true);
     }

   string accent = g_prefix + "PANEL_ACCENT";
   if(ObjectCreate(0, accent, OBJ_RECTANGLE_LABEL, 0, 0, 0))
     {
      ObjectSetInteger(0, accent, OBJPROP_CORNER, InpPanelCorner);
      ObjectSetInteger(0, accent, OBJPROP_XDISTANCE, InpPanelX);
      ObjectSetInteger(0, accent, OBJPROP_YDISTANCE, InpPanelY);
      ObjectSetInteger(0, accent, OBJPROP_XSIZE, panel_w);
      ObjectSetInteger(0, accent, OBJPROP_YSIZE, 3);
      ObjectSetInteger(0, accent, OBJPROP_BGCOLOR, g_palette.panel_border);
      ObjectSetInteger(0, accent, OBJPROP_BORDER_TYPE, BORDER_FLAT);
      ObjectSetInteger(0, accent, OBJPROP_COLOR, g_palette.panel_border);
      ObjectSetInteger(0, accent, OBJPROP_BACK, false);
      ObjectSetInteger(0, accent, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, accent, OBJPROP_HIDDEN, true);
     }

   CreatePanelText("TITLE",    "◆ MP ULTIMATE PRO",   18, 10,  11, g_palette.panel_accent, true);
   CreatePanelText("SUBTITLE", "Bimodal · Delta · Memory", 18, 28, 8, g_palette.panel_text, false);
   CreatePanelText("SEP1",     "──────────────────",   18, 44, 8,  g_palette.panel_border, false);

   CreatePanelText("LBL_POC",  "◆ POC",             18, 62,  9, g_palette.poc_color,  true);
   CreatePanelText("VAL_POC",  "—",                  140, 62, 9, g_palette.panel_text, true);

   CreatePanelText("LBL_POC2", "◇ POC²",            18, 80,  9, g_palette.secondary_poc, false);
   CreatePanelText("VAL_POC2", "—",                  140, 80, 9, g_palette.panel_text, false);

   CreatePanelText("LBL_VAH",  "▲ VAH",             18, 98,  9, g_palette.va_accent,  false);
   CreatePanelText("VAL_VAH",  "—",                  140, 98, 9, g_palette.panel_text, false);

   CreatePanelText("LBL_VAL",  "▼ VAL",             18, 116, 9, g_palette.va_color,   false);
   CreatePanelText("VAL_VAL",  "—",                  140, 116, 9, g_palette.panel_text, false);

   CreatePanelText("SEP2",     "──────────────────",   18, 134, 8, g_palette.panel_border, false);

   CreatePanelText("LBL_DELTA","Δ Total",            18, 150, 9, g_palette.panel_text, false);
   CreatePanelText("VAL_DELTA","—",                  140, 150, 9, g_palette.panel_text, true);

   CreatePanelText("LBL_ABS",  "⚡ Absorption",      18, 168, 9, g_palette.panel_text, false);
   CreatePanelText("VAL_ABS",  "—",                  140, 168, 9, g_palette.panel_text, true);

   CreatePanelText("SEP3",     "──────────────────",   18, 186, 8, g_palette.panel_border, false);

   CreatePanelText("LBL_PRICE","● Price",            18, 202, 9, g_palette.panel_text, false);
   CreatePanelText("VAL_PRICE","—",                  140, 202, 9, g_palette.panel_accent, true);

   CreatePanelText("LBL_ZONE", "  Zone",             18, 220, 9, g_palette.panel_text, false);
   CreatePanelText("VAL_ZONE", "—",                  140, 220, 9, g_palette.panel_text, true);

   CreatePanelText("LBL_STRUCT","  Structure",       18, 238, 9, g_palette.panel_text, false);
   CreatePanelText("VAL_STRUCT","—",                 140, 238, 9, g_palette.panel_text, true);
   CreatePanelText("LBL_COMP", "Memory",              18, 256, 9, g_palette.panel_text, false);
   CreatePanelText("VAL_COMP", "-",                   140, 256, 9, g_palette.panel_text, true);
   CreatePanelText("LBL_NPOC", "Naked POC",           18, 274, 9, g_palette.naked_poc, false);
   CreatePanelText("VAL_NPOC", "-",                   140, 274, 9, g_palette.panel_text, true);

   CreatePanelText("SEP4",     "──────────────────",   18, 292, 8, g_palette.panel_border, false);
   CreatePanelText("FOOTER",   _Symbol + " - " + EnumToString(_Period), 18, 306, 8, g_palette.panel_text, false);
  }

//+------------------------------------------------------------------+
//| Create panel text label                                          |
//+------------------------------------------------------------------+
void CreatePanelText(const string tag, const string text,
                     const int x, const int y, const int size,
                     const color clr, const bool bold)
  {
   string name = g_prefix + "PANEL_" + tag;
   string font_name = bold ? "Consolas Bold" : "Consolas";
   if(ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0))
     {
      ObjectSetInteger(0, name, OBJPROP_CORNER, InpPanelCorner);
      ObjectSetInteger(0, name, OBJPROP_XDISTANCE, InpPanelX + x);
      ObjectSetInteger(0, name, OBJPROP_YDISTANCE, InpPanelY + y);
      ObjectSetString(0, name, OBJPROP_TEXT, text);
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_FONTSIZE, size);
      ObjectSetString(0, name, OBJPROP_FONT, font_name);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
     }
  }

//+------------------------------------------------------------------+
//| Update info panel with current session data                      |
//+------------------------------------------------------------------+
void UpdateInfoPanel(const SessionProfile &prof, const double price)
  {
   ObjectSetString(0, g_prefix + "PANEL_VAL_POC",  OBJPROP_TEXT, DoubleToString(prof.poc_price, _Digits));
   ObjectSetString(0, g_prefix + "PANEL_VAL_VAH",  OBJPROP_TEXT, DoubleToString(prof.vah_price, _Digits));
   ObjectSetString(0, g_prefix + "PANEL_VAL_VAL",  OBJPROP_TEXT, DoubleToString(prof.val_price, _Digits));
   ObjectSetString(0, g_prefix + "PANEL_VAL_PRICE",OBJPROP_TEXT, DoubleToString(price, _Digits));

// Secondary POC
   string poc2_str = prof.is_bimodal ? DoubleToString(prof.secondary_poc, _Digits) : "—";
   ObjectSetString(0, g_prefix + "PANEL_VAL_POC2", OBJPROP_TEXT, poc2_str);

// Structure
   string struct_str;
   color struct_clr;
   if(prof.is_bimodal)
     {
      struct_str = "⑉ BIMODAL";
      struct_clr = g_palette.secondary_poc;
     }
   else
     {
      struct_str = "● NORMAL";
      struct_clr = g_palette.panel_text;
     }
   ObjectSetString(0, g_prefix + "PANEL_VAL_STRUCT", OBJPROP_TEXT, struct_str);
   ObjectSetInteger(0, g_prefix + "PANEL_VAL_STRUCT", OBJPROP_COLOR, struct_clr);

// Delta
   string delta_str;
   color delta_clr;
   if(InpEnableDelta)
     {
      if(prof.delta_total > 0)
        {
         delta_str = "+" + DoubleToString(prof.delta_total, 0);
         delta_clr = g_palette.delta_buy;
        }
      else
         if(prof.delta_total < 0)
           {
            delta_str = DoubleToString(prof.delta_total, 0);
            delta_clr = g_palette.delta_sell;
           }
         else
           {
            delta_str = "0";
            delta_clr = g_palette.panel_text;
           }
     }
   else
     {
      delta_str = "off";
      delta_clr = g_palette.panel_text;
     }
   ObjectSetString(0, g_prefix + "PANEL_VAL_DELTA", OBJPROP_TEXT, delta_str);
   ObjectSetInteger(0, g_prefix + "PANEL_VAL_DELTA", OBJPROP_COLOR, delta_clr);

// Absorption
   string abs_str;
   color abs_clr;
   if(prof.absorption_poc)
     {
      abs_str = "⚡ YES";
      abs_clr = g_palette.absorption;
     }
   else
     {
      abs_str = "—";
      abs_clr = g_palette.panel_text;
     }
   ObjectSetString(0, g_prefix + "PANEL_VAL_ABS", OBJPROP_TEXT, abs_str);
   ObjectSetInteger(0, g_prefix + "PANEL_VAL_ABS", OBJPROP_COLOR, abs_clr);

// Composite memory / Naked POCs
   string comp_str = InpEnableComposite ? StringFormat("%d sessions", g_composite_sessions) : "off";
   ObjectSetString(0, g_prefix + "PANEL_VAL_COMP", OBJPROP_TEXT, comp_str);
   ObjectSetInteger(0, g_prefix + "PANEL_VAL_COMP", OBJPROP_COLOR,
                    InpEnableComposite ? g_palette.panel_accent : g_palette.panel_text);

   string naked_str = g_show_naked_pocs ? StringFormat("%d / %d", g_naked_count, InpMaxNakedPOCs) : "off";
   ObjectSetString(0, g_prefix + "PANEL_VAL_NPOC", OBJPROP_TEXT, naked_str);
   ObjectSetInteger(0, g_prefix + "PANEL_VAL_NPOC", OBJPROP_COLOR,
                    g_show_naked_pocs ? g_palette.naked_poc : g_palette.panel_text);

// Zone
   string zone;
   color zone_clr;
   if(price > prof.vah_price)
     {
      zone = "▲ ABOVE VA";
      zone_clr = g_palette.va_accent;
     }
   else
      if(price < prof.val_price)
        {
         zone = "▼ BELOW VA";
         zone_clr = g_palette.va_color;
        }
      else
         if(MathAbs(price - prof.poc_price) < g_tick_size * 2)
           { zone = "◆ AT POC";   zone_clr = g_palette.poc_color; }
         else
            if(prof.is_bimodal && price >= prof.dead_zone_bot && price <= prof.dead_zone_top)
              { zone = "⚠ DEAD ZONE"; zone_clr = g_palette.secondary_poc; }
            else
              {
               zone = "● IN VA";
               zone_clr = g_palette.panel_accent;
              }

   ObjectSetString(0, g_prefix + "PANEL_VAL_ZONE", OBJPROP_TEXT, zone);
   ObjectSetInteger(0, g_prefix + "PANEL_VAL_ZONE", OBJPROP_COLOR, zone_clr);
  }

//+------------------------------------------------------------------+
//| Check and fire alerts                                            |
//+------------------------------------------------------------------+
void CheckAlerts(const SessionProfile &prof, const double close_price, const datetime bar_time)
  {
   if(bar_time == g_last_alert_bar)
      return;
   static double prev_close = 0;
   if(prev_close == 0)
     {
      prev_close = close_price;
      return;
     }

   bool poc_x = InpAlertOnPOCCross &&
                ((prev_close < prof.poc_price && close_price >= prof.poc_price) ||
                 (prev_close > prof.poc_price && close_price <= prof.poc_price));
   bool vah_x = InpAlertOnVACross &&
                ((prev_close < prof.vah_price && close_price >= prof.vah_price) ||
                 (prev_close > prof.vah_price && close_price <= prof.vah_price));
   bool val_x = InpAlertOnVACross &&
                ((prev_close < prof.val_price && close_price >= prof.val_price) ||
                 (prev_close > prof.val_price && close_price <= prof.val_price));

   if(poc_x)
      FireAlert("◆ POC crossed", prof.poc_price);
   if(vah_x)
      FireAlert("▲ VAH crossed", prof.vah_price);
   if(val_x)
      FireAlert("▼ VAL crossed", prof.val_price);

   if(InpAlertOnAbsorption && prof.absorption_poc)
      FireAlert("⚡ Absorption at POC", prof.poc_price);

   if(poc_x || vah_x || val_x)
      g_last_alert_bar = bar_time;
   prev_close = close_price;
  }

//+------------------------------------------------------------------+
//| Send alert via selected channels                                 |
//+------------------------------------------------------------------+
void FireAlert(const string event, const double price)
  {
   string msg = StringFormat("[%s %s] %s @ %s",
                             _Symbol, EnumToString(_Period), event,
                             DoubleToString(price, _Digits));
   if(InpAlertPopup)
      Alert(msg);
   if(InpAlertPush)
      SendNotification(msg);
   if(InpAlertEmail)
      SendMail("MP Ultimate PRO", msg);
  }

//+------------------------------------------------------------------+
//| Color utilities                                                  |
//+------------------------------------------------------------------+
void CreateRect(const string name, const datetime t1, const double p1,
                const datetime t2, const double p2,
                const color clr, const bool fill, const bool back)
  {
   if(ObjectCreate(0, name, OBJ_RECTANGLE, 0, t1, p1, t2, p2))
     {
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_FILL, fill);
      ObjectSetInteger(0, name, OBJPROP_BACK, back);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
     }
  }

//+------------------------------------------------------------------+
//| Blend two colors linearly                                        |
//+------------------------------------------------------------------+
color BlendColors(const color c1, const color c2, const double t)
  {
   double tt = MathMax(0.0, MathMin(1.0, t));
   int r1 = (c1 & 0xFF);
   int g1 = ((c1 >> 8) & 0xFF);
   int b1 = ((c1 >> 16) & 0xFF);
   int r2 = (c2 & 0xFF);
   int g2 = ((c2 >> 8) & 0xFF);
   int b2 = ((c2 >> 16) & 0xFF);
   int r = (int)(r1 + (r2 - r1) * tt);
   int g = (int)(g1 + (g2 - g1) * tt);
   int b = (int)(b1 + (b2 - b1) * tt);
   return (color)((b << 16) | (g << 8) | r);
  }

//+------------------------------------------------------------------+
//| Dim a color by factor (0..1)                                     |
//+------------------------------------------------------------------+
color DimColor(const color c, const double factor)
  {
   double f = MathMax(0.0, MathMin(1.0, factor));
   int r = (int)((c & 0xFF) * f);
   int g = (int)(((c >> 8) & 0xFF) * f);
   int b = (int)(((c >> 16) & 0xFF) * f);
   return (color)((b << 16) | (g << 8) | r);
  }

//+------------------------------------------------------------------+
//| Lighten a color by factor (>1 brighter, <1 darker)               |
//+------------------------------------------------------------------+
color LightenColor(const color c, const double factor)
  {
   int r = (int)MathMin(255, (c & 0xFF) * factor);
   int g = (int)MathMin(255, ((c >> 8) & 0xFF) * factor);
   int b = (int)MathMin(255, ((c >> 16) & 0xFF) * factor);
   return (color)((b << 16) | (g << 8) | r);
  }

//+------------------------------------------------------------------+
//| String helpers                                                   |
//+------------------------------------------------------------------+
string SessionName(const ENUM_SESSION_TYPE s)
  {
   switch(s)
     {
      case SESSION_DAILY:
         return "Daily";
      case SESSION_WEEKLY:
         return "Weekly";
      case SESSION_MONTHLY:
         return "Monthly";
      case SESSION_INTRADAY:
         return StringFormat("Intraday %dh", InpIntradayHours);
     }
   return "—";
  }

//+------------------------------------------------------------------+
//| Theme name helper                                                |
//+------------------------------------------------------------------+
string ThemeName(const ENUM_THEME t)
  {
   switch(t)
     {
      case THEME_NEON_DARK:
         return "Neon Dark";
      case THEME_SUNSET:
         return "Sunset";
      case THEME_OCEAN:
         return "Ocean";
      case THEME_MATRIX:
         return "Matrix";
      case THEME_ROYAL:
         return "Royal";
      case THEME_CUSTOM:
         return "Custom";
     }
   return "—";
  }
//+------------------------------------------------------------------+
