//+------------------------------------------------------------------+
//| FibHedge_Panel.mq5                                               |
//| v28.1 - TradeSymbol: signal on seconds chart, trade on real symbol |
//+------------------------------------------------------------------+
//
// ====================================================================
// THE CORRECT MODEL — SIGNED NET FIB LADDER
// ====================================================================
//
// The fib ladder is a SIGNED number line:
//   ...-0.55,-0.34,-0.21,-0.13,-0.08,-0.05,-0.03,-0.02,-0.01,
//    0,+0.01,+0.02,+0.03,+0.05,+0.08,+0.13,+0.21,+0.34,+0.55...
//
// BUY signal  → advance net ONE fib level in the + direction
// SELL signal → advance net ONE fib level in the − direction
//
// The lot size to open = |targetNet − currentNet|
// This can be LARGE when crossing zero (intentional — bigger trade
// needed to overcome the existing hedge and reach the next level).
//
// EXAMPLES:
//   net=0.00, BUY  → target=+0.01 → open 0.01 BUY
//   net=+0.03, BUY → target=+0.05 → open 0.02 BUY
//   net=+0.03, SELL→ target=−0.05 → open 0.08 SELL  (0.03+0.05)
//   net=−0.13, BUY → target=+0.21 → open 0.34 BUY   (0.13+0.21)
//   net=+0.21, SELL→ target=+0.13 → open 0.08 SELL   (step back)
//     ↑ This last case is DoStep — used by profit-taking exits
//
// DELEVERAGE: step net one level toward zero. Close most profitable
// on the dominant side first; if not enough positions, open opposite
// side for the shortfall (this is correct — you need opposing lots
// to move the net when existing positions are too small).
//
// MANUAL OVERRIDE: if the user adds 0.01 manually and goes off-grid,
// the system snaps to the nearest fib level on the next action.
//   FibFloor(net) = largest fib level ≤ |net|, keep sign
//   FibCeil(net)  = smallest fib level > |net|
//
// ====================================================================
// CHANGES IN v19
// ====================================================================
//
// [1] FibNextUp / FibNextDown helpers — compute the signed target net
//     for one step up or down the ladder from the current net.
//
// [2] DoAutoFibStep — completely rewritten to use signed net ladder.
//     Opens |targetNet - currentNet| lots in signal direction.
//
// [3] NLot(dir) — returns the lot to open for one step in dir direction
//     based on signed net (used by manual BUY/SELL buttons).
//
// [4] DoStep — steps net one level toward zero on signed ladder.
//     Closes most-profitable on the dominant side; if not enough
//     positions exist, opens opposite-side shortfall.
//
// [5] DoDeleverage — restored close+open hybrid. When closing one side
//     doesn't fully achieve the target net shift, opens the opposite
//     side for the remainder. This is correct: in a hedge book, moving
//     net from -0.13 to -0.08 may require closing 0.05 shorts, but if
//     you only have 0.03 short positions, you open 0.02 more longs.
//
// [6] DoMicroReduce — now closes from the dominant gross side (net
//     sign unreliable in hedge books with near-zero net).
//
// [7] New button: "CLOSE 0.01" (lb_c01) — closes the smallest lot
//     increment (MicroLots) from the dominant side's most profitable
//     position. Useful for manual fine-tuning off-grid.
//
// [8] Snap-to-grid logic in DoAutoFibStep: if current net is between
//     fib levels (manual trade moved it off-grid), the next signal
//     first snaps to the nearest clean fib before advancing.
//
#property strict
#property version   "29.1"
#property description "FibHedge Panel v29.1 - Commission-aware CloseAll target.1 - TradeSymbol separation"
#include <Trade\Trade.mqh>
CTrade trade;

//====================================================================
// INPUTS
//====================================================================
input group "==== Panel Layout ===="
input int  LeftX         = 0;
input int  LeftY         = 22;
input int  RightX        = 388;
input int  RightY        = 22;

input group "==== Auto Step Target ===="
input bool   UseAutoStep    = false;
input bool   AutoStepPerLot = true;
input double PerLotTrigger  = 8.0;
input double FixedStep1     = 10.0;
input double FixedStep2     = 25.0;
input double FixedStep3     = 50.0;
input double FixedBankAll   = 100.0;

input group "==== Strategy ===="
input int    Magic     = 0;
// v28.1: TradeSymbol — leave blank to trade on the chart symbol (normal use).
// Set to "XAUUSD" (or your broker's exact name) when running the EA on a
// synthetic/seconds chart: signals are read from _Symbol (seconds chart),
// but ALL orders, position queries, and market-data calls use TradeSymbol.
// The Alkemix Trend indicator must also be attached to the seconds chart.
input string TradeSymbol = "";    // Real symbol to trade on (blank = use chart symbol)
enum ENUM_INSTRUMENT_TYPE { INSTR_XAUUSD=0, INSTR_FOREX=1, INSTR_INDEX=2 };
input ENUM_INSTRUMENT_TYPE InstrType = INSTR_XAUUSD;
input int    FibStart  = 0;
input int    FibMax    = 13;
input bool   Skin      = true;
// v26: Skin now SCALES with net fib depth.
// SkinIdx     = minimum skin fib index (floor, used at shallow depth).
//               Default 0 = 0.01 lots minimum.
// SkinOffset  = how many fib levels below current net depth the skin sits.
//               e.g. SkinOffset=3: at net fib 7 (0.34) → skin = FIB[4] = 0.08
//               At net fib 2 (0.03) → FIB[max(0, 2-3)] = FIB[0] = 0.01 (floor)
// X2SkinReduce= extra levels skin drops when x2 mode is ACTIVE.
//               Allows faster deleveraging by leaving less behind each bank.
//               e.g. X2SkinReduce=2: skin at fib 7 normally = FIB[4]=0.08
//               With x2 active: FIB[max(0,4-2)] = FIB[2] = 0.03
input int    SkinIdx      = 0;    // Base (minimum) skin fib index
input int    SkinOffset   = 3;    // Fib levels below current depth for skin size
input int    X2SkinReduce = 2;    // Additional skin reduction when x2 latch is active
input double ProfitTgt = 50.0;
input double LossTgt   = 100.0;

input group "==== Micro Scalp ===="
input double MicroLots = 0.01;

input group "==== Bollinger Bands (Panel Display) ===="
input int    BB_P      = 20;
input double BB_D      = 2.0;

//---- v13: Auto Signal (Alkemix Trend) --------------------------------
input group "==== Auto Signal — Alkemix Trend ===="
// Attach "Alkemix Trend" to the same chart with default parameters.
// Buffer indices are 0-based. User confirmed: buy=7, sell=8.
input bool            AutoSignalOn    = true;         // v17: ON by default — advances fib on every new signal
input ENUM_TIMEFRAMES AlkTF           = PERIOD_CURRENT;
input int             AlkBuyBuffer    = 7;            // BUG FIX: was 6, corrected to 7
input int             AlkSellBuffer   = 8;            // BUG FIX: was 7, corrected to 8
input double          AlkSignalThresh = 0.0;          // Min absolute value for signal

input group "==== Deleverage ===="
input bool   DeleverageMode = false;  // Start with DELEVERAGE ON (backtest-friendly)

//---- v13: Auto Leverage ----------------------------------------------
input group "==== Auto Leverage ===="
// When true, lot size = (FreeMargin * RiskPctPerTrade%) / MarginPerLot.
// Result is clamped to [FIB[FibStart] .. FIB[FibMax]] and rounded to 0.01.
// When false, lot sizing uses the standard Fibonacci step (original behaviour).
input bool   AutoLeverage    = false;  // Enable auto lot-sizing from free margin
input double RiskPctPerTrade = 1.0;    // % of free margin risked per open

//---- v13: Auto Profit-Taking Suite -----------------------------------
input group "==== Auto Profit Taking ===="
// Master switch.  Also mirrors the new "AUTO PROFIT" panel button.
// Must be true for ALL automatic profit exits to fire.
input bool   AutoProfitTake  = false; // Master switch (button + backtest input)

// --- Threshold triggers (all fire DoStep/DoBank/DoCloseAll) ----------
input bool   PT_PnL_Step     = true;  // DoStep(1) when P&L >= PtPnlStep
input double PtPnlStep       = 20.0;  // $ open P&L to trigger step-down
input bool   PT_PnL_Bank     = false; // DoBank() when P&L >= PtPnlBank
input double PtPnlBank       = 50.0;  // $ open P&L to trigger bank
input bool   PT_PnL_Close    = false; // DoCloseAll() when P&L >= PtPnlClose
input double PtPnlClose      = 100.0; // $ open P&L to close everything
input bool   PT_Equity_Pct   = false; // DoBank() when equity >= balance * PtEqPct%
input double PtEqPct         = 102.0; // Equity % of balance trigger (e.g. 102 = +2%)
input bool   PT_Trail_Step   = false; // DoStep(1) if P&L drops PtTrailPct% from peak
input double PtTrailPct      = 25.0;  // % pullback from session P&L peak to trigger
input bool   PT_DD_Close     = false; // DoCloseAll() if open P&L <= -PtDDClose (hard stop)
input double PtDDClose       = 200.0; // $ drawdown limit (positive value)

//---- v13: BB Exit ----------------------------------------------------
input group "==== Auto Exit — BB Step ===="
enum ENUM_EXIT_MODE { EXIT_MANUAL=0, EXIT_BB_STEP=1 };
input ENUM_EXIT_MODE  ExitMode       = EXIT_MANUAL;   // Requires AutoProfitTake=true
input ENUM_TIMEFRAMES BBExitTF       = PERIOD_H1;
input int             BBExitPeriod   = 20;
input double          BBExitDev      = 2.0;
// v26: BBExit refinements
input bool   BBExit_TouchMode   = false;  // true=fire on BAR TOUCH of band (high/low), false=bar CLOSE beyond band
input bool   BBExit_X2Steps     = false;  // true=DoStep(x2) on BB exit (step 2 fib levels, independent of x2 latch)
input int    BBExit_SkipSignalBars = 0;   // After BB exit fires, skip this many signal-TF bars before next entry
//                                           e.g. chart on M3, BBExitTF=M15: set to 5 to block ~15min of re-entries
input bool   BBExit_SkinResetOnFlip = false; // v28: when Skin=true & TouchMode=true, DoBank on next signal flip (resets to starting fib)

//---- v14: Per-Position Profit Take -----------------------------------
// Gated by AutoProfitTake master switch.
// Scans every position on every tick. Closes (or partial-closes to Skin)
// any position whose P&L >= PtPerPosPnl. Independent of net direction.
// Use to harvest individual legs that spike into profit.
input bool   PT_PerPos     = false; // Close individual positions at profit target
input double PtPerPosPnl   = 15.0;  // $ per-position P&L to trigger close

//---- v14: Auto-Deleverage Trigger ------------------------------------
// Automatically enables g_delev (Deleverage Mode) when book gets too
// deep. Any single condition is enough to trigger (OR logic).
// Conditions reset when all clear if DelevAutoOff = true.
input group "==== Auto Deleverage Trigger ===="
input bool   AutoDelevEnable    = false; // Master: enable auto deleverage trigger
// Trigger thresholds (set to 0 to disable that specific condition):
input int    DelevOnPositions   = 5;     // Trigger when open positions >= N (0=off)
input int    DelevOnFibLevel    = 7;     // Trigger when net fib-index >= N (0=off, index 7 = 0.34 lots)
input double DelevOnNetLots     = 0.0;   // Trigger when |net lots| >= N   (0=off)
// Behaviour when conditions clear:
input bool   DelevAutoOff       = true;  // Auto-disable deleverage when ALL conditions clear
// v16: Signal spacing during active deleverage
input int    DelevSkipSignals   = 1;     // Skip N signal bars after a deleverage action before allowing new open
//                                         0 = no skip (original behaviour), 1 = skip 1 bar, 2 = skip 2, etc.

input group "==== Double Step at Depth ====" 
// x2 mode uses a LATCH with hysteresis — it turns ON when depth is reached
// and stays ON until the net has recovered to the exit threshold.
// This prevents rapid on/off oscillation when fib index hovers at the trigger.
//
//   DoubleStepOnFibIdx  (default 9 = 0.89 lots): net fib index that ARMS the latch.
//   DoubleStepOffFibIdx (default 2 = 0.03 lots): net fib index at which latch RESETS.
//
// Example:
//   net fib hits 9  → x2 ACTIVE (latch set)
//   net steps back to fib 7, 6, 5... x2 stays ON
//   net recovers to fib 2 or below   → x2 disarms (latch cleared)
//   next time fib hits 9             → x2 ACTIVE again
input bool   DoubleStepEnable    = false;  // Master enable for x2 step at depth
input int    DoubleStepOnFibIdx  = 9;      // Fib index to ARM x2 (9=0.89, 8=0.55, 7=0.34)
input int    DoubleStepOffFibIdx = 2;      // Fib index at/below which x2 DISARMS (2=0.03, 1=0.02, 0=0.01)

//---- v16: New Profit-Taking Modes ------------------------------------
input group "==== Anti-Swap / Age-Based PT ===="
// These address the equity/balance divergence caused by swap accumulation.
input bool   PT_SwapDrain       = false; // Close positions where swap < -PtSwapDrainUSD $
input double PtSwapDrainUSD     = 5.0;   // $ swap loss threshold per position (negative = drain)
input bool   PT_HoldDays        = false; // Close positions older than PtHoldDays days
input double PtHoldDays         = 5.0;   // Max days a position can stay open (0=off)
input bool   PT_EquityDD        = false; // Close all if equity drops PtEquityDDPct% from session-start equity
input double PtEquityDDPct      = 5.0;   // % equity drawdown from session high to trigger (e.g. 5 = -5%)

//====================================================================
// BALANCE GROWTH TRIGGER (v26)
//====================================================================
// Fires DoBank every time BALANCE grows by BG_StepUSD from session start.
// Tracks loops: loop 1 fires at baseline+$100, loop 2 at baseline+$200 etc.
//
// Why balance (not equity): in this system balance grows each time we bank
// profits, while equity can lag due to open hedges. Tracking balance gives
// clean, reliable milestones. Equity-to-balance divergence is expected and
// is actually the signal that the system is working — we capture it here.
//
// The loop count persists for the session. Restarts cleanly on EA reload.
//====================================================================
input group "==== Balance Growth DoBank ===="
input bool   BG_Enable          = false;  // Fire DoBank every time balance grows BG_StepUSD
input double BG_StepUSD         = 100.0;  // Balance growth step per loop ($)
//----------------------------------------------------------------------

//====================================================================
// SESSION & TIME FILTER (v25)
//====================================================================
// All times are in BROKER/SERVER time (as shown by TimeCurrent()).
// Configure your broker's UTC offset to set up ICT kill zones correctly.
//
// Standard ICT Kill Zones at UTC+2 broker (most common, e.g. Teletrade):
//   London Open  : 09:00 – 12:00
//   New York AM  : 15:30 – 18:00
//   London Close : 17:00 – 19:00
//   New York PM  : 20:30 – 23:00
//
// BLOCK MODE choices:
//   BlockOpens = true  → only gate new position OPENS (exits/steps/bank always allowed)
//   BlockOpens = false → gate ALL automated actions including exits
//   Manual panel buttons are NEVER blocked regardless of filter.
//====================================================================
input group "==== Session & Time Filter ===="
input bool   TF_Enable          = false;  // Master enable for all time filtering
input bool   TF_BlockOpens      = true;   // true=block opens only | false=block all auto actions
// --- Days of Week (server time) ---
input bool   TF_Monday          = true;
input bool   TF_Tuesday         = true;
input bool   TF_Wednesday       = true;
input bool   TF_Thursday        = true;
input bool   TF_Friday          = true;
// Friday early close — stop opening new positions after this server hour on Fridays
input bool   TF_FridayEarlyClose = true;
input int    TF_FridayCloseHour  = 20;    // Server hour — no new opens after this on Friday

// --- ICT Kill Zone 1: London Open ---
input group "==== KZ1: London Open ===="
input bool   KZ1_Enable         = true;
input int    KZ1_StartHour      = 9;      // Server time HH
input int    KZ1_StartMin       = 0;
input int    KZ1_EndHour        = 12;
input int    KZ1_EndMin         = 0;

// --- ICT Kill Zone 2: New York AM ---
input group "==== KZ2: New York AM ===="
input bool   KZ2_Enable         = true;
input int    KZ2_StartHour      = 15;
input int    KZ2_StartMin       = 30;
input int    KZ2_EndHour        = 18;
input int    KZ2_EndMin         = 0;

// --- ICT Kill Zone 3: London Close / NY Overlap ---
input group "==== KZ3: London Close ===="
input bool   KZ3_Enable         = false;
input int    KZ3_StartHour      = 17;
input int    KZ3_StartMin       = 0;
input int    KZ3_EndHour        = 19;
input int    KZ3_EndMin         = 0;

// --- ICT Kill Zone 4: New York PM ---
input group "==== KZ4: New York PM ===="
input bool   KZ4_Enable         = false;
input int    KZ4_StartHour      = 20;
input int    KZ4_StartMin       = 30;
input int    KZ4_EndHour        = 23;
input int    KZ4_EndMin         = 0;

// --- Custom Session 1 ---
input group "==== Custom Session 1 ===="
input bool   CS1_Enable         = false;
input int    CS1_StartHour      = 0;
input int    CS1_StartMin       = 0;
input int    CS1_EndHour        = 0;
input int    CS1_EndMin         = 0;

// --- Custom Session 2 ---
input group "==== Custom Session 2 ===="
input bool   CS2_Enable         = false;
input int    CS2_StartHour      = 0;
input int    CS2_StartMin       = 0;
input int    CS2_EndHour        = 0;
input int    CS2_EndMin         = 0;

//====================================================================
// SPREAD FILTER (v25)
//====================================================================
// Gates new position OPENS when spread exceeds threshold.
// Exits (DoStep, DoBank, DoCloseAll) are NEVER blocked by spread —
// you always want to be able to exit regardless of spread.
//====================================================================
input group "==== Spread Filter ===="
input bool   SF_Enable          = false;  // Master enable for spread filter
input int    SF_MaxSpreadPts    = 40;     // Max allowed spread in broker POINTS (XAU: 40pts = $0.40)
// Spread alert (warn on panel but don't block)
input int    SF_WarnSpreadPts   = 25;     // Yellow warning threshold (0=off)

//====================================================================
// SESSION RISK CAPS (v25)
//====================================================================
// Stops automated opens (not exits) when daily/session thresholds hit.
// Designed to prevent compounding losses on bad days.
//====================================================================
input group "==== Session Risk Caps ===="
input bool   SRC_Enable         = false;  // Master enable
input double SRC_MaxDailyLoss   = 150.0;  // Stop opens if daily closed loss >= this (positive $)
input double SRC_MaxDailyProfit = 0.0;    // Stop opens once daily closed profit >= this (0=off)
input int    SRC_MaxDailyTrades = 0;      // Stop opens after N closed trades today (0=off)
input int    SRC_MaxOpenPos     = 20;     // Hard cap on total open positions (0=off, safety net)

//----------------------------------------------------------------------

//====================================================================
// CONSTANTS
//====================================================================
#define MAX_POS_ROWS  15
#define MAX_HIST_ROWS  8

const double FIB[]   = {0.01,0.02,0.03,0.05,0.08,0.13,0.21,0.34,0.55,0.89,1.44,2.33,3.77,6.10,9.87};
const int    FIB_LEN = 15;

const int LBW = 370;
const int LBH = 64;
const int LBG = 8;
const int RDW = 460;

const color BK   = C'0,0,0';
const color BK2  = C'10,10,10';
const color BK3  = C'20,20,20';
const color TXT  = C'200,215,228';
const color DIM  = C'72,88,104';
const color GRN  = C'0,210,90';
const color RED  = C'218,45,45';
const color YLW  = C'225,185,0';
const color ACC  = C'0,130,240';
const color BUY  = C'0,150,68';
const color SEL  = C'175,25,25';
const color STP  = C'148,96,0';
const color BNK  = C'0,138,112';
const color NUK  = C'160,12,12';
const color AUTO_ON  = C'0,160,160';
const color AUTO_OFF = C'30,30,30';

//====================================================================
// GLOBALS
//====================================================================
int  g_bb = INVALID_HANDLE;
// v28.1: resolved trade symbol (set in OnInit — never changes at runtime)
string g_tradeSymbol = "";
bool g_lpMin  = false;
bool g_rpMin  = false;
bool g_ready  = false;
bool g_flash  = false;
bool g_delev  = false;
int  g_fibOvr = -1;

// Auto-step
int    g_stepFired    = 0;
double g_lastStepPnL  = 0.0;
datetime g_lastStepBar = 0;   // BUG FIX v13: was g_lastStepTime (second-res); now bar-res

// Flash timing
datetime g_lf = 0;

// Daily / session P&L
datetime g_lastDayDate      = 0;
double   g_dailyClosedPnL   = 0.0;
ulong    g_lastDeal         = 0;
double   g_sessionClosedPnL = 0.0;
datetime g_sessionStart     = 0;

// Trade history
struct HistEntry { datetime t; int dir; double lots; double pnl; };
HistEntry g_hist[MAX_HIST_ROWS];
int g_histCount = 0;

// Per-tick cache
double g_cNet  = 0.0;
double g_cAbsN = 0.0;
double g_cPnL  = 0.0;
int    g_cN    = 0;
// v18: directional gross exposure (independent of netting)
double g_cGrossL = 0.0;   // total BUY volume across all open positions
double g_cGrossS = 0.0;   // total SELL volume across all open positions

// v29: commission tracking
double g_commRatePerLot = 0.0;  // learned from closed deals: |commission| / volume
double g_cCommClose     = 0.0;  // estimated commission to close all open positions
int    g_commSamples    = 0;    // number of deals used to compute g_commRatePerLot

//---- v13 globals ----------------------------------------------------
int      g_alkHandle   = INVALID_HANDLE;  // Alkemix Trend handle
int      g_bbExitH     = INVALID_HANDLE;  // BB exit handle
bool     g_autoSig     = false;           // Runtime AUTO signal toggle (synced from input on init)
bool     g_autoPT      = false;           // Runtime AUTO PROFIT toggle (synced from input on init)
datetime g_lastSigBar  = 0;               // Last bar where signal fired
datetime g_lastBBExit  = 0;               // Last bar of BB exit (1-bar cooldown)
datetime g_bbExitSigBlockUntil = 0;       // v26: block new signals on signal TF until this bar time
int      g_lastAlkDir  = 0;              // v26: last confirmed Alkemix direction (+1=buy, -1=sell, 0=unknown)
bool     g_bbTouchFired = false;         // v28: true after BB touch exit fires; triggers DoBank reset on next signal flip
string   g_lastSigStr  = "---";           // Display: last signal description
color    g_lastSigClr  = DIM;

// Profit-taking state
double   g_ptPeakPnL   = 0.0;            // Session peak open P&L (for trailing)
datetime g_ptLastBar   = 0;              // Cooldown: one PT action per bar

// v14: Auto-deleverage trigger state
bool     g_delevAutoOn = false;          // true = deleverage was enabled by auto-trigger
string   g_delevReason = "";             // Which condition triggered (for display)

// v15: Shared bar-cooldown globals (Bug 4 + Bug 5 fix)
datetime g_anyPTBar    = 0;             // Any profit-taking action this bar (shared across AutoStep + PT suite)
datetime g_delevBar    = 0;             // Auto-deleverage action cooldown (once per bar)

// v16: Deleverage/signal coordination
int      g_delevSkipCount = 0;          // Bars remaining to skip new opens after a deleverage action
double   g_sessionEqHigh  = 0.0;        // Session equity high-water mark (for PT_EquityDD)

// v24: x2 step hysteresis latch
bool     g_x2Active       = false;      // true = x2 mode currently armed (stays on until OffFibIdx reached)

// v26: balance growth DoBank tracker
double   g_bgBaseline     = 0.0;        // Balance at session start (anchor for loop calculation)
double   g_bgNextTarget   = 0.0;        // Next balance milestone that fires DoBank
int      g_bgLoop         = 0;          // How many loops have fired this session
datetime g_bgLastBar      = 0;          // Bar cooldown — one fire per bar max

// v25: session/time/spread filter state (cached each tick for UI)
bool     g_tfAllowed      = true;       // true = current time is in an allowed session
bool     g_sfAllowed      = true;       // true = spread is within limit
bool     g_srcAllowed     = true;       // true = session risk caps not hit
bool     g_opensAllowed   = true;       // combined: all three above (gates new opens)
string   g_filterReason   = "";         // human-readable reason for current block state
int      g_dailyTradeCount = 0;         // closed trades today (for SRC_MaxDailyTrades)
datetime g_srcTradeCountDate = 0;       // date of last trade-count reset
//---------------------------------------------------------------------

//====================================================================
// HELPERS
//====================================================================
int FC(int i) { return MathMax(FibStart, MathMin(i, MathMin(FibMax, FIB_LEN-1))); }

int FF(double l)
{
   for(int i = 0; i < FIB_LEN; i++)
      if(MathAbs(FIB[i]-l) < 0.0015) return i;
   return -1;
}

int FA(double l)
{
   for(int i = 0; i < FIB_LEN; i++)
      if(FIB[i] > l+0.0015) return i;
   return FIB_LEN-1;
}

int FibAtOrBelow(double l)
{
   int best = 0;
   for(int i = 0; i < FIB_LEN; i++)
   {
      if(FIB[i] <= l+0.0015) best = i; else break;
   }
   return best;
}

bool PM(ulong t)
{
   if(!PositionSelectByTicket(t)) return false;
   if(PositionGetString(POSITION_SYMBOL) != g_tradeSymbol) return false;
   if(Magic > 0 && (int)PositionGetInteger(POSITION_MAGIC) != Magic) return false;
   return true;
}

double _CalcNet()
{
   double n = 0;
   for(int i = 0; i < PositionsTotal(); i++)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue;
      double v = PositionGetDouble(POSITION_VOLUME);
      n += (PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY) ? v : -v;
   }
   return NormalizeDouble(n, 2);
}

double _CalcPnL()
{
   double p = 0;
   for(int i = 0; i < PositionsTotal(); i++)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue;
      p += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
   }
   return p;
}

int _CalcN()
{
   int n = 0;
   for(int i = 0; i < PositionsTotal(); i++)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue; n++;
   }
   return n;
}

// v29: Learn commission rate from the most recent closed deals.
// Samples DEAL_ENTRY_IN deals (opening trades) to get commission paid per lot.
// Falls back to a direct POSITION_COMMISSION read if available.
void UpdateCommRate()
{
   // Use open positions — MT5 stores the opening commission on each position
   int n = 0; double sumRate = 0.0;
   for(int i = 0; i < PositionsTotal(); i++)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue;
      double vol  = PositionGetDouble(POSITION_VOLUME); if(vol < 0.005) continue;
      double comm = MathAbs(PositionGetDouble(POSITION_COMMISSION));
      if(comm > 0.001) { sumRate += comm / vol; n++; }
   }
   if(n > 0)
   {
      g_commRatePerLot = sumRate / n;
      g_commSamples    = n;
   }
   // If positions have no commission yet, fall back to history
   if(g_commRatePerLot < 0.001)
   {
      if(!HistorySelect(g_sessionStart, TimeCurrent()+1)) return;
      int total = HistoryDealsTotal(); int hn = 0; double hsum = 0.0;
      for(int i = MathMax(0, total-20); i < total; i++)
      {
         ulong tkt = HistoryDealGetTicket(i); if(tkt == 0) continue;
         if(HistoryDealGetString(tkt, DEAL_SYMBOL) != g_tradeSymbol) continue;
         if(Magic > 0 && (int)HistoryDealGetInteger(tkt, DEAL_MAGIC) != Magic) continue;
         double comm = MathAbs(HistoryDealGetDouble(tkt, DEAL_COMMISSION));
         double vol  = HistoryDealGetDouble(tkt, DEAL_VOLUME);
         if(comm > 0.001 && vol > 0.001) { hsum += comm / vol; hn++; }
      }
      if(hn > 0) { g_commRatePerLot = hsum / hn; g_commSamples = hn; }
   }
}

// v29: Estimate total commission to close all open positions.
// Commission is charged on BOTH open and close sides, so closing costs
// the same as opening. We use g_commRatePerLot per lot per side.
// Result: the P&L target must EXCEED this for a net-positive close.
double CalcCloseCommission()
{
   if(g_commRatePerLot < 0.001) return 0.0;
   double totalVol = 0.0;
   for(int i = 0; i < PositionsTotal(); i++)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue;
      totalVol += PositionGetDouble(POSITION_VOLUME);
   }
   // x2: close-side commission only (open-side already paid, reflected in P&L)
   return NormalizeDouble(totalVol * g_commRatePerLot, 2);
}

void RefreshCache()
{
   g_cNet  = _CalcNet();
   g_cAbsN = MathAbs(g_cNet);
   g_cPnL  = _CalcPnL();
   g_cN    = _CalcN();
   // v18: compute directional gross exposure
   g_cGrossL = 0.0;
   g_cGrossS = 0.0;
   for(int i = 0; i < PositionsTotal(); i++)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue;
      double v = PositionGetDouble(POSITION_VOLUME);
      if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)  g_cGrossL += v;
      else                                                          g_cGrossS += v;
   }
   g_cGrossL = NormalizeDouble(g_cGrossL, 2);
   g_cGrossS = NormalizeDouble(g_cGrossS, 2);
   // v29: update commission rate and close-cost estimate
   UpdateCommRate();
   g_cCommClose = CalcCloseCommission();
}

// ====================================================================
// SIGNED-NET FIB LADDER HELPERS
// ====================================================================
//
// FibIdxOfNet(net): returns the fib index of the level the net is AT
//   or the closest below on the absolute scale, preserving sign.
//   Returns -1 if net is 0 (flat).
//
// FibNextUp(net): the next higher value on the signed ladder.
//   e.g. net=+0.03 → +0.05 ; net=-0.13 → -0.08 ; net=0 → +0.01
//
// FibNextDown(net): the next lower value on the signed ladder.
//   e.g. net=+0.05 → +0.03 ; net=-0.08 → -0.13 ; net=0 → -0.01
//
// FibSnap(net): nearest clean fib value to net (for off-grid detection).
// =====================================================================

// Index of the fib level at or immediately below |net|
int FibFloorIdx(double net)
{
   double absN = MathAbs(net);
   return FibAtOrBelow(absN);   // reuse existing helper
}

// The next signed fib value in the + direction from current net
double FibNextUp(double net)
{
   double absN = MathAbs(net);
   if(net >= 0.005)
   {
      // Positive: go to next higher positive fib
      int ci = FibAtOrBelow(absN);
      int ti = FC(ci + 1);
      return FIB[ti];
   }
   else if(net <= -0.005)
   {
      // Negative: go toward zero — next less-negative fib
      int ci = FibAtOrBelow(absN);
      int ti = MathMax(0, ci - 1);
      return (ti == 0 && FIB[0] >= absN - 0.0015) ? 0.0 : -FIB[ti];
   }
   else
   {
      // Flat or near-flat: first positive level
      return FIB[FC(FibStart)];
   }
}

// The next signed fib value in the − direction from current net
double FibNextDown(double net)
{
   double absN = MathAbs(net);
   if(net <= -0.005)
   {
      // Negative: go to next more-negative fib
      int ci = FibAtOrBelow(absN);
      int ti = FC(ci + 1);
      return -FIB[ti];
   }
   else if(net >= 0.005)
   {
      // Positive: go toward zero — next less-positive fib
      int ci = FibAtOrBelow(absN);
      int ti = MathMax(0, ci - 1);
      return (ti == 0 && FIB[0] >= absN - 0.0015) ? 0.0 : FIB[ti];
   }
   else
   {
      // Flat: first negative level
      return -FIB[FC(FibStart)];
   }
}

// Snap net to nearest fib level (handles off-grid manual trades)
// Returns the closest fib value on the signed ladder
double FibSnap(double net)
{
   if(MathAbs(net) < 0.005) return 0.0;
   double absN = MathAbs(net);
   double best = FIB[0]; double bestDist = MathAbs(absN - FIB[0]);
   for(int i = 1; i < FIB_LEN; i++)
   {
      double d = MathAbs(absN - FIB[i]);
      if(d < bestDist) { bestDist = d; best = FIB[i]; }
   }
   return (net > 0) ? best : -best;
}

// Is net on a clean fib level (within tolerance)?
bool IsOnGrid(double net)
{
   if(MathAbs(net) < 0.005) return true;  // flat is on-grid
   double absN = MathAbs(net);
   for(int i = 0; i < FIB_LEN; i++)
      if(MathAbs(FIB[i] - absN) < 0.0015) return true;
   return false;
}

// =====================================================================
// GetStepCount — v24 hysteresis latch
//
// Returns 2 when DoubleStepEnable and the latch g_x2Active is set.
// The latch is managed here every call (called every tick via UpdateLeftButtons):
//   ARM:   fib index >= DoubleStepOnFibIdx  → set latch
//   DISARM: fib index <= DoubleStepOffFibIdx → clear latch
//   IN BETWEEN: latch holds its previous state (no oscillation)
//
// Called from: CheckAutoStep, CheckBBExit, PT suite, CheckAutoDelev,
//              manual direction-delev buttons, STEP button labels.
// NOT called from DoAutoFibStep (opening signals always single-step).
// =====================================================================
int GetStepCount()
{
   if(!DoubleStepEnable) return 1;

   int ci = FibFloorIdx(g_cNet);

   // ARM threshold — turn x2 on
   if(ci >= DoubleStepOnFibIdx && !g_x2Active)
   {
      g_x2Active = true;
      Print(StringFormat("[x2] ARMED — fib%d >= on-threshold fib%d  net=%.4f", ci, DoubleStepOnFibIdx, g_cNet));
   }
   // DISARM threshold — clear latch only when sufficiently recovered
   else if(ci <= DoubleStepOffFibIdx && g_x2Active)
   {
      g_x2Active = false;
      Print(StringFormat("[x2] DISARMED — fib%d <= off-threshold fib%d  net=%.4f", ci, DoubleStepOffFibIdx, g_cNet));
   }
   // Between thresholds: latch holds

   return g_x2Active ? 2 : 1;
}

// =====================================================================
// v25 — SESSION, TIME & SPREAD FILTER HELPERS
// =====================================================================

// Returns true if the current server time is within the given HH:MM window.
// Handles midnight-spanning windows (e.g. 22:00–02:00).
bool TimeInZone(int startH, int startM, int endH, int endM)
{
   MqlDateTime dt; TimeToStruct(TimeCurrent(), dt);
   int now   = dt.hour * 60 + dt.min;
   int start = startH  * 60 + startM;
   int end   = endH    * 60 + endM;
   if(start == end) return false;        // zero-width zone = disabled
   if(start < end)  return (now >= start && now < end);
   return (now >= start || now < end);   // midnight-spanning
}

// Returns true if any enabled kill zone or custom session is active right now.
bool IsKillZoneActive()
{
   if(KZ1_Enable && TimeInZone(KZ1_StartHour, KZ1_StartMin, KZ1_EndHour, KZ1_EndMin)) return true;
   if(KZ2_Enable && TimeInZone(KZ2_StartHour, KZ2_StartMin, KZ2_EndHour, KZ2_EndMin)) return true;
   if(KZ3_Enable && TimeInZone(KZ3_StartHour, KZ3_StartMin, KZ3_EndHour, KZ3_EndMin)) return true;
   if(KZ4_Enable && TimeInZone(KZ4_StartHour, KZ4_StartMin, KZ4_EndHour, KZ4_EndMin)) return true;
   if(CS1_Enable && TimeInZone(CS1_StartHour, CS1_StartMin, CS1_EndHour, CS1_EndMin)) return true;
   if(CS2_Enable && TimeInZone(CS2_StartHour, CS2_StartMin, CS2_EndHour, CS2_EndMin)) return true;
   return false;
}

// Returns a short label for the currently active kill zone (for panel display).
string ActiveKillZoneName()
{
   if(KZ1_Enable && TimeInZone(KZ1_StartHour, KZ1_StartMin, KZ1_EndHour, KZ1_EndMin)) return "London Open";
   if(KZ2_Enable && TimeInZone(KZ2_StartHour, KZ2_StartMin, KZ2_EndHour, KZ2_EndMin)) return "NY AM";
   if(KZ3_Enable && TimeInZone(KZ3_StartHour, KZ3_StartMin, KZ3_EndHour, KZ3_EndMin)) return "London Close";
   if(KZ4_Enable && TimeInZone(KZ4_StartHour, KZ4_StartMin, KZ4_EndHour, KZ4_EndMin)) return "NY PM";
   if(CS1_Enable && TimeInZone(CS1_StartHour, CS1_StartMin, CS1_EndHour, CS1_EndMin)) return "Custom 1";
   if(CS2_Enable && TimeInZone(CS2_StartHour, CS2_StartMin, CS2_EndHour, CS2_EndMin)) return "Custom 2";
   return "---";
}

// Returns true if day-of-week is allowed.
bool IsDayAllowed()
{
   MqlDateTime dt; TimeToStruct(TimeCurrent(), dt);
   switch(dt.day_of_week)
   {
      case 1: return TF_Monday;
      case 2: return TF_Tuesday;
      case 3: return TF_Wednesday;
      case 4: return TF_Thursday;
      case 5:
      {
         if(!TF_Friday) return false;
         if(TF_FridayEarlyClose && dt.hour >= TF_FridayCloseHour) return false;
         return true;
      }
      default: return false; // Sat/Sun always blocked
   }
}

// Full time-filter check: day OK AND (no kill zones enabled OR current time in a kill zone).
// If all kill zones are disabled → filter passes whenever the day is allowed.
bool IsTimeFilterPassed()
{
   if(!TF_Enable) return true;
   if(!IsDayAllowed()) return false;
   // If at least one kill zone is enabled, require being inside one of them.
   bool anyKZEnabled = KZ1_Enable || KZ2_Enable || KZ3_Enable || KZ4_Enable || CS1_Enable || CS2_Enable;
   if(anyKZEnabled && !IsKillZoneActive()) return false;
   return true;
}

// Current spread in broker points.
int GetCurrentSpread()
{
   return (int)SymbolInfoInteger(g_tradeSymbol, SYMBOL_SPREAD);
}

// Returns true if spread is within the allowed limit.
bool IsSpreadOK()
{
   if(!SF_Enable) return true;
   return (GetCurrentSpread() <= SF_MaxSpreadPts);
}

// Session risk cap check — returns true if opens are still permitted.
bool IsSessionRiskCapOK()
{
   if(!SRC_Enable) return true;

   // Reset daily trade counter at midnight
   MqlDateTime dt; TimeToStruct(TimeCurrent(), dt);
   dt.hour=0; dt.min=0; dt.sec=0;
   datetime todayMid = StructToTime(dt);
   if(todayMid != g_srcTradeCountDate)
   {
      g_srcTradeCountDate = todayMid;
      g_dailyTradeCount   = 0;
   }

   // Max open positions hard cap
   if(SRC_MaxOpenPos > 0 && g_cN >= SRC_MaxOpenPos) return false;

   // Daily closed loss cap
   if(SRC_MaxDailyLoss > 0 && g_dailyClosedPnL <= -(MathAbs(SRC_MaxDailyLoss))) return false;

   // Daily closed profit cap (take a day off when target hit)
   if(SRC_MaxDailyProfit > 0 && g_dailyClosedPnL >= SRC_MaxDailyProfit) return false;

   // Daily trade count cap
   if(SRC_MaxDailyTrades > 0 && g_dailyTradeCount >= SRC_MaxDailyTrades) return false;

   return true;
}

// Master gate function — call before any automated new OPEN.
// Returns true = allowed, false = blocked.
// Always updates g_opensAllowed, g_tfAllowed, g_sfAllowed, g_srcAllowed, g_filterReason.
bool CheckOpensAllowed()
{
   g_tfAllowed  = IsTimeFilterPassed();
   g_sfAllowed  = IsSpreadOK();
   g_srcAllowed = IsSessionRiskCapOK();

   g_filterReason = "";
   if(!g_tfAllowed)
   {
      MqlDateTime dt; TimeToStruct(TimeCurrent(), dt);
      bool anyKZEnabled = KZ1_Enable || KZ2_Enable || KZ3_Enable || KZ4_Enable || CS1_Enable || CS2_Enable;
      if(!IsDayAllowed())
         g_filterReason = StringFormat("DAY BLOCKED (%s)", EnumToString((ENUM_DAY_OF_WEEK)dt.day_of_week));
      else if(anyKZEnabled)
         g_filterReason = StringFormat("OUT OF KILL ZONE  %02d:%02d", dt.hour, dt.min);
      else
         g_filterReason = "TIME FILTER";
   }
   else if(!g_sfAllowed)
      g_filterReason = StringFormat("SPREAD %d > MAX %d pts", GetCurrentSpread(), SF_MaxSpreadPts);
   else if(!g_srcAllowed)
   {
      if(SRC_MaxOpenPos > 0 && g_cN >= SRC_MaxOpenPos)
         g_filterReason = StringFormat("MAX POS %d/%d", g_cN, SRC_MaxOpenPos);
      else if(SRC_MaxDailyLoss > 0 && g_dailyClosedPnL <= -MathAbs(SRC_MaxDailyLoss))
         g_filterReason = StringFormat("DAILY LOSS CAP $%.0f", SRC_MaxDailyLoss);
      else if(SRC_MaxDailyProfit > 0 && g_dailyClosedPnL >= SRC_MaxDailyProfit)
         g_filterReason = StringFormat("DAILY PROFIT TARGET $%.0f", SRC_MaxDailyProfit);
      else if(SRC_MaxDailyTrades > 0 && g_dailyTradeCount >= SRC_MaxDailyTrades)
         g_filterReason = StringFormat("DAILY TRADES %d/%d", g_dailyTradeCount, SRC_MaxDailyTrades);
   }

   g_opensAllowed = g_tfAllowed && g_sfAllowed && g_srcAllowed;
   return g_opensAllowed;
}

// =====================================================================
// FibSignalTarget — THE correct target for BUY/SELL signal steps.
//
// KEY DESIGN RULE:
//   The SIGNAL direction determines the SIGN of the target.
//   The |currentNet| determines the SIZE (fib index) of the target.
//   Zero-crossing is INTENTIONAL — a SELL from +0.03 goes to -0.05,
//   not to +0.02.  A BUY from -0.13 goes to +0.21, not to -0.08.
//
// Formula:
//   BUY  (dir>=0): target = +FIB[ fibIndex(|net|) + 1 ]
//   SELL (dir< 0): target = -FIB[ fibIndex(|net|) + 1 ]
//   Flat  (|net|<0.005): target = ±FIB[FibStart]
//
// Examples (FibStart=0):
//   net=+0.01, SELL → -FIB[1] = -0.02  →  open 0.03 SELL  ✓
//   net=+0.03, SELL → -FIB[3] = -0.05  →  open 0.08 SELL  ✓
//   net=+0.05, SELL → -FIB[4] = -0.08  →  open 0.13 SELL  ✓
//   net=-0.13, BUY  → +FIB[6] = +0.21  →  open 0.34 BUY   ✓
//   net=+0.03, BUY  → +FIB[3] = +0.05  →  open 0.02 BUY   ✓
//
// NOTE: FibNextUp/FibNextDown are KEPT UNCHANGED — they correctly step
//   *toward zero* and are used only by DoStep and auto-deleverage.
// =====================================================================
double FibSignalTarget(int dir, double net)
{
   double absN = MathAbs(net);
   int ti;
   if(absN < 0.005)
      ti = FC(FibStart);         // from flat, use minimum fib level
   else
   {
      int ci = FibAtOrBelow(absN);
      ti = FC(ci + 1);
   }
   return (dir >= 0) ? FIB[ti] : -FIB[ti];
}

// =====================================================================
// NLot(dir): lot to open for one (or more) steps in direction dir.
//   Uses FibSignalTarget — always crosses zero toward signal direction.
// =====================================================================
double NLot(int steps = 1, int dir = 1)
{
   if(g_fibOvr >= 0) return FIB[FC(g_fibOvr)];

   double net = g_cNet;
   if(!IsOnGrid(net) && g_cN > 0)
      net = FibSnap(net);

   // Walk steps using FibSignalTarget, updating intermediate net each time
   double targetNet = net;
   for(int s = 0; s < steps; s++)
      targetNet = FibSignalTarget(dir, targetNet);

   double lot = NormalizeDouble(MathAbs(targetNet - g_cNet), 2);
   return MathMax(lot, FIB[FC(FibStart)]);
}

//--------------------------------------------------------------------
// CalcAutoLot — AutoLeverage override. Falls back to NLot.
//--------------------------------------------------------------------
double CalcAutoLot(int steps = 1, int dir = 1)
{
   if(!AutoLeverage) return NLot(steps, dir);

   double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   if(freeMargin <= 0) return NLot(steps, dir);

   double marginPerLot = SymbolInfoDouble(g_tradeSymbol, SYMBOL_MARGIN_INITIAL);
   if(marginPerLot <= 0)
   {
      double contractSize = SymbolInfoDouble(g_tradeSymbol, SYMBOL_TRADE_CONTRACT_SIZE);
      double price        = SymbolInfoDouble(g_tradeSymbol, SYMBOL_ASK);
      long   leverage     = AccountInfoInteger(ACCOUNT_LEVERAGE);
      if(leverage <= 0) leverage = 100;
      marginPerLot = (contractSize * price) / (double)leverage;
   }
   if(marginPerLot <= 0) return NLot(steps, dir);

   double lotRaw  = (freeMargin * RiskPctPerTrade / 100.0) / marginPerLot;
   double lotStep = SymbolInfoDouble(g_tradeSymbol, SYMBOL_VOLUME_STEP);
   if(lotStep <= 0) lotStep = 0.01;
   lotRaw = MathFloor(lotRaw / lotStep) * lotStep;
   lotRaw = NormalizeDouble(lotRaw, 2);
   lotRaw = MathMax(FIB[FC(FibStart)], MathMin(FIB[MathMin(FibMax, FIB_LEN-1)], lotRaw));
   return lotRaw;
}

void DoGoldenNorm(int dir)
{
   // v18: use gross side matching dir for fib level
   double grossSide = (dir >= 0) ? g_cGrossL : g_cGrossS;
   if(grossSide < 0.005) { DoOpen(dir, FIB[FC(FibStart)], "GOLDEN:first"); return; }
   int nearest = 0; double nearDist = DBL_MAX;
   for(int i = 0; i < FIB_LEN; i++)
   {
      double d = MathAbs(FIB[i] - grossSide);
      if(d < nearDist) { nearDist = d; nearest = i; }
   }
   int ti = FC(nearest+1);
   double openLot = NormalizeDouble(FIB[ti] - grossSide, 2);
   if(openLot > 0.005) { DoOpen(dir, openLot, StringFormat("GOLDEN fib%d→%d +%.2f", nearest, ti, openLot)); return; }
   ti = FC(nearest+2);
   openLot = NormalizeDouble(FIB[ti] - grossSide, 2);
   if(openLot > 0.005) DoOpen(dir, openLot, StringFormat("GOLDEN fib%d→%d +%.2f", nearest, ti, openLot));
}

ENUM_ORDER_TYPE_FILLING FM()
{
   long f = SymbolInfoInteger(g_tradeSymbol, SYMBOL_FILLING_MODE);
   if((f & SYMBOL_FILLING_FOK) != 0) return ORDER_FILLING_FOK;
   if((f & SYMBOL_FILLING_IOC) != 0) return ORDER_FILLING_IOC;
   return ORDER_FILLING_RETURN;
}

string FmtDur(datetime openTime)
{
   int s = (int)(TimeCurrent()-openTime);
   if(s < 60)   return StringFormat("%ds", s);
   if(s < 3600) return StringFormat("%dm%ds", s/60, s%60);
   return StringFormat("%dh%dm", s/3600, (s%3600)/60);
}

//====================================================================
// DEAL HISTORY
//====================================================================
void ScanNewDeals()
{
   MqlDateTime dt; TimeToStruct(TimeCurrent(), dt);
   dt.hour=0; dt.min=0; dt.sec=0;
   datetime todayMid = StructToTime(dt);
   if(todayMid != g_lastDayDate)
   {
      g_lastDayDate     = todayMid;
      g_dailyClosedPnL  = 0.0;
      g_lastDeal        = 0;
      g_dailyTradeCount = 0;   // v25: reset daily trade count at midnight
   }

   if(!HistorySelect(todayMid, TimeCurrent()+1)) return;
   int total = HistoryDealsTotal();
   for(int i = 0; i < total; i++)
   {
      ulong tkt = HistoryDealGetTicket(i);
      if(tkt == 0 || tkt <= g_lastDeal) continue;
      if(HistoryDealGetString(tkt, DEAL_SYMBOL) != g_tradeSymbol) continue;
      if(Magic > 0 && (int)HistoryDealGetInteger(tkt, DEAL_MAGIC) != Magic) continue;
      ENUM_DEAL_ENTRY en = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(tkt, DEAL_ENTRY);
      if(en != DEAL_ENTRY_OUT && en != DEAL_ENTRY_INOUT) continue;

      double pnl = HistoryDealGetDouble(tkt, DEAL_PROFIT)
                 + HistoryDealGetDouble(tkt, DEAL_SWAP)
                 + HistoryDealGetDouble(tkt, DEAL_COMMISSION);
      datetime dt2 = (datetime)HistoryDealGetInteger(tkt, DEAL_TIME);

      g_dailyClosedPnL += pnl;
      if(dt2 >= g_sessionStart) g_sessionClosedPnL += pnl;

      // v25: increment daily trade count for session risk cap
      g_dailyTradeCount++;

      for(int j = MAX_HIST_ROWS-1; j > 0; j--) g_hist[j] = g_hist[j-1];
      g_hist[0].t    = dt2;
      g_hist[0].dir  = (int)HistoryDealGetInteger(tkt, DEAL_TYPE);
      g_hist[0].lots = HistoryDealGetDouble(tkt, DEAL_VOLUME);
      g_hist[0].pnl  = pnl;
      if(g_histCount < MAX_HIST_ROWS) g_histCount++;
      if(tkt > g_lastDeal) g_lastDeal = tkt;
   }
}

void ScanStartupHistory()
{
   if(!HistorySelect(g_sessionStart, TimeCurrent()+1)) return;
   int total = HistoryDealsTotal();

   datetime tmpT[];
   int      tmpDir[];
   double   tmpLots[], tmpPnl[];
   int tc = 0;

   for(int i = 0; i < total; i++)
   {
      ulong tkt = HistoryDealGetTicket(i); if(tkt == 0) continue;
      if(HistoryDealGetString(tkt, DEAL_SYMBOL) != g_tradeSymbol) continue;
      if(Magic > 0 && (int)HistoryDealGetInteger(tkt, DEAL_MAGIC) != Magic) continue;
      ENUM_DEAL_ENTRY en = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(tkt, DEAL_ENTRY);
      if(en != DEAL_ENTRY_OUT && en != DEAL_ENTRY_INOUT) continue;

      double pnl = HistoryDealGetDouble(tkt, DEAL_PROFIT)
                 + HistoryDealGetDouble(tkt, DEAL_SWAP)
                 + HistoryDealGetDouble(tkt, DEAL_COMMISSION);

      g_sessionClosedPnL += pnl;
      if(tkt > g_lastDeal) g_lastDeal = tkt;

      ArrayResize(tmpT,    tc+1);
      ArrayResize(tmpDir,  tc+1);
      ArrayResize(tmpLots, tc+1);
      ArrayResize(tmpPnl,  tc+1);
      tmpT[tc]    = (datetime)HistoryDealGetInteger(tkt, DEAL_TIME);
      tmpDir[tc]  = (int)HistoryDealGetInteger(tkt, DEAL_TYPE);
      tmpLots[tc] = HistoryDealGetDouble(tkt, DEAL_VOLUME);
      tmpPnl[tc]  = pnl;
      tc++;
   }

   for(int i = tc-1; i >= 0 && g_histCount < MAX_HIST_ROWS; i--)
   {
      g_hist[g_histCount].t    = tmpT[i];
      g_hist[g_histCount].dir  = tmpDir[i];
      g_hist[g_histCount].lots = tmpLots[i];
      g_hist[g_histCount].pnl  = tmpPnl[i];
      g_histCount++;
   }

   MqlDateTime dt; TimeToStruct(TimeCurrent(), dt);
   dt.hour=0; dt.min=0; dt.sec=0;
   datetime todayMid = StructToTime(dt);
   if(!HistorySelect(todayMid, TimeCurrent()+1)) return;
   total = HistoryDealsTotal();
   for(int i = 0; i < total; i++)
   {
      ulong tkt = HistoryDealGetTicket(i); if(tkt == 0) continue;
      if(HistoryDealGetString(tkt, DEAL_SYMBOL) != g_tradeSymbol) continue;
      if(Magic > 0 && (int)HistoryDealGetInteger(tkt, DEAL_MAGIC) != Magic) continue;
      ENUM_DEAL_ENTRY en = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(tkt, DEAL_ENTRY);
      if(en != DEAL_ENTRY_OUT && en != DEAL_ENTRY_INOUT) continue;
      g_dailyClosedPnL += HistoryDealGetDouble(tkt, DEAL_PROFIT)
                        + HistoryDealGetDouble(tkt, DEAL_SWAP)
                        + HistoryDealGetDouble(tkt, DEAL_COMMISSION);
   }
}

//====================================================================
// BB READER
//====================================================================
double RBB(int b)
{
   if(g_bb == INVALID_HANDLE) return 0;
   double v[]; ArraySetAsSeries(v, true);
   if(CopyBuffer(g_bb, b, 0, 1, v) >= 1) return v[0];
   return 0;
}

double RBBExit(int b)
{
   if(g_bbExitH == INVALID_HANDLE) return 0;
   double v[]; ArraySetAsSeries(v, true);
   if(CopyBuffer(g_bbExitH, b, 0, 1, v) >= 1) return v[0];
   return 0;
}

//====================================================================
// EXECUTION
//====================================================================
//--------------------------------------------------------------------
// DoOpen — v17: reason string embedded in trade comment + Print log.
// All callers must pass a reason so every open is traceable.
// The comment appears in MT5 Toolbox → Trade and Account History.
// Format recommendation: "WHY dir fibOLD→fibNEW +lot"
// e.g. "SIG:BUY fib5→6 +0.08" or "DELEV:-1 fib7→6 +0.05"
//--------------------------------------------------------------------
void DoOpen(int d, double l, string reason = "FibHedge")
{
   if(l < 0.005) return;
   trade.SetDeviationInPoints(20);
   trade.SetTypeFilling(FM());
   if(Magic > 0) trade.SetExpertMagicNumber(Magic);

   // Capture state before open for the log
   int    ciBefore = (d == 1) ? FibAtOrBelow(g_cGrossL) : FibAtOrBelow(g_cGrossS);
   double netBefore = g_cNet;

   string comment = StringFormat("FHP|%s", reason);
   if(StringLen(comment) > 31) comment = StringSubstr(comment, 0, 31);

   bool ok;
   if(d == 1) ok = trade.Buy( l, g_tradeSymbol, 0, 0, 0, comment);
   else       ok = trade.Sell(l, g_tradeSymbol, 0, 0, 0, comment);

   string dirStr = (d == 1) ? "BUY " : "SELL";
   if(ok)
      Print(StringFormat("[OPEN] %s %.2f  grossL=%.2f grossS=%.2f net=%.2f  sideFib=%d  reason=%s",
                         dirStr, l, g_cGrossL, g_cGrossS, netBefore, ciBefore, reason));
   else
      Print(StringFormat("[OPEN FAIL] %s %.2f  reason=%s  err=%d",
                         dirStr, l, reason, trade.ResultRetcode()));
}

void DoCloseAll(string reason = "MANUAL")
{
   Print(StringFormat("[CLOSE-ALL] reason=%s  net=%.2f  pnl=%.2f  pos=%d",
                      reason, g_cNet, g_cPnL, g_cN));
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue;
      double vol = PositionGetDouble(POSITION_VOLUME);
      double pnl = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
      Print(StringFormat("  [CLOSE] tkt=%llu  vol=%.2f  pnl=%.2f  reason=%s", t, vol, pnl, reason));
      trade.PositionClose(t);
   }
}

//--------------------------------------------------------------------
// ====================================================================
// CalcSkin — v26: depth-scaled skin size.
//
// Returns the lot size of the runner to keep after banking.
// Scales with current net fib depth so runners are meaningful at depth.
//
//   skinFibIdx = max(SkinIdx, FibFloorIdx(g_cNet) - SkinOffset)
//   If x2 latch active: skinFibIdx -= X2SkinReduce  (faster deleveraging)
//   Clamped to [SkinIdx .. FibMax].
//
// Examples with SkinIdx=0, SkinOffset=3, X2SkinReduce=2:
//   net fib 2  → skinFib = max(0, 2-3)=0   → skin=0.01
//   net fib 5  → skinFib = max(0, 5-3)=2   → skin=0.03
//   net fib 7  → skinFib = max(0, 7-3)=4   → skin=0.08
//   net fib 9  → skinFib = max(0, 9-3)=6   → skin=0.21
//   net fib 9 + x2 → skinFib = max(0,6-2)=4 → skin=0.08 (faster unwind)
//
// Skin=false always returns 0 (full close on bank).
// ====================================================================
double CalcSkin()
{
   if(!Skin) return 0.0;
   int ci = FibFloorIdx(g_cNet);   // current net fib depth index
   int si = MathMax(SkinIdx, ci - SkinOffset);
   if(g_x2Active) si = MathMax(SkinIdx, si - X2SkinReduce);
   si = MathMin(si, MathMin(FibMax, FIB_LEN-1));
   return FIB[si];
}

// DoBank — v26: skin computed by CalcSkin() (depth-scaled, x2-aware).
//
// BUG FIXED: old code trimmed every profitable ticket to mk lots,
//   leaving mk × numProfitableTickets net. e.g. 7 tickets × 0.01 = 0.07 net.
//
// CORRECT behaviour:
//   targetNet = sign(currentNet) × FIB[SkinIdx]   (one skin unit total)
//   needed    = |currentNet − targetNet|
//   Close dominant-side positions, most-profitable first, until `needed`
//   is fully covered. The result is net = exactly ±FIB[SkinIdx].
//
//   Skin=false: targetNet = 0 → close ALL dominant-side positions fully.
//   |net| already ≤ skin: nothing to do.
//
// Opposing positions (the hedge) are left untouched — they form part of
// the skin-level book and define the next fib resequence base.
//--------------------------------------------------------------------
void DoBank(string reason = "MANUAL")
{
   RefreshCache();
   double skin   = CalcSkin();
   double net    = g_cNet;
   int    ciL    = FibAtOrBelow(g_cGrossL), ciS = FibAtOrBelow(g_cGrossS);

   // targetNet: ±skin in current net direction, or 0 when Skin=false
   double targetNet;
   if     (net >  0.005) targetNet =  skin;
   else if(net < -0.005) targetNet = -skin;
   else return;  // flat — nothing to do

   double needed = NormalizeDouble(MathAbs(net - targetNet), 2);

   Print(StringFormat("[BANK] reason=%s  grossL=%.4f(fib%d) grossS=%.4f(fib%d)  net=%.4f→%.4f  close=%.4f  pnl=%.2f  skin=%.4f",
                      reason, g_cGrossL, ciL, g_cGrossS, ciS, net, targetNet, needed, g_cPnL, skin));

   if(needed < 0.005)
   {
      Print("[BANK] net already at/within skin level — nothing to do.");
      return;
   }

   // Close dominant-side positions (most profitable first) until needed=0
   int closeType = (net > 0) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL;
   double rem    = needed;

   while(rem > 0.001)
   {
      // Find most-profitable closeable position on dominant side
      ulong  best  = 0;
      double bestP = -DBL_MAX;
      double bestV = 0;
      for(int i = 0; i < PositionsTotal(); i++)
      {
         ulong t = PositionGetTicket(i); if(!PM(t)) continue;
         if((int)PositionGetInteger(POSITION_TYPE) != closeType) continue;
         double p = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
         double v = PositionGetDouble(POSITION_VOLUME);
         if(p > bestP) { bestP = p; best = t; bestV = v; }
      }
      if(best == 0) break;   // no more dominant-side positions

      double cv = NormalizeDouble(MathMin(bestV, rem), 2);
      if(cv < 0.005) break;

      Print(StringFormat("  [BANK-CLOSE] tkt=%llu  type=%s  vol=%.4f  closing=%.4f  pnl=%.2f",
                         best, closeType==POSITION_TYPE_BUY?"BUY":"SELL", bestV, cv, bestP));

      if(cv >= bestV - 0.001) trade.PositionClose(best);
      else                    trade.PositionClosePartial(best, cv);

      rem = NormalizeDouble(rem - cv, 2);
   }

   if(rem > 0.005)
      Print(StringFormat("[BANK] WARNING: could not close full needed=%.4f, remaining=%.4f (opposing hedge may be larger than net)", needed, rem));
}

//--------------------------------------------------------------------
// DoStep: reduce net by `steps` fib levels, closing most-profitable
// same-direction positions. reason explains what triggered the step.
//--------------------------------------------------------------------
void DoStep(int steps, string reason = "MANUAL")
{
   // Step the net position 'steps' levels TOWARD ZERO on the signed ladder.
   // If net > 0: target = FibNextDown(net)  → close some longs (or open shorts)
   // If net < 0: target = FibNextUp(net)    → close some shorts (or open longs)
   // If net ≈ 0: nothing to do.
   //
   // Closes most-profitable on the side we're reducing. If not enough
   // closeable positions, opens opposite side for shortfall.
   RefreshCache();

   double net = g_cNet;
   if(MathAbs(net) < 0.005) return;

   // Snap off-grid
   double netSnapped = IsOnGrid(net) ? net : FibSnap(net);

   double targetNet = netSnapped;
   for(int s = 0; s < steps; s++)
      targetNet = (targetNet > 0.0005) ? FibNextDown(targetNet)
                                       : (targetNet < -0.0005) ? FibNextUp(targetNet) : 0.0;

   double needed = NormalizeDouble(MathAbs(net - targetNet), 2);
   if(needed < 0.005) return;

   // Which side to close: if net>0 we close longs; if net<0 we close shorts
   bool closeIsLong   = (net > 0);
   int  closeType     = closeIsLong ? POSITION_TYPE_BUY : POSITION_TYPE_SELL;
   int  openDir       = closeIsLong ? -1 : 1;   // open opposite if shortfall

   int fibBefore = FibFloorIdx(net);
   int fibAfter  = FibFloorIdx(targetNet);

   Print(StringFormat("[STEP] reason=%s  net=%.4f(fib%d)→%.4f(fib%d)  close=%.4f  pnl=%.2f",
                      reason, net, fibBefore, targetNet, fibAfter, needed, g_cPnL));

   double mk  = CalcSkin();
   double rem = needed;

   // --- CLOSE PHASE: most profitable first ---
   while(rem > 0.001)
   {
      ulong best = 0; double bestP = -DBL_MAX, bestV = 0;
      for(int i = 0; i < PositionsTotal(); i++)
      {
         ulong t = PositionGetTicket(i); if(!PM(t)) continue;
         if((int)PositionGetInteger(POSITION_TYPE) != closeType) continue;
         double p = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
         if(p > bestP) { bestP = p; best = t; bestV = PositionGetDouble(POSITION_VOLUME); }
      }
      if(best == 0) break;
      double kp = (mk > 0 && bestP > 0) ? mk : 0;
      double mx = NormalizeDouble(bestV - kp, 2); if(mx < 0.005) break;
      double cv = NormalizeDouble(MathMin(mx, rem), 2); if(cv < 0.005) break;
      Print(StringFormat("  [STEP-CLOSE] tkt=%llu vol=%.4f closing=%.4f pnl=%.2f", best, bestV, cv, bestP));
      if(cv >= bestV - 0.001 && kp <= 0) trade.PositionClose(best);
      else trade.PositionClosePartial(best, cv);
      rem = NormalizeDouble(rem - cv, 2);
   }

   // --- OPEN PHASE: shortfall if not enough positions to close ---
   if(rem > 0.005)
   {
      string openR = StringFormat("%s:STEP-SHORTFALL +%.4f", reason, rem);
      Print(StringFormat("  [STEP-OPEN] shortfall=%.4f  opening %s %.4f", rem, openDir>0?"BUY":"SELL", rem));
      DoOpen(openDir, rem, openR);
   }
}

//--------------------------------------------------------------------
// DoFlip: open hedge in new direction, then bank winning legs.
//--------------------------------------------------------------------
void DoFlip(int d)
{
   // v18: flip uses gross side in the direction we're opening
   double grossSide = (d >= 0) ? g_cGrossL : g_cGrossS;
   int ci    = FibAtOrBelow(grossSide);
   int ti    = FC(ci+1);
   double nl = NormalizeDouble(FIB[ti] - grossSide, 2);
   if(nl < 0.005) { ti = FC(ci+2); nl = NormalizeDouble(FIB[ti] - grossSide, 2); }
   if(nl < 0.005) nl = FIB[FC(FibStart)];
   nl = MathMin(nl, FIB[MathMin(FibMax, FIB_LEN-1)]);
   string flipReason = StringFormat("FLIP:%s fib%d→%d +%.2f", (d==1?"BUY":"SEL"), ci, ti, nl);
   DoOpen(d, nl, flipReason);
   DoBank(flipReason);
}

//--------------------------------------------------------------------
// DoDeleverage — v22: mirrors BUY/SELL but prefers closes over opens.
//
// signalMode=true (buttons + Alkemix signal with DELEVERAGE ON):
//   Target = FibSignalTarget(dir, net) × steps
//   = SAME target net as pressing BUY/SELL normally, but achieved by
//     CLOSING positions first, opening shortfall only if needed.
//
//   steps=1 example (net=+0.05, SELL):
//     target = -0.08  → close 0.13 long (all longs) → net=-0.08 ✓
//     If only 0.05 long: close 0.05 → net=0 → open 0.08 SELL → net=-0.08 ✓
//
//   steps=2 example (net=+0.05, SELL, double-step active):
//     step1 = FibSignalTarget(-1, 0.05) = -0.08
//     step2 = FibSignalTarget(-1, -0.08) = -0.13
//     target = -0.13 → close available longs → open shortfall → net=-0.13
//
// signalMode=false (CheckAutoDelev only — reduce exposure TOWARD ZERO):
//   Target = FibNextUp/Down × steps, never crosses zero.
//   CLOSE ONLY — never opens new positions. Gets as close as possible
//   with existing positions. Opening would INCREASE exposure, wrong here.
//--------------------------------------------------------------------
void DoDeleverage(int dir, string reason = "MANUAL", bool signalMode = true, int steps = 1)
{
   RefreshCache();
   double net = g_cNet;

   // Snap off-grid net to nearest fib before computing step
   double netSnapped = IsOnGrid(net) ? net : FibSnap(net);

   // Compute target net: walk `steps` levels in signal direction
   double targetNet = netSnapped;
   for(int s = 0; s < steps; s++)
   {
      if(signalMode)
         targetNet = FibSignalTarget(dir, targetNet);   // crosses zero, matches BUY/SELL button
      else
      {
         // toward-zero only: never cross zero
         double candidate = (dir >= 0) ? FibNextUp(targetNet) : FibNextDown(targetNet);
         // Stop at zero if we'd cross it
         if(dir >= 0 && candidate > 0.005 && targetNet < -0.005) break;
         if(dir <  0 && candidate < -0.005 && targetNet > 0.005) break;
         targetNet = candidate;
      }
   }

   double needed = NormalizeDouble(MathAbs(targetNet - net), 2);

   if(needed < 0.005)
   {
      Print(StringFormat("[DELEV] nothing to do — net=%.4f already at/near target=%.4f", net, targetNet));
      return;
   }

   // To move net UP (dir>=0): close SHORTS; to move DOWN (dir<0): close LONGS
   int closeType = (dir >= 0) ? POSITION_TYPE_SELL : POSITION_TYPE_BUY;
   int openDir   = dir;

   int fibBefore = FibFloorIdx(net);
   int fibAfter  = FibFloorIdx(targetNet);

   // How much of the closing side is available
   double available = 0;
   for(int i = 0; i < PositionsTotal(); i++)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue;
      if((int)PositionGetInteger(POSITION_TYPE) != closeType) continue;
      available += PositionGetDouble(POSITION_VOLUME);
   }
   available = NormalizeDouble(available, 2);

   double closeAmt = NormalizeDouble(MathMin(needed, available), 2);
   // signalMode=false: CLOSE ONLY — no opens even if shortfall
   double openAmt  = signalMode ? NormalizeDouble(needed - closeAmt, 2) : 0.0;

   Print(StringFormat("[DELEV] reason=%s  mode=%s  steps=%d  dir=%d  net=%.4f(fib%d)→%.4f(fib%d)  close%s=%.4f  open%s=%.4f",
                      reason, signalMode?"SIGNAL":"REDUCE", steps, dir,
                      net, fibBefore, targetNet, fibAfter,
                      dir>=0?"SHORT":"LONG", closeAmt,
                      dir>=0?"BUY":"SELL",   openAmt));

   // --- CLOSE PHASE: most profitable first ---
   double rem = closeAmt;
   while(rem > 0.001)
   {
      ulong best = 0; double bestP = -DBL_MAX, bestV = 0;
      for(int i = 0; i < PositionsTotal(); i++)
      {
         ulong t = PositionGetTicket(i); if(!PM(t)) continue;
         if((int)PositionGetInteger(POSITION_TYPE) != closeType) continue;
         double p = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
         double v = PositionGetDouble(POSITION_VOLUME);
         if(p > bestP) { bestP = p; best = t; bestV = v; }
      }
      if(best == 0) break;
      double cv = NormalizeDouble(MathMin(bestV, rem), 2); if(cv < 0.005) break;
      double posP = 0;
      if(PositionSelectByTicket(best)) posP = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
      Print(StringFormat("  [DELEV-CLOSE] tkt=%llu vol=%.4f closing=%.4f pnl=%.2f", best, bestV, cv, posP));
      if(cv >= bestV - 0.001) trade.PositionClose(best);
      else                    trade.PositionClosePartial(best, cv);
      rem = NormalizeDouble(rem - cv, 2);
   }

   // --- OPEN PHASE: shortfall (signal mode only) ---
   if(signalMode && openAmt > 0.005)
   {
      // v25: gate shortfall opens — if filter blocks opens, log and skip
      bool autoAction = (StringFind(reason, "BTN") < 0);
      if(autoAction && !CheckOpensAllowed())
      {
         Print(StringFormat("  [FILTER] DELEV-SHORTFALL open blocked — %s", g_filterReason));
      }
      else
      {
         string openR = StringFormat("%s:DELEV-SHORTFALL net%.4f→%.4f +%.4f", reason, net, targetNet, openAmt);
         Print(StringFormat("  [DELEV-OPEN] shortfall=%.4f  opening %s %.4f",
                            openAmt, dir>=0?"BUY":"SELL", openAmt));
         DoOpen(openDir, openAmt, openR);
      }
   }
   else if(!signalMode && rem > 0.005)
   {
      Print(StringFormat("  [DELEV-REDUCE] closed %.4f of %.4f needed (close-only mode, no opens)",
                         closeAmt - rem, needed));
   }
}

void DoMicroReduce(double lots)
{
   // Close 'lots' from the dominant side (largest gross), most profitable first.
   // This is used by the panel REDUCE button in manual micro-scalp mode.
   if(lots < 0.005) return;
   RefreshCache();
   // Dominant side = whichever gross is larger; if equal, use net sign
   int closeType;
   if(g_cGrossL > g_cGrossS + 0.0015)      closeType = POSITION_TYPE_BUY;
   else if(g_cGrossS > g_cGrossL + 0.0015) closeType = POSITION_TYPE_SELL;
   else if(g_cNet >= 0)                     closeType = POSITION_TYPE_BUY;
   else                                     closeType = POSITION_TYPE_SELL;

   ulong best = 0; double bestP = -DBL_MAX;
   for(int i = 0; i < PositionsTotal(); i++)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue;
      if((int)PositionGetInteger(POSITION_TYPE) != closeType) continue;
      double p = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
      if(p > bestP) { bestP = p; best = t; }
   }
   if(best == 0 || !PositionSelectByTicket(best)) return;
   double v  = PositionGetDouble(POSITION_VOLUME);
   double cv = NormalizeDouble(MathMin(lots, v), 2); if(cv < 0.005) return;
   Print(StringFormat("[MICRO-REDUCE] tkt=%llu vol=%.4f closing=%.4f pnl=%.2f", best, v, cv, bestP));
   if(cv >= v - 0.001) trade.PositionClose(best);
   else                trade.PositionClosePartial(best, cv);
}

//--------------------------------------------------------------------
// DoCloseSmall — close MicroLots from ANY position (most profitable
// across all positions, either side). Used by the CLOSE 0.01 button.
// Different from DoMicroReduce: doesn't filter by dominant side —
// lets you manually pick off any profitable position at 0.01 lots.
//--------------------------------------------------------------------
void DoCloseSmall(double lots, string reason = "BTN:CLOSE_SMALL")
{
   if(lots < 0.005) return;
   RefreshCache();
   ulong best = 0; double bestP = -DBL_MAX, bestV = 0;
   for(int i = 0; i < PositionsTotal(); i++)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue;
      double p = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
      double v = PositionGetDouble(POSITION_VOLUME);
      if(p > bestP) { bestP = p; best = t; bestV = v; }
   }
   if(best == 0 || !PositionSelectByTicket(best)) return;
   double cv = NormalizeDouble(MathMin(lots, bestV), 2); if(cv < 0.005) return;
   Print(StringFormat("[CLOSE-SMALL] tkt=%llu vol=%.4f closing=%.4f pnl=%.2f reason=%s", best, bestV, cv, bestP, reason));
   if(cv >= bestV - 0.001) trade.PositionClose(best);
   else                    trade.PositionClosePartial(best, cv);
}

//--------------------------------------------------------------------
// CheckAutoStep — v21: per-lot denominator fixed.
// OLD: ppl = g_cPnL / g_cAbsN  → divides by raw lots (tiny for 0.01 lot → ppl×100)
// NEW: ppl = g_cPnL / numBaseUnits  where numBaseUnits = g_cAbsN / FIB[FibStart]
//      "25 per unit" = fire when each FIB[FibStart]-lot increment of net has made $25.
//      For 0.01 lot net and FibStart=0 (0.01): ppl = g_cPnL / 1 = raw $ P&L → fires at $25. ✓
//      For 0.05 lot net: ppl = g_cPnL / 5 → fires at $125 total. Scales cleanly.
//--------------------------------------------------------------------
void CheckAutoStep()
{
   if(!UseAutoStep) return;
   if(g_cN == 0) { g_stepFired = 0; g_lastStepPnL = 0; return; }

   datetime barNow = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(barNow == g_lastStepBar) return;

   if(AutoStepPerLot)
   {
      if(g_cAbsN < 0.005) return;
      double baseUnit  = FIB[FC(FibStart)];              // e.g. 0.01
      double numUnits  = g_cAbsN / baseUnit;             // e.g. 0.05/0.01 = 5
      double ppl       = (numUnits > 0.001) ? g_cPnL / numUnits : 0;  // $ per base unit
      if(ppl >= PerLotTrigger)
      {
         DoStep(GetStepCount(), StringFormat("AUTOSTEP_PPL ppl=%.2f/%.2f units=%.1f net=%.4f pnl=%.2f",
                                ppl, PerLotTrigger, numUnits, g_cNet, g_cPnL));
         g_stepFired++;
         g_lastStepPnL = g_cPnL;
         g_lastStepBar = barNow;
         g_anyPTBar    = barNow;
      }
   }
   else
   {
      double thr = (g_stepFired == 0) ? FixedStep1
                 : (g_stepFired == 1) ? FixedStep2
                 : (g_stepFired == 2) ? FixedStep3
                 : FixedBankAll;
      if(g_cPnL >= thr)
      {
         string stepReason = StringFormat("AUTOSTEP_%d pnl=%.2f/%.2f", g_stepFired+1, g_cPnL, thr);
         if(g_stepFired >= 3) DoBank(stepReason); else DoStep(GetStepCount(), stepReason);
         g_stepFired++;
         g_lastStepPnL = g_cPnL;
         g_lastStepBar = barNow;
         g_anyPTBar    = barNow;
      }
   }
   if(g_cPnL <= 0) g_stepFired = 0;
}

//--------------------------------------------------------------------
// NEW v12 — CheckAutoSignal
//
// Reads Alkemix Trend buffers once per completed bar (index 1).
// A "new signal" is a bar that has a signal when the prior bar didn't.
// This avoids acting on repainting mid-bar values.
//
// On new BUY signal:
//   deleverage ON  → DoDeleverage(1)  (reduce short / toward long)
//   deleverage OFF → DoAutoFibStep(1) (open buy to next fib level)
//
// On new SELL signal:
//   deleverage ON  → DoDeleverage(-1) (reduce long / toward short)
//   deleverage OFF → DoAutoFibStep(-1)(open sell to next fib level)
//
// NEVER calls DoCloseAll — positions are only exited via the exit
// options or manual buttons.
//--------------------------------------------------------------------
bool AlkSignalActive(double val)
{
   // Inactive buffers return DBL_MAX (~1.8e308) on this MT5 build.
   // MathAbs(val - EMPTY_VALUE) overflows to inf, cannot be used.
   // Reject any value with magnitude >= 1e100 as a sentinel.
   if(val == EMPTY_VALUE) return false;
   if(val >=  1e100) return false;
   if(val <= -1e100) return false;
   return (MathAbs(val) > 1e-10 && MathAbs(val) > AlkSignalThresh);
}

void DoAutoFibStep(int dir, string triggerReason = "SIG")
{
   // ================================================================
   // SIGNED-NET FIB LADDER — v20 corrected implementation:
   //
   // dir=+1 (BUY):  targetNet = +FIB[ fibIdx(|net|) + 1 ]  (always +)
   // dir=-1 (SELL): targetNet = -FIB[ fibIdx(|net|) + 1 ]  (always -)
   //
   // This means a SELL from +0.03 → -0.05 (NOT +0.02).
   // A BUY from -0.13 → +0.21 (NOT -0.08).
   // Lot = |targetNet - currentNet| opened in signal direction.
   // ================================================================

   // v25: gate automated opens through time/spread/risk filters
   // Manual button calls pass "BTN" as triggerReason — not filtered here
   // (manual overrides are never blocked; the user decides consciously).
   bool isAutomatic = (StringFind(triggerReason, "BTN") < 0);
   if(isAutomatic && !CheckOpensAllowed())
   {
      Print(StringFormat("[FILTER] DoAutoFibStep blocked — %s  reason=%s", triggerReason, g_filterReason));
      return;
   }
   RefreshCache();

   double net    = g_cNet;
   double netSnapped = net;

   // Snap off-grid net to nearest fib level
   if(!IsOnGrid(net) && g_cN > 0)
   {
      netSnapped = FibSnap(net);
      Print(StringFormat("[SNAP] net=%.4f off-grid, snapped to %.4f for step calc", net, netSnapped));
   }

   // v20 FIX: use FibSignalTarget — always goes to next fib on signal side
   double targetNet = FibSignalTarget(dir, netSnapped);
   double lot       = NormalizeDouble(MathAbs(targetNet - net), 2);

   if(lot < 0.005)
   {
      lot = FIB[FC(FibStart)];
      Print(StringFormat("[AUTOFIB] WARNING: computed lot < 0.005, using min %.2f", lot));
   }

   // Clamp to FibMax
   double maxLot = FIB[MathMin(FibMax, FIB_LEN-1)];
   lot = MathMin(lot, maxLot);

   int    fibBefore = FibFloorIdx(net);
   int    fibAfter  = FibFloorIdx(targetNet);
   string dirStr    = (dir >= 0) ? "BUY" : "SEL";
   string reason    = StringFormat("%s:%s net=%.4f→%.4f(fib%d→%d) open=%.4f",
                                   triggerReason, dirStr, net, targetNet, fibBefore, fibAfter, lot);

   DoOpen(dir, lot, reason);
   g_fibOvr = -1;
}

void CheckAutoSignal()
{
   if(!g_autoSig) return;
   if(g_alkHandle == INVALID_HANDLE) return;

   // Run once per completed AlkTF bar
   datetime barTime = iTime(_Symbol, AlkTF, 1);
   if(barTime == 0 || barTime == g_lastSigBar) return;

   // BBExit signal-bar skip — count down on each new bar
   if(g_delevSkipCount > 0)
   {
      if(barTime != g_bbExitSigBlockUntil)
      {
         g_bbExitSigBlockUntil = barTime;
         g_delevSkipCount--;
      }
      if(g_delevSkipCount > 0)
      {
         Print(StringFormat("[BB_SKIP] Signal blocked — %d bars remaining", g_delevSkipCount));
         return;
      }
   }

   // Read 3 bars: [0]=forming, [1]=last closed, [2]=bar before that.
   // Both buffers carry a price value on EVERY bar (the MA line itself),
   // so "is value present" cannot distinguish signal direction.
   // Instead: detect which buffer TRANSITIONED between bar[2] and bar[1].
   // Bar[2] is fully settled when we process bar[1] (keyed on AlkTF bar close).
   double buf7[], buf8[];
   ArraySetAsSeries(buf7, true);
   ArraySetAsSeries(buf8, true);
   if(CopyBuffer(g_alkHandle, AlkBuyBuffer,  0, 3, buf7) < 3) return;
   if(CopyBuffer(g_alkHandle, AlkSellBuffer, 0, 3, buf8) < 3) return;

   double buyVal1  = buf7[1],  sellVal1 = buf8[1];   // bar[1] — last closed
   double buyVal2  = buf7[2],  sellVal2 = buf8[2];   // bar[2] — previous

   bool buyActive1  = AlkSignalActive(buyVal1);
   bool sellActive1 = AlkSignalActive(sellVal1);
   bool buyActive2  = AlkSignalActive(buyVal2);
   bool sellActive2 = AlkSignalActive(sellVal2);

   // Which buffer just appeared (was absent bar[2], present bar[1])?
   bool buyNew  = buyActive1  && !buyActive2;
   bool sellNew = sellActive1 && !sellActive2;

   // Determine bar[1] direction:
   // Priority 1: fresh transition on exactly one buffer
   // Priority 2: only one buffer currently active (persistent signal)
   // Priority 3: ambiguous — bar1Dir stays 0
   int bar1Dir = 0;
   if     (buyNew  && !sellNew)             bar1Dir =  1;
   else if(sellNew && !buyNew)              bar1Dir = -1;
   else if(buyActive1  && !sellActive1)     bar1Dir =  1;
   else if(sellActive1 && !buyActive1)      bar1Dir = -1;



   // === INITIALISATION ===
   // g_lastAlkDir == 0 means "never seen a clean directional bar yet".
   // Stay in init until we get a clean bar1Dir (+1 or -1).
   // Do NOT set g_lastAlkDir here — let the direction-change check below
   // handle it. When g_lastAlkDir==0, both newBuy/newSell checks will
   // evaluate (bar1Dir==±1 && 0 != ±1) = true → first signal fires. ✓
   if(g_lastAlkDir == 0)
   {
      g_lastSigBar = barTime;
      if(bar1Dir == 0)
      {
         Print(StringFormat("[SIG] Waiting for first clean direction (flat/ambiguous) @ %s",
                            TimeToString(barTime, TIME_MINUTES)));
         return;  // try again next bar
      }
      Print(StringFormat("[SIG] First direction seen: %s @ %s — falling through to trade",
                         bar1Dir > 0 ? "BUY" : "SELL",
                         TimeToString(barTime, TIME_MINUTES)));
      // *** fall through with g_lastAlkDir still 0 so newBuy/newSell fires ***
   }

   // Mark this bar as processed NOW — prevents re-entry on subsequent ticks
   g_lastSigBar = barTime;

   // Detect direction CHANGE — this is the only thing we act on
   bool newBuy  = (bar1Dir ==  1 && g_lastAlkDir != 1);
   bool newSell = (bar1Dir == -1 && g_lastAlkDir != -1);

   if(!newBuy && !newSell)
   {
      // Same direction continuing or ambiguous — no action, update tracking
      if(bar1Dir != 0) g_lastAlkDir = bar1Dir;
      return;
   }

   // Direction changed — log it, then update tracking
   Print(StringFormat("[SIG] Direction CHANGE: %s → %s @ %s",
                      g_lastAlkDir==1?"BUY":g_lastAlkDir==-1?"SELL":"FLAT",
                      bar1Dir==1?"BUY":"SELL",
                      TimeToString(barTime, TIME_MINUTES)));
   if(bar1Dir != 0) g_lastAlkDir = bar1Dir;

   if(newBuy && newSell)
   {
      Print("[SIG] WARNING: BUY+SELL both active @ ", TimeToString(barTime, TIME_MINUTES), " — acting on BUY.");
      newSell = false;
   }

   RefreshCache();
   string barStr = TimeToString(barTime, TIME_MINUTES);

   // v28: On direction flip, reset the BB skin-step cycle.
   // BBExit_SkinResetOnFlip=true means: while in skin mode, BB touches step the
   // skin runner down one level per touch. On the NEXT flip, clear that cycle so
   // the new position starts fresh from FIB[FibStart] with BB exits re-enabled.
   // DoBank is NOT called here — the skin runner has already been stepped down by
   // CheckBBExit; we simply clear the flag so DoAutoFibStep enters from the
   // current (stepped-down) net cleanly, producing a FibStart-sized first trade.
   if(g_bbTouchFired && BBExit_SkinResetOnFlip && Skin)
   {
      g_bbTouchFired = false;
      Print(StringFormat("[SIG] BB skin-step cycle reset on flip — net=%.4f  new cycle starts from FIB[FibStart]=%.4f",
                         g_cNet, FIB[FC(FibStart)]));
   }

   if(newBuy)
   {
      int fibNow = FibFloorIdx(g_cNet);
      double snapNet = IsOnGrid(g_cNet) ? g_cNet : FibSnap(g_cNet);
      double tgt = FibSignalTarget(1, snapNet);
      g_lastSigClr = GRN;
      if(g_delev)
      {
         g_lastSigStr = StringFormat("^ BUY[D] @ %s net%.4f→%.4f(fib%d)", barStr, g_cNet, tgt, fibNow);
         DoDeleverage(1, "SIG-DELEV:BUY", true);
      }
      else
      {
         g_lastSigStr = StringFormat("^ BUY @ %s net%.4f→%.4f(fib%d)", barStr, g_cNet, tgt, fibNow);
         DoAutoFibStep(1, "SIG");
      }
   }
   else
   {
      int fibNow = FibFloorIdx(g_cNet);
      double snapNet = IsOnGrid(g_cNet) ? g_cNet : FibSnap(g_cNet);
      double tgt = FibSignalTarget(-1, snapNet);
      g_lastSigClr = RED;
      if(g_delev)
      {
         g_lastSigStr = StringFormat("v SEL[D] @ %s net%.4f→%.4f(fib%d)", barStr, g_cNet, tgt, fibNow);
         DoDeleverage(-1, "SIG-DELEV:SEL", true);
      }
      else
      {
         g_lastSigStr = StringFormat("v SEL @ %s net%.4f→%.4f(fib%d)", barStr, g_cNet, tgt, fibNow);
         DoAutoFibStep(-1, "SIG");
      }
   }
}

//--------------------------------------------------------------------
// CheckBBExit — v26:
//
// BBExit_TouchMode=false (default): fires when bar[1] CLOSES beyond the band.
//   Same as before — candle must close outside to trigger.
//
// BBExit_TouchMode=true: fires when bar[1] HIGH touches upper band (long book)
//   or bar[1] LOW touches lower band (short book). More aggressive — fires on
//   any wick extension, not just closes. Good for catching quick reversals.
//
// BBExit_X2Steps: DoStep(2) instead of DoStep(GetStepCount()).
//   Independent of x2 latch — lets you set aggressive per-BB-candle unwinding
//   without needing to be at depth. Combine with touch mode for fastest exit.
//
// BBExit_SkipSignalBars: after BBExit fires, block new signal-TF opens for
//   N bars on the signal chart TF (AlkTF). Prevents re-entry while price is
//   still extended and the BB exit is harvesting profits.
//   e.g. M3 chart, BBExitTF=M15, SkipSignalBars=5 → ~15min cooldown on entries.
//
// Cooldown: once per BBExitTF completed bar.
//--------------------------------------------------------------------
//--------------------------------------------------------------------
// CheckBBExit — v28 revised:
//
// Standard mode (BBExit_SkinResetOnFlip=false):
//   Fires DoStep(sc) when bar[1] touches/closes beyond BB band.
//   sc = BBExit_X2Steps ? 2 : GetStepCount().
//
// Skin-step mode (BBExit_TouchMode=true AND BBExit_SkinResetOnFlip=true AND Skin=true):
//   Each BB band touch steps the skin runner DOWN by 1 fib level.
//   DoStep(1) is always used (ignores x2 — stepping a skin, not a big book).
//   g_bbTouchFired is set true as long as we're in this mode (cleared on flip).
//   When the net reaches flat (skin fully stepped away), stops firing.
//   On the next signal flip, CheckAutoSignal clears g_bbTouchFired and
//   DoAutoFibStep starts fresh from FIB[FibStart] — the BB cycle resets.
//
// Cooldown: once per BBExitTF completed bar.
//--------------------------------------------------------------------
void CheckBBExit()
{
   if(!g_autoPT)               return;
   if(ExitMode != EXIT_BB_STEP) return;
   if(g_bbExitH == INVALID_HANDLE) return;
   if(g_cN == 0 || g_cAbsN < 0.005) return;

   datetime barTime = iTime(_Symbol, BBExitTF, 1);
   if(barTime == 0 || barTime == g_lastBBExit) return;

   // Read BB bands at bar[1]
   double bbVal[]; ArraySetAsSeries(bbVal, true);
   double upper = 0, lower = 0;
   if(CopyBuffer(g_bbExitH, 1, 1, 1, bbVal) >= 1) upper = bbVal[0];
   if(CopyBuffer(g_bbExitH, 2, 1, 1, bbVal) >= 1) lower = bbVal[0];
   if(upper <= 0 || lower <= 0) return;

   bool fire = false;
   if(BBExit_TouchMode)
   {
      // Touch mode: check bar[1] high/low against bands
      double highs[], lows[];
      ArraySetAsSeries(highs, true); ArraySetAsSeries(lows, true);
      double bar1High = 0, bar1Low = 0;
      if(CopyHigh(_Symbol, BBExitTF, 1, 1, highs) >= 1) bar1High = highs[0];
      if(CopyLow (_Symbol, BBExitTF, 1, 1, lows)  >= 1) bar1Low  = lows[0];
      if(g_cNet >  0.005 && bar1High >= upper) fire = true;
      if(g_cNet < -0.005 && bar1Low  <= lower) fire = true;
   }
   else
   {
      // Close mode: bar must close beyond the band
      double closePrices[]; ArraySetAsSeries(closePrices, true);
      double bar1Close = 0;
      if(CopyClose(_Symbol, BBExitTF, 1, 1, closePrices) >= 1) bar1Close = closePrices[0];
      if(bar1Close <= 0) return;
      if(g_cNet >  0.005 && bar1Close >= upper) fire = true;
      if(g_cNet < -0.005 && bar1Close <= lower) fire = true;
   }

   if(fire)
   {
      g_lastBBExit = barTime;

      // v28 SKIN-STEP MODE:
      // When BBExit_SkinResetOnFlip=true + TouchMode=true + Skin=true:
      //   Always DoStep(1) — one fib level per touch, stepping the skin runner down.
      //   g_bbTouchFired stays true throughout the skin-step cycle so CheckAutoSignal
      //   knows to reset on the next flip.
      //   Once net is flat (skin fully consumed) we stop — nothing left to step.
      bool skinStepMode = (BBExit_TouchMode && BBExit_SkinResetOnFlip && Skin);

      int sc;
      if(skinStepMode)
         sc = 1;   // always 1 per touch in skin-step mode
      else
         sc = BBExit_X2Steps ? MathMax(2, GetStepCount()) : GetStepCount();

      string modeTag = skinStepMode ? "SKIN_STEP" : (BBExit_TouchMode ? "TOUCH" : "CLOSE");
      string bbReason = StringFormat("BB_EXIT[%s%s] net=%.4f %s band=%.5f",
                        modeTag, sc>1?" x2":"",
                        g_cNet, g_cNet>0?"H>=upper":"L<=lower",
                        g_cNet>0 ? upper : lower);
      DoStep(sc, bbReason);
      Print("[PT] BB_EXIT fired — ", bbReason);

      // Mark the skin-step cycle as active — cleared on next signal flip
      if(skinStepMode)
         g_bbTouchFired = true;

      // v26: block signal entries on signal TF for SkipSignalBars bars
      if(BBExit_SkipSignalBars > 0)
      {
         datetime sigBar0 = iTime(_Symbol, AlkTF, 0);
         g_bbExitSigBlockUntil = sigBar0;
         g_delevSkipCount = BBExit_SkipSignalBars;
         Print(StringFormat("[BB_EXIT] Blocking signal entries for %d %s bars",
                            BBExit_SkipSignalBars, EnumToString(AlkTF)));
      }
   }
}

//--------------------------------------------------------------------
// v13 — CheckAutoProfitTake
//
// Master switch: g_autoPT (runtime) / AutoProfitTake (input).
// All sub-modes are independent bool inputs; multiple can fire per bar
// but only ONE action fires per bar (highest-priority first):
//
//  Priority order (highest to lowest):
//  1. PT_DD_Close   : hard stop — DoCloseAll if open P&L <= -PtDDClose
//  2. PT_PnL_Close  : full close when P&L >= PtPnlClose
//  3. PT_Equity_Pct : bank when equity >= balance * PtEqPct%
//  4. PT_PnL_Bank   : bank when P&L >= PtPnlBank
//  5. PT_Trail_Step : step if P&L drops PtTrailPct% from peak
//  6. PT_PnL_Step   : step when P&L >= PtPnlStep
//
// One action per BAR to prevent cascading same-bar triggers.
//--------------------------------------------------------------------
void CheckAutoProfitTake()
{
   if(!g_autoPT) return;

   // BUG C FIX v16: peak reset was AFTER the early return — dead code.
   // Moved before the guard so trailing stop resets between trade cycles.
   if(g_cN == 0) { g_ptPeakPnL = 0.0; return; }
   if(g_cAbsN < 0.005) return;

   // Update session peak
   if(g_cPnL > g_ptPeakPnL) g_ptPeakPnL = g_cPnL;

   // BUG 4 FIX: check shared cooldown — AutoStep may have already fired this bar
   datetime barNow = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(barNow == g_ptLastBar || barNow == g_anyPTBar) return;

   // --- 1. HARD STOP ---
   if(PT_DD_Close && PtDDClose > 0 && g_cPnL <= -(MathAbs(PtDDClose)))
   {
      Print(StringFormat("[PT] PT_DD_CLOSE fired. P&L=%.2f <= -%.2f", g_cPnL, PtDDClose));
      g_ptLastBar = barNow; g_anyPTBar = barNow; g_ptPeakPnL = 0.0;
      DoCloseAll(StringFormat("PT_DD_CLOSE pnl=%.2f", g_cPnL)); return;
   }
   // --- 2. PROFIT CLOSE (v29: adjusted for close-side commission) ---
   if(PT_PnL_Close && PtPnlClose > 0)
   {
      // Effective target = user goal + commission to close all positions.
      // Close-side commission has not been paid yet; we must earn it too.
      double commAdj = g_cCommClose;   // set by RefreshCache each tick
      double adjTarget = PtPnlClose + commAdj;
      if(g_cPnL >= adjTarget)
      {
         Print(StringFormat("[PT] PT_PNL_CLOSE fired. P&L=%.2f >= goal=%.2f + comm=%.2f (adj=%.2f)",
                            g_cPnL, PtPnlClose, commAdj, adjTarget));
         g_ptLastBar = barNow; g_anyPTBar = barNow; g_ptPeakPnL = 0.0;
         DoCloseAll(StringFormat("PT_PNL_CLOSE pnl=%.2f adj=%.2f", g_cPnL, adjTarget)); return;
      }
   }
   // --- 3. EQUITY % ---
   if(PT_Equity_Pct && PtEqPct > 0)
   {
      double bal = AccountInfoDouble(ACCOUNT_BALANCE);
      double eq  = AccountInfoDouble(ACCOUNT_EQUITY);
      if(bal > 0 && eq >= bal * PtEqPct / 100.0)
      {
         Print(StringFormat("[PT] PT_EQUITY_PCT fired. Eq=%.2f >= %.1f%% of Bal=%.2f", eq, PtEqPct, bal));
         g_ptLastBar = barNow; g_anyPTBar = barNow;
         DoBank(StringFormat("PT_EQ_PCT eq=%.2f/%.1f%%", eq, PtEqPct)); return;
      }
   }
   // --- 4. P&L BANK ---
   if(PT_PnL_Bank && PtPnlBank > 0 && g_cPnL >= PtPnlBank)
   {
      Print(StringFormat("[PT] PT_PNL_BANK fired. P&L=%.2f >= %.2f", g_cPnL, PtPnlBank));
      g_ptLastBar = barNow; g_anyPTBar = barNow;
      DoBank(StringFormat("PT_PNL_BANK pnl=%.2f", g_cPnL)); return;
   }
   // --- 5. TRAILING STEP ---
   if(PT_Trail_Step && PtTrailPct > 0 && g_ptPeakPnL > 0)
   {
      double dropPct = (g_ptPeakPnL - g_cPnL) / g_ptPeakPnL * 100.0;
      if(dropPct >= PtTrailPct)
      {
         Print(StringFormat("[PT] PT_TRAIL_STEP fired. Peak=%.2f PnL=%.2f Drop=%.1f%%", g_ptPeakPnL, g_cPnL, dropPct));
         g_ptLastBar = barNow; g_anyPTBar = barNow;
         g_ptPeakPnL = g_cPnL;
         DoStep(GetStepCount(), StringFormat("PT_TRAIL peak=%.2f drop=%.1f%%", g_ptPeakPnL, dropPct)); return;
      }
   }
   // --- 6. P&L STEP ---
   if(PT_PnL_Step && PtPnlStep > 0 && g_cPnL >= PtPnlStep)
   {
      Print(StringFormat("[PT] PT_PNL_STEP fired. P&L=%.2f >= %.2f", g_cPnL, PtPnlStep));
      g_ptLastBar = barNow; g_anyPTBar = barNow;
      DoStep(GetStepCount(), StringFormat("PT_PNL_STEP pnl=%.2f", g_cPnL)); return;
   }
}


//--------------------------------------------------------------------
// CheckAutoDelev — v23 CORRECTED BEHAVIOUR
//
// Deleverage is PASSIVE. It only sets/clears the g_delev flag.
// It NEVER calls DoDeleverage() on its own.
//
// The actual position-closing only happens inside CheckAutoSignal
// when a NEW signal bar fires. At that point, because g_delev=true,
// CheckAutoSignal routes through DoDeleverage instead of DoAutoFibStep.
//
// This means:
//   - Position count hits 8 → g_delev turns ON (flag only, no action)
//   - Market keeps moving, new bars open but no signal → nothing happens
//   - A new Alkemix signal fires → CheckAutoSignal sees g_delev=true
//     → calls DoDeleverage(dir, ...) → closes existing positions to
//     reach the next fib level instead of opening new ones
//   - If position count drops below threshold → g_delev turns OFF
//
// This is exactly how the BUY/SELL button works in deleverage mode:
// pressing BUY with deleverage ON closes shorts to advance the net,
// instead of opening new longs. CheckAutoSignal does the same thing
// automatically on each new signal bar.
//--------------------------------------------------------------------
void CheckAutoDelev()
{
   if(!AutoDelevEnable) return;

   int    ci      = FibFloorIdx(g_cNet);
   bool   tPos    = (DelevOnPositions > 0 && g_cN    >= DelevOnPositions);
   bool   tFib    = (DelevOnFibLevel  > 0 && ci      >= DelevOnFibLevel);
   bool   tLots   = (DelevOnNetLots   > 0 && g_cAbsN >= DelevOnNetLots - 0.0015);
   bool   anyFire = tPos || tFib || tLots;

   if(anyFire)
   {
      // Build display reason string
      g_delevReason = "";
      if(tPos)  g_delevReason += StringFormat("pos:%d/%d ", g_cN, DelevOnPositions);
      if(tFib)  g_delevReason += StringFormat("fib:%d/%d ", ci, DelevOnFibLevel);
      if(tLots) g_delevReason += StringFormat("|net|:%.4f/%.2f", g_cAbsN, DelevOnNetLots);
      string dr = g_delevReason; StringTrimRight(dr); g_delevReason = dr;

      // Set flag ON if not already — no trading action here
      if(!g_delev)
      {
         g_delev       = true;
         g_delevAutoOn = true;
         Print("FibHedge v23: Auto-Delev ON (flag only) — ", g_delevReason,
               " — will close on next signal instead of opening");
      }
   }
   else
   {
      // All conditions cleared — auto-turn off if it was auto-triggered
      if(DelevAutoOff && g_delevAutoOn && g_delev)
      {
         g_delev       = false;
         g_delevAutoOn = false;
         g_delevReason = "";
         Print("FibHedge v23: Auto-Delev OFF — all conditions cleared, resuming normal opens");
      }
      else if(!g_delev)
         g_delevReason = "";
   }
}

//--------------------------------------------------------------------
// v14 — DoPerPosTake
//
// Scans every open position. Closes any that have P&L >= PtPerPosPnl.
// Skin=true  → partial close to FIB[SkinIdx] runner.
// Skin=false → full close.
// Fires every tick (no bar cooldown) to catch intrabar spikes.
// This harvests individual legs independent of net direction.
//--------------------------------------------------------------------
void DoPerPosTake()
{
   if(!g_autoPT || !PT_PerPos || PtPerPosPnl <= 0) return;
   double mk = CalcSkin();
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue;
      if(!PositionSelectByTicket(t)) continue;
      double pnl = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
      if(pnl < PtPerPosPnl) continue;
      double vol = PositionGetDouble(POSITION_VOLUME);
      if(mk > 0 && vol > mk + 0.0015)
      {
         double cv = NormalizeDouble(vol - mk, 2);
         Print(StringFormat("[PT] PER_POS tkt=%llu  pnl=%.2f>=%.2f  vol=%.2f->%.2f(keep)", t, pnl, PtPerPosPnl, vol, mk));
         trade.PositionClosePartial(t, cv);
      }
      else if(!Skin)
      {
         Print(StringFormat("[PT] PER_POS_FULL tkt=%llu  pnl=%.2f>=%.2f  vol=%.2f", t, pnl, PtPerPosPnl, vol));
         trade.PositionClose(t);
      }
   }
}

//--------------------------------------------------------------------
// v16 — CheckSwapDrain
// Closes positions where accumulated swap < -PtSwapDrainUSD.
// This is the primary tool for stopping equity/balance divergence.
// Fires every tick so it catches positions at swap booking time.
// NOT gated by g_anyPTBar — swap drain is urgent and independent.
//--------------------------------------------------------------------
void CheckSwapDrain()
{
   if(!g_autoPT || !PT_SwapDrain || PtSwapDrainUSD <= 0) return;
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue;
      if(!PositionSelectByTicket(t)) continue;
      double swap = PositionGetDouble(POSITION_SWAP);
      if(swap < -MathAbs(PtSwapDrainUSD))
      {
         double vol = PositionGetDouble(POSITION_VOLUME);
         double pnl = PositionGetDouble(POSITION_PROFIT) + swap;
         Print(StringFormat("[PT] SWAP_DRAIN tkt=%llu  vol=%.2f  swap=%.2f  pnl=%.2f  thresh=-%.2f",
                            t, vol, swap, pnl, PtSwapDrainUSD));
         trade.PositionClose(t);
      }
   }
}

//--------------------------------------------------------------------
// v16 — CheckHoldDays
// Closes any position older than PtHoldDays days regardless of P&L.
// Forces book turnover and prevents ancient hedges from festering.
// Fires once per bar.
//--------------------------------------------------------------------
void CheckHoldDays()
{
   if(!g_autoPT || !PT_HoldDays || PtHoldDays <= 0) return;
   datetime barNow = iTime(_Symbol, PERIOD_CURRENT, 0);
   static datetime lastHoldBar = 0;
   if(barNow == lastHoldBar) return;
   datetime ageLimit = (datetime)(PtHoldDays * 86400);
   datetime now      = TimeCurrent();
   for(int i = PositionsTotal()-1; i >= 0; i--)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue;
      if(!PositionSelectByTicket(t)) continue;
      datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
      if((now - openTime) >= ageLimit)
      {
         double ageDays = (double)(now - openTime) / 86400.0;
         double pnl = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
         double vol = PositionGetDouble(POSITION_VOLUME);
         Print(StringFormat("[PT] HOLD_DAYS tkt=%llu  vol=%.2f  pnl=%.2f  age=%.1fd  limit=%.1fd",
                            t, vol, pnl, ageDays, PtHoldDays));
         trade.PositionClose(t);
         lastHoldBar = barNow;
      }
   }
}

//--------------------------------------------------------------------
// v16 — CheckEquityDD
// Closes all when equity drops PtEquityDDPct% below session high.
// Protects from runaway drawdown relative to session performance.
// Fires every tick (equity moves every tick).
//--------------------------------------------------------------------
void CheckEquityDD()
{
   if(!g_autoPT || !PT_EquityDD || PtEquityDDPct <= 0) return;
   double eq = AccountInfoDouble(ACCOUNT_EQUITY);
   if(eq > g_sessionEqHigh) g_sessionEqHigh = eq;
   if(g_sessionEqHigh <= 0) return;
   double ddPct = (g_sessionEqHigh - eq) / g_sessionEqHigh * 100.0;
   if(ddPct >= PtEquityDDPct)
   {
      Print(StringFormat("[PT] EQUITY_DD fired. EqHigh=%.2f  Eq=%.2f  DD=%.2f%%  limit=%.1f%%",
                         g_sessionEqHigh, eq, ddPct, PtEquityDDPct));
      g_sessionEqHigh = eq;
      DoCloseAll(StringFormat("PT_EQ_DD eq=%.2f dd=%.1f%%", eq, ddPct));
   }
}

//--------------------------------------------------------------------
// v26 — CheckBalanceGrowth
//
// Fires DoBank each time the account BALANCE has grown by BG_StepUSD
// from the session-start baseline. Tracks loops so targets compound:
//   Loop 0: next target = baseline + 1×BG_StepUSD
//   Loop 1: next target = baseline + 2×BG_StepUSD
//   etc.
//
// Uses BALANCE (not equity) because:
//   - Balance only grows when deals close (real locked-in profit)
//   - Equity fluctuates constantly with unrealised P&L
//   - In this hedge system, equity-to-balance divergence is expected
//     and accelerates as we deleverage — balance growth is the cleanest
//     measure of actual progress.
//
// Cooldown: once per bar (prevents double-fire on the same balance tick).
//--------------------------------------------------------------------
void CheckBalanceGrowth()
{
   if(!BG_Enable || BG_StepUSD <= 0) return;
   if(g_bgBaseline <= 0) return;   // not initialised yet

   double bal = AccountInfoDouble(ACCOUNT_BALANCE);
   if(bal < g_bgNextTarget) return;   // not there yet

   // Bar cooldown
   datetime barNow = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(barNow == g_bgLastBar) return;

   g_bgLoop++;
   g_bgLastBar = barNow;
   string bgReason = StringFormat("BG_LOOP_%d  bal=%.2f  target=%.2f  base=%.2f",
                                  g_bgLoop, bal, g_bgNextTarget, g_bgBaseline);
   Print(StringFormat("[BG] Loop %d fired — balance=%.2f crossed target=%.2f",
                      g_bgLoop, bal, g_bgNextTarget));
   DoBank(bgReason);

   // Advance to next target
   g_bgNextTarget = g_bgBaseline + (double)g_bgLoop * BG_StepUSD;
   Print(StringFormat("[BG] Next target: %.2f (loop %d)", g_bgNextTarget, g_bgLoop + 1));
}

//====================================================================
// OBJECT FACTORY
//====================================================================
string N(string s) { return "FHP_"+s; }

void Rect(string id, int x, int y, int w, int h, color bg, ENUM_BASE_CORNER cr, color bd=C'40,40,40', int bw=1)
{
   string n = N(id); ObjectDelete(0,n); ObjectCreate(0,n,OBJ_RECTANGLE_LABEL,0,0,0);
   ObjectSetInteger(0,n,OBJPROP_XDISTANCE,x);  ObjectSetInteger(0,n,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,n,OBJPROP_XSIZE,w);       ObjectSetInteger(0,n,OBJPROP_YSIZE,h);
   ObjectSetInteger(0,n,OBJPROP_BGCOLOR,bg);    ObjectSetInteger(0,n,OBJPROP_BORDER_TYPE,BORDER_FLAT);
   ObjectSetInteger(0,n,OBJPROP_COLOR,bd);      ObjectSetInteger(0,n,OBJPROP_WIDTH,bw);
   ObjectSetInteger(0,n,OBJPROP_CORNER,cr);     ObjectSetInteger(0,n,OBJPROP_BACK,false);
   ObjectSetInteger(0,n,OBJPROP_ZORDER,10);     ObjectSetInteger(0,n,OBJPROP_SELECTABLE,false);
}

void Lbl(string id, int x, int y, string t, color c, int f, ENUM_BASE_CORNER cr, bool b=false)
{
   string n = N(id); ObjectDelete(0,n); ObjectCreate(0,n,OBJ_LABEL,0,0,0);
   ObjectSetInteger(0,n,OBJPROP_XDISTANCE,x);  ObjectSetInteger(0,n,OBJPROP_YDISTANCE,y);
   ObjectSetString( 0,n,OBJPROP_TEXT,t);        ObjectSetInteger(0,n,OBJPROP_COLOR,c);
   ObjectSetInteger(0,n,OBJPROP_FONTSIZE,f);    ObjectSetString(0,n,OBJPROP_FONT,b?"Consolas Bold":"Consolas");
   ObjectSetInteger(0,n,OBJPROP_CORNER,cr);     ObjectSetInteger(0,n,OBJPROP_BACK,false);
   ObjectSetInteger(0,n,OBJPROP_ZORDER,20);     ObjectSetInteger(0,n,OBJPROP_SELECTABLE,false);
}

void Btn(string id, int x, int y, int w, int h, string t, color bg, color tc=C'200,215,228', int f=11)
{
   string n = N(id); ObjectDelete(0,n); ObjectCreate(0,n,OBJ_BUTTON,0,0,0);
   ObjectSetInteger(0,n,OBJPROP_XDISTANCE,x);  ObjectSetInteger(0,n,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,n,OBJPROP_XSIZE,w);       ObjectSetInteger(0,n,OBJPROP_YSIZE,h);
   ObjectSetString( 0,n,OBJPROP_TEXT,t);        ObjectSetInteger(0,n,OBJPROP_BGCOLOR,bg);
   ObjectSetInteger(0,n,OBJPROP_COLOR,tc);      ObjectSetInteger(0,n,OBJPROP_FONTSIZE,f);
   ObjectSetString( 0,n,OBJPROP_FONT,"Consolas Bold");
   ObjectSetInteger(0,n,OBJPROP_CORNER,CORNER_LEFT_UPPER);
   ObjectSetInteger(0,n,OBJPROP_BACK,false);    ObjectSetInteger(0,n,OBJPROP_ZORDER,30);
   ObjectSetInteger(0,n,OBJPROP_STATE,false);
}

void TL(string id, int x, int y, string t, color c, int f, bool b=false) { Lbl(id,x,y,t,c,f,CORNER_LEFT_UPPER,b); }
void TR(string id, int x, int y, int w, int h, color bg)                 { Rect(id,x,y,w,h,bg,CORNER_LEFT_UPPER); }

void UL(string id, string t, color c=C'200,215,228')
{
   string n = N(id); if(ObjectFind(0,n) < 0) return;
   ObjectSetString( 0,n,OBJPROP_TEXT,t);
   ObjectSetInteger(0,n,OBJPROP_COLOR,c);
}

void UBg(string id, color c)
{
   string n = N(id); if(ObjectFind(0,n) >= 0) ObjectSetInteger(0,n,OBJPROP_BGCOLOR,c);
}

void UW(string id, int w)
{
   string n = N(id); if(ObjectFind(0,n) >= 0) ObjectSetInteger(0,n,OBJPROP_XSIZE,MathMax(w,2));
}

void UBtn(string id, string t, color bg=clrNONE)
{
   string n = N(id); if(ObjectFind(0,n) < 0) return;
   ObjectSetString( 0,n,OBJPROP_TEXT,t);
   if(bg != clrNONE) ObjectSetInteger(0,n,OBJPROP_BGCOLOR,bg);
   ObjectSetInteger(0,n,OBJPROP_STATE,false);
}

void DelAll()
{
   for(int i = ObjectsTotal(0)-1; i >= 0; i--)
   {
      string nm = ObjectName(0,i);
      if(StringFind(nm,"FHP_") == 0) ObjectDelete(0,nm);
   }
   ChartRedraw(0);
}

//====================================================================
// BUILD -- LEFT BUTTONS
//====================================================================
void BuildLeftButtons()
{
   int x = LeftX, cy = LeftY, w = LBW;
   Rect("lb_bg",  x,cy,w, g_lpMin?28:2600, BK, CORNER_LEFT_UPPER, C'30,30,30',1);
   Rect("lb_hdr", x,cy,w, 26,              C'14,20,32', CORNER_LEFT_UPPER, C'0,120,200',1);
   TL("lb_hdr_t", x+8,cy+7,"TRADE PANEL",ACC,10,true);
   Btn("lb_min",  x+w-36,cy+3,32,20, g_lpMin?"[+]":"[-]", C'20,30,50',ACC,9);
   cy += 30;
   if(g_lpMin) return;

   // BUY / SELL
   int mainW = (w*2)/3 - LBG/2;
   int x2W   = w - mainW - LBG;
   Btn("lb_buy",  x,            cy, mainW, LBH+8, "^ BUY  0.01",  BUY, clrWhite, 13);
   Btn("lb_buy2", x+mainW+LBG,  cy, x2W,  LBH+8, "x2",           C'0,100,50', clrWhite, 11);
   cy += LBH+8+LBG;
   Btn("lb_sel",  x,            cy, mainW, LBH+8, "v SELL  0.01", SEL, clrWhite, 13);
   Btn("lb_sel2", x+mainW+LBG,  cy, x2W,  LBH+8, "x2",           C'120,15,15', clrWhite, 11);
   cy += LBH+8+LBG*2;

   // v25: Filter status bar — always visible, shows combined filter state
   TR("lb_flt_bg",  x,cy,w,28, BK2);
   TL("lb_flt_lbl", x+6,cy+7,  "FILTER:", DIM, 9);
   TL("lb_flt_val", x+60,cy+5, "loading...", DIM, 11, true);
   TL("lb_flt_spd", x+w-90,cy+7, "SPD:--", DIM, 9);
   cy += 32+LBG;

   // AUTO BUY/SELL button
   Btn("lb_auto", x,cy,w,LBH,"AUTO BUY/SELL: OFF", AUTO_OFF, DIM, 12); cy += LBH+LBG;
   // Signal status row
   TR("lb_sig_bg", x,cy,w,22,BK3);
   TL("lb_sig_val",x+6,cy+5,"last signal: ---", DIM, 9);
   cy += 26+LBG;

   // v13: AUTO PROFIT TAKE button
   Btn("lb_apt", x,cy,w,LBH,"AUTO PROFIT: OFF", C'30,30,30', DIM, 12); cy += LBH+LBG;
   // PT status row
   TR("lb_apt_bg",  x,cy,w,22,BK3);
   TL("lb_apt_val", x+6,cy+5,"DD/close/bank/trail/step: --", DIM, 9);
   cy += 26+LBG;

   // Deleverage toggle + direction buttons
   Btn("lb_dlv",  x,cy,w,LBH,"DELEVERAGE MODE: OFF",C'30,30,30',DIM,12); cy += LBH+LBG;
   // v14: Auto-deleverage trigger status row
   TR("lb_adlv_bg",  x,cy,w,22,BK3);
   TL("lb_adlv_val", x+6,cy+5,"auto-delev: disabled", DIM, 9);
   cy += 26+LBG;
   int hw = (LBW-LBG)/2;
   Btn("lb_dlvl", x,       cy,hw,LBH,"<- TOWARD LONG",  C'20,20,20',DIM,11);
   Btn("lb_dlvs", x+hw+LBG,cy,hw,LBH,"TOWARD SHORT ->", C'20,20,20',DIM,11);
   cy += LBH+LBG*2;

   // Flip + golden ratio
   int flipW = (w*2)/3 - LBG/2;
   int grW   = w - flipW - LBG;
   Btn("lb_flip", x,          cy, flipW, LBH, ">> FLIP  0.01", C'50,80,160', clrWhite,12);
   Btn("lb_gr",   x+flipW+LBG,cy, grW,  LBH, "phi",            C'60,40,100', clrWhite,11);
   cy += LBH+LBG*2;

   // Step / Bank
   Btn("lb_s1",x,cy,w,LBH,"v  STEP -1",      STP,clrWhite,13); cy += LBH+LBG;
   Btn("lb_s2",x,cy,w,LBH,"vv STEP -2",      STP,clrWhite,13); cy += LBH+LBG;
   Btn("lb_bk",x,cy,w,LBH,"$  BANK PROFITS", BNK,clrWhite,13); cy += LBH+LBG*2;

   // Lot override
   TR("lb_fov_bg",  x,cy,w,22,BK);
   TL("lb_fov_lbl", x+4,cy+5,"LOT OVERRIDE:",DIM,10);
   cy += 26;
   int fbw = (w-4*4)/3;
   for(int i=0;  i<3;  i++) Btn(StringFormat("lb_f%d",i),   x+i*(fbw+4),     cy,fbw,32,StringFormat("%.2g",FIB[i]), BK3,DIM,11);  cy += 36;
   for(int i=3;  i<6;  i++) Btn(StringFormat("lb_f%d",i),   x+(i-3)*(fbw+4), cy,fbw,32,StringFormat("%.2g",FIB[i]), BK3,DIM,11);  cy += 36;
   for(int i=6;  i<9;  i++) Btn(StringFormat("lb_f%d",i),   x+(i-6)*(fbw+4), cy,fbw,32,StringFormat("%.2g",FIB[i]), BK3,DIM,11);  cy += 36;
   for(int i=9;  i<12; i++) Btn(StringFormat("lb_f%d",i),   x+(i-9)*(fbw+4), cy,fbw,32,StringFormat("%.2g",FIB[i]), BK3,DIM,11);  cy += 36;
   Btn("lb_f12",x,          cy,fbw,32,StringFormat("%.2g",FIB[12]),BK3,DIM,11);
   Btn("lb_f13",x+(fbw+4),  cy,fbw,32,StringFormat("%.2g",FIB[13]),BK3,DIM,11);
   TL("lb_fmax_lbl",x+2*(fbw+4)+6,cy+10,"(max)",DIM,8);
   cy += 36+LBG;

   // Micro scalp
   TR("lb_mic_bg",  x,cy,w,22,BK);
   TL("lb_mic_lbl", x+4,cy+5,"MICRO SCALP:",DIM,10);
   TL("lb_mic_sz",  x+120,cy+5,"",DIM,10);
   cy += 26;
   Btn("lb_m_buy",x,        cy,hw,36,"+0.01 BUY",  BUY,clrWhite,11);
   Btn("lb_m_sel",x+hw+LBG, cy,hw,36,"+0.01 SELL", SEL,clrWhite,11);
   cy += 36+LBG;
   int hw3 = (w - LBG*2) / 3;
   Btn("lb_m_red", x,             cy, hw3, 32, "-0.01 NET",  C'200,120,0', clrWhite, 10);
   Btn("lb_c01",   x+hw3+LBG,    cy, hw3, 32, "CLOSE 0.01", C'80,40,120', clrWhite, 10);
   Btn("lb_snap",  x+2*(hw3+LBG),cy, hw3, 32, "SNAP GRID",  C'20,60,100', clrWhite, 10);
   cy += 32+LBG*2;

   // Close all
   Btn("lb_ca",x,cy,w,LBH+8,"X  CLOSE ALL",NUK,clrWhite,14);
}

//====================================================================
// BUILD -- RIGHT DATA PANEL
//====================================================================
void BuildRightPanel()
{
   int x = RightX, cy = RightY, w = RDW;
   Rect("rd_bg",  x,cy,w,g_rpMin?28:4200,BK,CORNER_LEFT_UPPER,C'30,30,30',1);
   Rect("rd_hdr", x,cy,w,26,C'14,20,32',CORNER_LEFT_UPPER,C'0,120,200',1);
   TL("rd_hdr_t", x+8,cy+7,"DATA PANEL",ACC,10,true);
   Btn("rd_min",  x+w-36,cy+3,32,20,g_rpMin?"[+]":"[-]",C'20,30,50',ACC,9);
   cy += 30;
   if(g_rpMin) return;

   // Net Exposure
   TR("rd_net_bg",  x,cy,w,96,BK2);
   TL("rd_net_lbl", x+8,    cy+6, "NET EXPOSURE",DIM,10);
   TL("rd_sym_lbl", x+w-160,cy+6, "",DIM,8);
   TL("rd_net_val", x+8,    cy+24,"0.00",TXT,28,true);
   TL("rd_net_dir", x+8+185,cy+34,"FLAT",DIM,18);
   TL("rd_fib_lbl", x+w-120,cy+6, "FIB LEVEL",DIM,10);
   TL("rd_fib_val", x+w-88, cy+24,"0",ACC,26,true);
   cy += 100;

   // Open P&L + progress bar
   TR("rd_pnl_bg",  x,cy,w,72,BK2);
   TL("rd_pnl_lbl", x+8,    cy+6, "OPEN P&L",DIM,10);
   TL("rd_pnl_val", x+8,    cy+22,"$0.00",TXT,22,true);
   TL("rd_pos_val", x+w-110,cy+26,"0 pos",DIM,12);
   TR("rd_pgb",x,cy+62,w,8,BK3);
   TR("rd_pgf",x,cy+62,4,8,ACC);
   cy += 80;

   // Next flip
   TR("rd_nxt_bg",  x,cy,w,52,BK3);
   TL("rd_nxt_lbl", x+8,cy+6, "NEXT FLIP LOT",DIM,10);
   TL("rd_nxt_val", x+8,cy+22,"0.01 lots",ACC,17,true);
   cy += 58;

   // NEW v12 — Auto signal status strip
   TR("rd_alk_bg",  x,cy,w,44,C'5,10,20');
   TL("rd_alk_ttl", x+8,cy+5, "ALKEMIX SIGNAL",DIM,9);
   TL("rd_alk_state",x+8, cy+20,"AUTO: OFF",DIM,11,true);
   TL("rd_alk_last", x+180,cy+22,"last: ---",DIM,9);
   cy += 50;

   // Signal / Market strip
   TR("rd_sig_bg",   x,cy,w,56,C'5,8,16');
   TL("rd_sig_ttl",  x+8,cy+5,"SPREAD & BANDS",DIM,9);
   TL("rd_sig_sl",   x+8,  cy+20,"SPD",DIM,9);
   TL("rd_sig_sv",   x+38, cy+18,"---",DIM,13,true);
   TL("rd_sig_bul",  x+150,cy+20,"BB-U",DIM,8);
   TL("rd_sig_buv",  x+190,cy+18,"---",TXT,11);
   TL("rd_sig_bll",  x+290,cy+20,"BB-L",DIM,8);
   TL("rd_sig_blv",  x+330,cy+18,"---",TXT,11);
   TL("rd_bbu_dst",  x+190,cy+40,"",DIM,9);
   TL("rd_bbl_dst",  x+330,cy+40,"",DIM,9);
   cy += 62;

   // Account totals
   TR("rd_acc_bg",x,cy,w,44,C'8,8,16');
   TL("rd_acc_bl",x+8,  cy+6, "BALANCE",DIM,9);
   TL("rd_acc_bv",x+80, cy+4, "$0.00",TXT,14,true);
   TL("rd_acc_el",x+220,cy+6, "EQUITY",DIM,9);
   TL("rd_acc_ev",x+290,cy+4, "$0.00",TXT,14,true);
   TL("rd_acc_ml",x+8,  cy+26,"MARGIN FREE",DIM,8);
   TL("rd_acc_mv",x+100,cy+22,"$0.00",DIM,13);
   cy += 48;

   // Closed P&L
   TR("rd_cls_bg",  x,cy,w,76,BK2);
   TL("rd_day_lbl", x+8,cy+6,  "DAILY CLOSED",DIM,10);
   TL("rd_day_val", x+8,cy+22, "$0.00",TXT,17,true);
   TL("rd_ses_lbl", x+8,cy+48, "SESSION CLOSED",DIM,10);
   TL("rd_ses_val", x+8,cy+62, "$0.00",TXT,17,true);
   cy += 84;

   // v26: Balance growth DoBank tracker
   TR("rd_bg_bg",   x,cy,w,52,BK3);
   TL("rd_bg_lbl",  x+8,cy+5, "BALANCE GROWTH BANK",DIM,9);
   TL("rd_bg_mode", x+8,cy+20,"OFF",DIM,10,true);
   TR("rd_bg_pgb",  x+8,cy+40,w-16,8,BK);
   TR("rd_bg_pgf",  x+8,cy+40,4,   8,ACC);
   cy += 58;

   // v26: Skin size display
   TR("rd_sk_bg",   x,cy,w,26,BK2);
   TL("rd_sk_lbl",  x+8,cy+6, "SKIN:",DIM,9);
   TL("rd_sk_val",  x+60,cy+4,"0.01",ACC,13,true);
   TL("rd_sk_inf",  x+140,cy+6,"",DIM,8);
   cy += 30;

   // Auto step
   TR("rd_ast_bg",   x,cy,w,68,BK3);
   TL("rd_ast_lbl",  x+8,cy+5, "AUTO STEP TARGET",DIM,9);
   TL("rd_ast_mode", x+8,cy+20,"OFF",DIM,9);
   TR("rd_ast_pgb",  x+8,cy+36,w-16,8,BK);
   TR("rd_ast_pgf",  x+8,cy+36,4,   8,ACC);
   TL("rd_ast_val",  x+8,cy+50,"",DIM,8);
   cy += 74;

   // Position table header
   TR("rd_pt_hdr",x,cy,w,24,C'15,15,25');
   TL("rd_pth0",x+8,  cy+5,"DIR",   C'80,100,180',9);
   TL("rd_pth1",x+65, cy+5,"LOTS",  C'80,100,180',9);
   TL("rd_pth2",x+130,cy+5,"ENTRY", C'80,100,180',9);
   TL("rd_pth3",x+240,cy+5,"P&L",   C'80,100,180',9);
   TL("rd_pth4",x+330,cy+5,"DUR",   C'80,100,180',9);
   cy += 26;

   // 15 position rows
   for(int i = 0; i < MAX_POS_ROWS; i++)
   {
      string ri = IntegerToString(i);
      TR("rd_pr"+ri, x,cy,w,28, i%2==0?BK:BK2);
      TL("rd_pd"+ri, x+8,  cy+6," ",DIM,10);
      TL("rd_pl"+ri, x+65, cy+6," ",DIM,10);
      TL("rd_pe"+ri, x+130,cy+6," ",DIM,9);
      TL("rd_pp"+ri, x+240,cy+6," ",DIM,10);
      TL("rd_pu"+ri, x+330,cy+6," ",DIM,9);
      Btn("rd_px"+ri,x+w-46,cy+4,42,20,"X",BK3,RED,9);
      cy += 30;
   }

   // Position summary
   TR("rd_sum",    x,cy,w,30,BK3);
   TL("rd_sum_val",x+8,cy+8,"FLAT  Open: $0.00",DIM,12,true);
   cy += 36;

   // Trade history
   TR("rd_hst_hdr",x,cy,w,24,C'15,15,25');
   TL("rd_hst_ttl", x+8,    cy+5,"TRADE HISTORY",C'80,100,180',10);
   Btn("rd_hst_clr",x+w-60, cy+2,56,20,"CLR",C'30,30,40',DIM,8);
   cy += 26;
   for(int i = 0; i < MAX_HIST_ROWS; i++)
   {
      string hi = IntegerToString(i);
      TR("rd_hr"+hi,x,cy,w,24,i%2==0?C'8,8,12':C'12,12,18');
      TL("rd_ht"+hi,x+8,  cy+5,"---",DIM,8);
      TL("rd_hd"+hi,x+68, cy+5,"---",DIM,9);
      TL("rd_hv"+hi,x+120,cy+5,"---",DIM,9);
      TL("rd_hp"+hi,x+185,cy+4,"---",DIM,10,true);
      TL("rd_hw"+hi,x+300,cy+5,"",   DIM,8);
      cy += 26;
   }
   TR("rd_hst_sum",x,cy,w,28,C'15,15,25');
   TL("rd_hst_sl", x+8,  cy+8,"SESSION CLOSED:",C'80,100,180',9);
   TL("rd_hst_sv", x+155,cy+6,"$0.00",TXT,13,true);
   TL("rd_hst_sc", x+280,cy+8,"TRADES: 0",DIM,8);
}

//====================================================================
// BUILD ALL
//====================================================================
void Build()
{
   DelAll();
   BuildLeftButtons();
   BuildRightPanel();
   g_ready = true;
   ChartRedraw(0);
}

//====================================================================
// UPDATE -- LEFT BUTTONS
//====================================================================
void UpdateLeftButtons()
{
   if(g_lpMin) return;

   // v25: Update filter status bar
   {
      int  spd     = GetCurrentSpread();
      bool sfWarn  = (SF_Enable && SF_WarnSpreadPts > 0 && spd >= SF_WarnSpreadPts);
      bool sfBlock = (SF_Enable && spd > SF_MaxSpreadPts);
      color spdClr = sfBlock ? RED : (sfWarn ? YLW : DIM);
      UL("lb_flt_spd", StringFormat("SPD:%d", spd), spdClr);

      if(!TF_Enable && !SF_Enable && !SRC_Enable)
      {
         UL("lb_flt_val", "filters OFF", DIM);
         UBg("lb_flt_bg", BK2);
      }
      else if(g_opensAllowed)
      {
         string kzName = (TF_Enable && IsKillZoneActive()) ? ActiveKillZoneName() : (TF_Enable ? "day OK" : "");
         string fStr   = (StringLen(kzName) > 0) ? StringFormat("OPEN  %s", kzName) : "OPEN";
         UL("lb_flt_val", fStr, GRN);
         UBg("lb_flt_bg", C'0,20,8');
      }
      else
      {
         string bStr = StringLen(g_filterReason) > 0 ? g_filterReason : "BLOCKED";
         UL("lb_flt_val", g_flash ? ("! "+bStr+" !") : bStr, RED);
         UBg("lb_flt_bg", g_flash ? C'40,0,0' : C'28,0,0');
      }
   }
   double net     = g_cNet;
   double netSnap = (g_cN > 0 && !IsOnGrid(net)) ? FibSnap(net) : net;
   // v20 fix: use FibSignalTarget so labels match what will actually be traded
   double targetB = FibSignalTarget(1,  netSnap);   // target net after BUY signal
   double targetS = FibSignalTarget(-1, netSnap);   // target net after SELL signal
   double lotB    = NormalizeDouble(MathAbs(targetB - net), 2);
   double lotS    = NormalizeDouble(MathAbs(targetS - net), 2);
   if(lotB < 0.005) lotB = FIB[FC(FibStart)];
   if(lotS < 0.005) lotS = FIB[FC(FibStart)];

   int fibNow = FibFloorIdx(net);
   int fibB   = FibFloorIdx(targetB);
   int fibS   = FibFloorIdx(targetS);

   string buyLbl, selLbl, buy2Lbl, sel2Lbl;
   if(g_cN == 0)
   {
      buyLbl  = StringFormat("^ BUY  %.4f (first +)", lotB);
      selLbl  = StringFormat("v SELL %.4f (first −)", lotS);
      buy2Lbl = "x2 BUY";
      sel2Lbl = "x2 SEL";
   }
   else
   {
      bool offGrid = !IsOnGrid(net);
      string snapNote = offGrid ? StringFormat("[SNAP %.4f]", netSnap) : "";
      buyLbl  = StringFormat("^ BUY  %.4f  %s→%.4f(fib%d→%d)", lotB, snapNote, targetB, fibNow, fibB);
      selLbl  = StringFormat("v SELL %.4f  %s→%.4f(fib%d→%d)", lotS, snapNote, targetS, fibNow, fibS);
      buy2Lbl = StringFormat("x2 BUY %.4f", NLot(2, 1));
      sel2Lbl = StringFormat("x2 SEL %.4f", NLot(2,-1));
   }
   UBtn("lb_buy",  buyLbl,  BUY);
   UBtn("lb_sel",  selLbl,  SEL);
   UBtn("lb_buy2", buy2Lbl, C'0,100,50');
   UBtn("lb_sel2", sel2Lbl, C'120,15,15');

   // AUTO BUY/SELL button
   if(g_autoSig)
   {
      string aTxt = g_flash ? ">>> AUTO SIGNAL ON <<<" : "=== AUTO SIGNAL ON ===";
      color  aClr = g_flash ? AUTO_ON : C'0,120,120';
      UBtn("lb_auto", aTxt, aClr);
      UL("lb_sig_val", g_lastSigStr, g_lastSigClr);
   }
   else
   {
      UBtn("lb_auto", "AUTO BUY/SELL: OFF", AUTO_OFF);
      UL("lb_sig_val", "last signal: " + g_lastSigStr, DIM);
   }

   // v13: AUTO PROFIT TAKE button
   {
      static color PT_ON  = C'0,120,80';
      static color PT_OFF = C'30,30,30';
      if(g_autoPT)
      {
         string ptTxt = g_flash ? ">>> AUTO PROFIT ON <<<" : "=== AUTO PROFIT ON ===";
         color  ptClr = g_flash ? C'0,200,120' : PT_ON;
         UBtn("lb_apt", ptTxt, ptClr);
         // Build active-modes summary string
         string modes = "";
         if(PT_DD_Close)   modes += "DD ";
         if(PT_PnL_Close)  modes += "CLZ ";
         if(PT_Equity_Pct) modes += "EQ% ";
         if(PT_PnL_Bank)   modes += "BNK ";
         if(PT_Trail_Step) modes += "TRL ";
         if(PT_PnL_Step)   modes += "STP ";
         if(ExitMode==EXIT_BB_STEP) modes += "BB ";
         // v29: show commission-adjusted close target when PT_PnL_Close is active
         string commNote = "";
         if(PT_PnL_Close && g_commRatePerLot > 0.001)
            commNote = StringFormat("  CLZ@$%.2f+$%.2f(comm)=$%.2f",
                                    PtPnlClose, g_cCommClose, PtPnlClose+g_cCommClose);
         UL("lb_apt_val", StringLen(modes)>0 ? "active: "+modes+commNote : "no modes enabled", YLW);
      }
      else
      {
         UBtn("lb_apt", "AUTO PROFIT: OFF", PT_OFF);
         UL("lb_apt_val", "enable in inputs or click above", DIM);
      }
   }

   // Flip label — lot to open in the OPPOSITE direction to current net

   double domGross = MathMax(g_cGrossL, g_cGrossS);

   int    flipDirBtn = (net > 0.005) ? -1 : (net < -0.005) ? 1 : 1;

   double lotFlip  = NLot(1, flipDirBtn);
   if(domGross < 0.005)     UBtn("lb_flip", StringFormat(">> FLIP  %.2f", lotFlip),         C'50,80,160');
   else if(net > 0)         UBtn("lb_flip", StringFormat("<< FLIP SHORT  %.2f", lotFlip),   C'0,80,180');
   else                     UBtn("lb_flip", StringFormat(">> FLIP LONG   %.2f", lotFlip),   C'0,80,180');

   // Deleverage
   if(g_delev)
   {
      bool   isAuto  = g_delevAutoOn && AutoDelevEnable;
      string dlvTxt  = g_flash ? ">>> DELEVERAGE ON <<<" : "=== DELEVERAGE ON ===";
      color  dlvClr  = g_flash ? C'255,120,0' : C'200,80,0';
      if(isAuto) { dlvTxt = g_flash ? ">>> AUTO DELEV ON <<<" : "=== AUTO DELEV ON ==="; dlvClr = g_flash ? C'255,160,0' : C'180,100,0'; }
      UBtn("lb_dlv", dlvTxt, dlvClr);
      string nl = N("lb_dlvl"); if(ObjectFind(0,nl)>=0){ ObjectSetInteger(0,nl,OBJPROP_BGCOLOR,C'0,100,60');  ObjectSetInteger(0,nl,OBJPROP_COLOR,clrWhite); ObjectSetInteger(0,nl,OBJPROP_STATE,false); }
      string ns = N("lb_dlvs"); if(ObjectFind(0,ns)>=0){ ObjectSetInteger(0,ns,OBJPROP_BGCOLOR,C'120,20,20'); ObjectSetInteger(0,ns,OBJPROP_COLOR,clrWhite); ObjectSetInteger(0,ns,OBJPROP_STATE,false); }
      // Auto-deleverage status row
      if(AutoDelevEnable && StringLen(g_delevReason) > 0)
         UL("lb_adlv_val", "triggered: " + g_delevReason, YLW);
      else if(AutoDelevEnable)
         UL("lb_adlv_val", "manual on | auto watching", DIM);
      else
         UL("lb_adlv_val", "auto-delev: disabled", DIM);
   }
   else
   {
      UBtn("lb_dlv","DELEVERAGE MODE: OFF",C'30,30,30');
      string nl = N("lb_dlvl"); if(ObjectFind(0,nl)>=0){ ObjectSetInteger(0,nl,OBJPROP_BGCOLOR,C'20,20,20'); ObjectSetInteger(0,nl,OBJPROP_COLOR,DIM); ObjectSetInteger(0,nl,OBJPROP_STATE,false); }
      string ns = N("lb_dlvs"); if(ObjectFind(0,ns)>=0){ ObjectSetInteger(0,ns,OBJPROP_BGCOLOR,C'20,20,20'); ObjectSetInteger(0,ns,OBJPROP_COLOR,DIM); ObjectSetInteger(0,ns,OBJPROP_STATE,false); }
      // Auto-deleverage status row — show thresholds when idle
      if(AutoDelevEnable)
      {
         int ci = FibFloorIdx(g_cNet);
         string watching = "watching:";
         if(DelevOnPositions > 0) watching += StringFormat(" pos %d/%d", g_cN, DelevOnPositions);
         if(DelevOnFibLevel  > 0) watching += StringFormat(" fib %d/%d", ci, DelevOnFibLevel);
         if(DelevOnNetLots   > 0) watching += StringFormat(" |net| %.4f/%.2f", g_cAbsN, DelevOnNetLots);
         UL("lb_adlv_val", watching, ACC);
      }
      else
         UL("lb_adlv_val", "auto-delev: disabled", DIM);
   }

   // Step labels — reflect effective step count (1 or 2 when DoubleStepEnable is active)
   {
      if(MathAbs(g_cNet) > 0.005)
      {
         double netSnap2 = IsOnGrid(g_cNet) ? g_cNet : FibSnap(g_cNet);
         // Walk stepCount levels toward zero for the label target
         int    sc       = GetStepCount();
         double stepDown = netSnap2;
         for(int s = 0; s < sc; s++)
            stepDown = (stepDown > 0.0005) ? FibNextDown(stepDown)
                     : (stepDown < -0.0005) ? FibNextUp(stepDown) : 0.0;
         double stepDown2 = stepDown;
         for(int s = 0; s < sc; s++)
            stepDown2 = (stepDown2 > 0.0005) ? FibNextDown(stepDown2)
                      : (stepDown2 < -0.0005) ? FibNextUp(stepDown2) : 0.0;
         string scTag = (sc > 1) ? " [x2]" : "";
         color  scClr = (sc > 1) ? YLW : STP;
         UBtn("lb_s1", StringFormat("v  STEP-%d%s  → net %.4f", sc, scTag, stepDown), scClr);
         UBtn("lb_s2", StringFormat("vv STEP-%d%s  → net %.4f", sc*2, scTag, stepDown2), scClr);
      }
      else { UBtn("lb_s1","v  STEP -1",STP); UBtn("lb_s2","vv STEP -2",STP); }
   }

   // Micro size label
   UL("lb_mic_sz", StringFormat("%.2f lots", MicroLots), DIM);

   // Lot override highlight
   for(int i = 0; i < FIB_LEN; i++)
   {
      string bn = N(StringFormat("lb_f%d",i));
      if(ObjectFind(0,bn) >= 0)
      {
         ObjectSetInteger(0,bn,OBJPROP_BGCOLOR, g_fibOvr==i ? ACC  : BK3);
         ObjectSetInteger(0,bn,OBJPROP_COLOR,   g_fibOvr==i ? clrWhite : DIM);
         ObjectSetInteger(0,bn,OBJPROP_STATE,   false);
      }
   }
}

//====================================================================
// UPDATE -- RIGHT PANEL
//====================================================================
void UpdateRightPanel()
{
   if(g_rpMin) return;

   double net  = g_cNet, absN = g_cAbsN, pnl = g_cPnL;
   int    np   = g_cN;
   int    ci   = FibFloorIdx(net);  // signed-net fib index
   // "NEXT FLIP LOT" = what a signal AGAINST the current position would open.

   // Net long → next flip is SELL (dir=-1); net short → BUY (dir=+1).

   int    flipDir = (net < -0.005) ? 1 : -1;   // opposite of current net

   double lot  = NLot(1, flipDir);

   double bid  = SymbolInfoDouble(g_tradeSymbol, SYMBOL_BID);

   // Net Exposure — show signed net fib level + gross breakdown
   string nv, nd; color nc;
   bool   offGrid = (g_cN > 0 && !IsOnGrid(net));
   string gridNote = offGrid ? StringFormat("*snap:%.4f", FibSnap(net)) : "";
   if(absN < 0.005)     { nv="0.00";                    nd="FLAT";       nc=DIM; }
   else if(net > 0)     { nv=StringFormat("%.4f",net);  nd="^ LONG";     nc=GRN; }
   else                 { nv=StringFormat("%.4f",net);  nd="v SHORT";    nc=RED; }
   string instrStr = (InstrType==INSTR_FOREX)?"FX":(InstrType==INSTR_INDEX)?"IDX":"XAU";
   string symLabel = (g_tradeSymbol != _Symbol)
      ? StringFormat("SIG:%s → TRD:%s [%s] fib%d%s", _Symbol, g_tradeSymbol, instrStr, ci, gridNote)
      : StringFormat("%s [%s] fib%d%s  L:%.4f S:%.4f", _Symbol, instrStr, ci, gridNote, g_cGrossL, g_cGrossS);
   UL("rd_sym_lbl", symLabel, g_tradeSymbol != _Symbol ? YLW : DIM);
   UL("rd_net_val", nv, nc);
   UL("rd_net_dir", nd, nc);
   UL("rd_fib_val", StringFormat("%d",ci), ACC);

   // Open P&L + bar
   color pc  = pnl >= 0 ? GRN : RED;
   UL("rd_pnl_val", StringFormat("$%.2f",pnl), pc);
   UL("rd_pos_val", StringFormat("%d pos",np),  np>0?TXT:DIM);
   double pct = 0; color pgC = ACC;
   if(pnl > 0 && ProfitTgt > 0)            { pct = MathMin(pnl/ProfitTgt, 1.0);            pgC = pct>0.8?GRN:YLW; }
   else if(pnl < 0 && LossTgt > 0)         { pct = MathMin(MathAbs(pnl)/LossTgt, 1.0);     pgC = RED; }
   UW("rd_pgf",  MathMax(4, (int)((RDW-2)*pct)));
   UBg("rd_pgf", pgC);

   // Flash P&L block near limits
   if(pnl > 0 && pnl >= ProfitTgt*0.9)              UBg("rd_pnl_bg", g_flash?C'0,28,10':BK2);
   else if(pnl < 0 && MathAbs(pnl) >= LossTgt*0.8)  UBg("rd_pnl_bg", g_flash?C'32,0,0':BK2);
   else                                               UBg("rd_pnl_bg", BK2);

   // Next flip
   UL("rd_nxt_val", StringFormat("%.2f lots",lot), ACC);

   // NEW v12 — Alkemix signal status
   {
      string alkIndicStr;
      color  alkStateClr;
      if(g_alkHandle == INVALID_HANDLE)
      {
         alkIndicStr = "AUTO: NO INDICATOR";
         alkStateClr = RED;
      }
      else if(g_autoSig)
      {
      string modeStr = g_delev ? "+DELEV(close→open)" : "+OPEN";
         alkIndicStr = g_flash ? StringFormat("AUTO ON [%s] >>>", modeStr)
                               : StringFormat("AUTO ON [%s]    ", modeStr);
         alkStateClr = g_flash ? AUTO_ON : C'0,120,120';
      }
      else
      {
         alkIndicStr = "AUTO: OFF  (btn to enable)";
         alkStateClr = DIM;
      }
      UL("rd_alk_state", alkIndicStr, alkStateClr);
      UL("rd_alk_last",  g_lastSigStr, g_lastSigClr);
      string exitModeStr = (ExitMode==EXIT_BB_STEP) ?
                           StringFormat("BB EXIT: %s", EnumToString(BBExitTF)) : "EXIT: manual";
      // Reuse alk strip background color to show exit mode hint
      UBg("rd_alk_bg", g_autoSig ? C'5,16,28' : C'5,10,20');
   }

   // Spread + BB
   {
      int spd = GetCurrentSpread();
      // v25: colour against configurable filter thresholds if enabled, fallback to static
      color spdC;
      if(SF_Enable)
         spdC = (spd > SF_MaxSpreadPts) ? RED : (SF_WarnSpreadPts > 0 && spd >= SF_WarnSpreadPts) ? YLW : TXT;
      else
         spdC = (spd > 20) ? RED : (spd > 8) ? YLW : TXT;
      UL("rd_sig_sv", StringFormat("%d%s", spd, !g_sfAllowed?" BLK":""), spdC);
      double bbu = RBB(1), bbl = RBB(2);
      if(bbu > 0)
      {
         double pipDiv = (SymbolInfoDouble(g_tradeSymbol,SYMBOL_POINT)<0.001)?10.0:1.0;
         double dU = (bbu-bid)/_Point/pipDiv;
         double dL = (bid-bbl)/_Point/pipDiv;
         UL("rd_sig_buv", DoubleToString(bbu,_Digits), bid>bbu?RED:TXT);
         UL("rd_sig_blv", DoubleToString(bbl,_Digits), bid<bbl?GRN:TXT);
         UL("rd_bbu_dst",  dU<10?StringFormat("%.1f pips!",dU):"far", dU<10?YLW:DIM);
         UL("rd_bbl_dst",  dL<10?StringFormat("%.1f pips!",dL):"far", dL<10?YLW:DIM);
      }
   }

   // Account totals
   double acBal = AccountInfoDouble(ACCOUNT_BALANCE);
   double acEq  = AccountInfoDouble(ACCOUNT_EQUITY);
   double acFM  = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   color eqClr  = acEq>=acBal?GRN:(acEq<acBal*0.95?RED:YLW);
   UL("rd_acc_bv", StringFormat("$%.2f",acBal), TXT);
   UL("rd_acc_ev", StringFormat("$%.2f",acEq),  eqClr);
   UL("rd_acc_mv", StringFormat("$%.2f",acFM),  acFM<acBal*0.2?RED:DIM);

   // Closed P&L — v25: annotate when SRC cap is active
   {
      string daySuffix = "";
      if(SRC_Enable)
      {
         if(SRC_MaxDailyLoss > 0 && g_dailyClosedPnL <= -MathAbs(SRC_MaxDailyLoss))
            daySuffix = " [LOSS CAP]";
         else if(SRC_MaxDailyProfit > 0 && g_dailyClosedPnL >= SRC_MaxDailyProfit)
            daySuffix = " [PROFIT CAP]";
         else if(SRC_MaxDailyTrades > 0)
            daySuffix = StringFormat(" [%d trades]", g_dailyTradeCount);
      }
      UL("rd_day_val", StringFormat("$%.2f%s", g_dailyClosedPnL, daySuffix),
         g_dailyClosedPnL >= 0 ? GRN : RED);
   }
   UL("rd_ses_val", StringFormat("$%.2f", g_sessionClosedPnL), g_sessionClosedPnL>=0?GRN:RED);

   // v26: Balance growth tracker display
   if(BG_Enable && BG_StepUSD > 0 && g_bgBaseline > 0)
   {
      double bal      = AccountInfoDouble(ACCOUNT_BALANCE);
      double progress = g_bgNextTarget - g_bgBaseline;  // = BG_StepUSD always
      double done     = bal - (g_bgNextTarget - BG_StepUSD);
      double pct      = (progress > 0) ? MathMin(MathMax(done / progress, 0), 1.0) : 0;
      double toGo     = MathMax(g_bgNextTarget - bal, 0);
      string bgStr    = StringFormat("Loop %d  Bal:$%.2f → $%.2f  ($%.2f to go)",
                                     g_bgLoop+1, bal, g_bgNextTarget, toGo);
      color bgClr     = (pct >= 1.0) ? GRN : ACC;
      UL("rd_bg_mode", bgStr, bgClr);
      UW("rd_bg_pgf",  MathMax(4,(int)((RDW-16)*pct)));
      UBg("rd_bg_pgf", bgClr);
   }
   else
   {
      UL("rd_bg_mode", BG_Enable ? "BG_Enable ON but no baseline yet" : "OFF — enable BG_Enable", DIM);
      UW("rd_bg_pgf",4); UBg("rd_bg_pgf",BK);
   }

   // v26: Skin size display (shows CalcSkin result with context)
   {
      double sk  = CalcSkin();
      int    ci  = FibFloorIdx(g_cNet);
      int    si  = MathMax(SkinIdx, ci - SkinOffset);
      if(g_x2Active) si = MathMax(SkinIdx, si - X2SkinReduce);
      si = MathMin(si, MathMin(FibMax, FIB_LEN-1));
      string skInf = g_x2Active
         ? StringFormat("fib%d  depth=%d  offset=%d  x2-%d → fib%d", si, ci, SkinOffset, X2SkinReduce, si)
         : StringFormat("fib%d  depth=%d  offset=%d → fib%d", si, ci, SkinOffset, si);
      UL("rd_sk_val", Skin ? StringFormat("%.4f lots", sk) : "OFF (full close)", Skin ? ACC : DIM);
      UL("rd_sk_inf", skInf, g_x2Active ? YLW : DIM);
   }

   // Auto step — v21: per-unit display (base fib unit = FIB[FibStart], default 0.01 lots)
   if(UseAutoStep)
   {
      if(AutoStepPerLot)
      {
         double baseUnit = FIB[FC(FibStart)];
         double numUnits = (g_cAbsN > 0.005) ? g_cAbsN / baseUnit : 0;
         double ppl      = (numUnits > 0.001) ? g_cPnL / numUnits : 0;
         double ppct     = (PerLotTrigger > 0) ? MathMin(MathMax(ppl,0) / PerLotTrigger, 1.0) : 0;
         double need     = (numUnits > 0.001) ? MathMax(0, PerLotTrigger * numUnits - g_cPnL) : 0;
         string needStr  = (ppl >= PerLotTrigger) ? "FIRING!" : StringFormat("need $%.2f more", need);
         UL("rd_ast_mode", StringFormat("PER-UNIT $%.2f/$%.2f  units=%.1f  [%d fired]  %s",
                           ppl, PerLotTrigger, numUnits, g_stepFired, needStr), ppct>=1?GRN:ACC);
         UW("rd_ast_pgf",  MathMax(4,(int)((RDW-16)*ppct)));
         UBg("rd_ast_pgf", ppct>=1?GRN:ACC);
         UL("rd_ast_val",  StringFormat("fires at $%.2f/0.01lot-unit | net=%.4f(%.1f units) | tgt=$%.2f | last:$%.2f",
                           PerLotTrigger, g_cNet, numUnits, PerLotTrigger*numUnits, g_lastStepPnL), DIM);
      }
      else
      {
         double tgt  = (g_stepFired==0)?FixedStep1:(g_stepFired==1)?FixedStep2:(g_stepFired==2)?FixedStep3:FixedBankAll;
         string mode = (g_stepFired>=3)?"BANK ALL":StringFormat("STEP-%d",g_stepFired+1);
         double ppct = (tgt>0)?MathMin(MathMax(pnl,0)/tgt,1.0):0;
         double need = MathMax(0, tgt - pnl);
         string needStr = (pnl >= tgt) ? "FIRING!" : StringFormat("$%.2f to go", need);
         UL("rd_ast_mode", StringFormat("%s: $%.2f / $%.2f  [%s]", mode, pnl, tgt, needStr), ppct>=1?GRN:ACC);
         UW("rd_ast_pgf",  MathMax(4,(int)((RDW-16)*ppct)));
         UBg("rd_ast_pgf", ppct>=1?GRN:ACC);
         UL("rd_ast_val",  StringFormat("Ladder: $%.0f → $%.0f → $%.0f → $%.0f bank  | last:$%.2f",
                           FixedStep1, FixedStep2, FixedStep3, FixedBankAll, g_lastStepPnL), DIM);
      }
   }
   else
   {
      UL("rd_ast_mode","OFF — enable UseAutoStep in inputs",DIM);
      UW("rd_ast_pgf",4); UBg("rd_ast_pgf",BK3);
      UL("rd_ast_val","Fires DoStep(1) or DoBank on P&L threshold, once per bar",DIM);
   }

   // Double-step status indicator
   if(DoubleStepEnable)
   {
      int    ci     = FibFloorIdx(g_cNet);
      // v24: show latch state — armed/disarmed/watching
      string dsStr;
      color  dsClr;
      if(g_x2Active)
      {
         // Latch is SET — x2 running, show off-threshold
         dsStr = StringFormat(">>> x2 STEP ACTIVE (disarms at fib<=%d / %.4f lot) <<<", DoubleStepOffFibIdx, FIB[MathMin(DoubleStepOffFibIdx, FIB_LEN-1)]);
         dsClr = g_flash ? YLW : C'200,150,0';
      }
      else if(ci >= DoubleStepOnFibIdx - 1)
      {
         // One step away from arming — warn
         dsStr = StringFormat("x2 armed — fib%d/%d  arms at fib>=%d (%.4f lot)", ci, DoubleStepOnFibIdx, DoubleStepOnFibIdx, FIB[MathMin(DoubleStepOnFibIdx, FIB_LEN-1)]);
         dsClr = ACC;
      }
      else
      {
         // Watching — show progress toward arm threshold
         dsStr = StringFormat("x2 watching — fib%d/%d  (%.4f/%.4f lot)", ci, DoubleStepOnFibIdx, g_cAbsN, FIB[MathMin(DoubleStepOnFibIdx, FIB_LEN-1)]);
         dsClr = DIM;
      }
      UL("lb_adlv_val", dsStr, dsClr);
   }

   // Position rows
   ulong    tks[]; double vs[],ps[],sws[]; int ds[]; datetime ots[];
   int pc2 = 0;
   int total = PositionsTotal();
   ArrayResize(tks,total); ArrayResize(vs,total); ArrayResize(ps,total);
   ArrayResize(sws,total); ArrayResize(ds,total); ArrayResize(ots,total);
   for(int i = 0; i < total; i++)
   {
      ulong t = PositionGetTicket(i); if(!PM(t)) continue;
      tks[pc2] = t;
      vs[pc2]  = PositionGetDouble(POSITION_VOLUME);
      ps[pc2]  = PositionGetDouble(POSITION_PROFIT);
      sws[pc2] = PositionGetDouble(POSITION_SWAP);
      ds[pc2]  = (int)PositionGetInteger(POSITION_TYPE);
      ots[pc2] = (datetime)PositionGetInteger(POSITION_TIME);
      pc2++;
   }

   for(int i = 0; i < MAX_POS_ROWS; i++)
   {
      string ri = IntegerToString(i);
      if(i < pc2)
      {
         bool   isL  = (ds[i]==POSITION_TYPE_BUY);
         double tot  = ps[i]+sws[i];
         double ep   = 0;
         if(PositionSelectByTicket(tks[i])) ep = PositionGetDouble(POSITION_PRICE_OPEN);
         UL("rd_pd"+ri, isL?"^ LONG":"v SHORT",  isL?GRN:RED);
         UL("rd_pl"+ri, StringFormat("%.2f",vs[i]), TXT);
         UL("rd_pe"+ri, DoubleToString(ep,_Digits), DIM);
         UL("rd_pp"+ri, StringFormat("$%.2f",tot),  tot>=0?GRN:RED);
         UL("rd_pu"+ri, FmtDur(ots[i]), DIM);
         UBg("rd_pr"+ri, i%2==0?BK:BK2);
         string bname = N("rd_px"+ri);
         if(ObjectFind(0,bname)>=0)
         {
            ObjectSetString( 0,bname,OBJPROP_TOOLTIP, IntegerToString((long)tks[i]));
            ObjectSetInteger(0,bname,OBJPROP_STATE,   false);
         }
      }
      else
      {
         UL("rd_pd"+ri," ",DIM); UL("rd_pl"+ri," ",DIM);
         UL("rd_pe"+ri," ",DIM); UL("rd_pp"+ri," ",DIM); UL("rd_pu"+ri," ",DIM);
         UBg("rd_pr"+ri, BK);
         string bname = N("rd_px"+ri);
         if(ObjectFind(0,bname)>=0){ ObjectSetString(0,bname,OBJPROP_TOOLTIP,"0"); ObjectSetInteger(0,bname,OBJPROP_STATE,false); }
      }
   }

   // Summary
   string sumStr;
   if(absN < 0.005) sumStr = StringFormat("FLAT   Open: $%.2f", pnl);
   else if(net > 0) sumStr = StringFormat("%.2f LONG   Open: $%.2f", absN, pnl);
   else             sumStr = StringFormat("%.2f SHORT  Open: $%.2f", absN, pnl);
   UL("rd_sum_val", sumStr, pnl>=0?GRN:RED);

   // Trade history
   for(int i = 0; i < MAX_HIST_ROWS; i++)
   {
      string hi = IntegerToString(i);
      if(i < g_histCount)
      {
         bool  isB = (g_hist[i].dir==DEAL_TYPE_BUY);
         color hpc = g_hist[i].pnl>=0?GRN:RED;
         UL("rd_ht"+hi, TimeToString(g_hist[i].t,TIME_MINUTES), DIM);
         UL("rd_hd"+hi, isB?"BUY":"SELL", isB?GRN:RED);
         UL("rd_hv"+hi, StringFormat("%.2f",g_hist[i].lots), TXT);
         UL("rd_hp"+hi, StringFormat("$%.2f",g_hist[i].pnl), hpc);
         UL("rd_hw"+hi, g_hist[i].pnl>=0?"W":"L", hpc);
      }
      else
      {
         UL("rd_ht"+hi,"---",DIM); UL("rd_hd"+hi,"",DIM);
         UL("rd_hv"+hi,"",  DIM); UL("rd_hp"+hi,"",DIM); UL("rd_hw"+hi,"",DIM);
      }
   }
   UL("rd_hst_sv", StringFormat("$%.2f",g_sessionClosedPnL),  g_sessionClosedPnL>=0?GRN:RED);
   UL("rd_hst_sc", StringFormat("TRADES: %d",g_histCount), DIM);
}

//====================================================================
// CLICK HANDLER
//====================================================================
void Click(string obj)
{
   string id = obj; StringReplace(id,"FHP_","");

   if(id=="lb_buy")
   {
      RefreshCache();
      if(g_delev) DoDeleverage(1, StringFormat("BTN:BUY-DELEV net=%.4f grossL=%.2f grossS=%.2f", g_cNet, g_cGrossL, g_cGrossS), true, 1);
      else        DoAutoFibStep(1, "BTN");
   }
   else if(id=="lb_sel")
   {
      RefreshCache();
      if(g_delev) DoDeleverage(-1, StringFormat("BTN:SEL-DELEV net=%.4f grossL=%.2f grossS=%.2f", g_cNet, g_cGrossL, g_cGrossS), true, 1);
      else        DoAutoFibStep(-1, "BTN");
   }
   else if(id=="lb_buy2") { RefreshCache(); DoAutoFibStep(1,  "BTN2"); }
   else if(id=="lb_sel2") { RefreshCache(); DoAutoFibStep(-1, "BTN2"); }
   else if(id=="lb_gr")
   {
      if(g_cNet >= 0) DoGoldenNorm(1); else DoGoldenNorm(-1);
   }
   else if(id=="lb_auto")
   {
      g_autoSig = !g_autoSig;
      if(g_autoSig && g_alkHandle == INVALID_HANDLE)
      {
         g_alkHandle = iCustom(_Symbol, AlkTF, "Alkemix Trend");
         if(g_alkHandle == INVALID_HANDLE)
         {
            Print("FibHedge v17: Cannot load 'Alkemix Trend'. Is it installed?");
            g_autoSig = false;
         }
         else
         {
            g_lastSigBar = 0;
            g_lastAlkDir = 0;
            Print("FibHedge v17: Alkemix Trend loaded. Auto signal ON.");
         }
      }
   }
   else if(id=="lb_apt")
   {
      g_autoPT = !g_autoPT;
      Print("FibHedge v17: Auto Profit Taking ", g_autoPT ? "ON" : "OFF");
   }
   else if(id=="lb_dlv")
   {
      g_delev = !g_delev;
      g_delevAutoOn = false;
      Print("FibHedge v19: Deleverage mode ", g_delev ? "ON" : "OFF");
   }
   // lb_dlvl / lb_dlvs = manual deleverage one step in that direction
   else if(id=="lb_dlvl" && g_delev) { RefreshCache(); DoDeleverage(1,  StringFormat("BTN:DELEV-BUY net=%.4f", g_cNet),  true, GetStepCount()); }
   else if(id=="lb_dlvs" && g_delev) { RefreshCache(); DoDeleverage(-1, StringFormat("BTN:DELEV-SEL net=%.4f", g_cNet), true, GetStepCount()); }
   else if(id=="lb_flip")
   {
      if     (g_cNet >  0.005) DoFlip(-1);
      else if(g_cNet < -0.005) DoFlip(1);
   }
   else if(id=="lb_s1")  { RefreshCache(); DoStep(1, StringFormat("BTN:STEP1 net=%.4f pnl=%.2f", g_cNet, g_cPnL)); }
   else if(id=="lb_s2")  { RefreshCache(); DoStep(2, StringFormat("BTN:STEP2 net=%.4f pnl=%.2f", g_cNet, g_cPnL)); }
   else if(id=="lb_bk")  { RefreshCache(); DoBank(StringFormat("BTN:BANK net=%.4f pnl=%.2f", g_cNet, g_cPnL)); }
   else if(id=="lb_ca")  { RefreshCache(); DoCloseAll(StringFormat("BTN:CLOSEALL net=%.4f pnl=%.2f", g_cNet, g_cPnL)); }
   else if(id=="lb_min")      { g_lpMin=!g_lpMin; DelAll(); Build(); return; }
   else if(id=="rd_min")      { g_rpMin=!g_rpMin; DelAll(); Build(); return; }
   else if(id=="lb_m_buy")    DoOpen(1,  MicroLots, StringFormat("BTN:MICRO_BUY net=%.4f", g_cNet));
   else if(id=="lb_m_sel")    DoOpen(-1, MicroLots, StringFormat("BTN:MICRO_SEL net=%.4f", g_cNet));
   else if(id=="lb_m_red")    DoMicroReduce(MicroLots);
   else if(id=="lb_c01")      DoCloseSmall(MicroLots, StringFormat("BTN:CLOSE0.01 net=%.4f", g_cNet));
   else if(id=="lb_snap")
   {
      // Snap to grid: if off-grid, open/close the delta to reach nearest fib
      RefreshCache();
      if(!IsOnGrid(g_cNet) && g_cN > 0)
      {
         double snapped = FibSnap(g_cNet);
         double delta   = NormalizeDouble(snapped - g_cNet, 2);
         string r       = StringFormat("BTN:SNAP net%.4f→%.4f delta=%.4f", g_cNet, snapped, delta);
         Print("[SNAP-BTN] ", r);
         if(delta > 0.005)       DoOpen(1,  delta, r);
         else if(delta < -0.005) DoOpen(-1, -delta, r);
         else Print("[SNAP-BTN] already on grid");
      }
      else Print("[SNAP-BTN] net=%.4f already on grid or flat", g_cNet);
   }
   else if(id=="rd_hst_clr")  g_histCount=0;
   else if(StringFind(id,"lb_f")==0 && StringLen(id)>4
           && StringGetCharacter(id,4)>='0' && StringGetCharacter(id,4)<='9')
   {
      int idx = (int)StringToInteger(StringSubstr(id,4));
      if(idx>=0 && idx<FIB_LEN) g_fibOvr = (g_fibOvr==idx)?-1:idx;
   }
   else if(StringFind(id,"rd_px")==0)
   {
      ulong tkt = (ulong)StringToInteger(ObjectGetString(0,obj,OBJPROP_TOOLTIP));
      if(tkt>0 && PM(tkt)) trade.PositionClose(tkt);
   }

   ObjectSetInteger(0,obj,OBJPROP_STATE,false);
}

//====================================================================
// MT5 LIFECYCLE
//====================================================================
int OnInit()
{
   // v28.1: resolve trade symbol — blank = use chart symbol (normal), or use provided name
   string tsInput = TradeSymbol;
   StringTrimLeft(tsInput); StringTrimRight(tsInput);
   g_tradeSymbol = (StringLen(tsInput) > 0) ? tsInput : _Symbol;
   if(g_tradeSymbol != _Symbol)
      Print(StringFormat("[INIT] TradeSymbol mode: signals=%s  trades=%s", _Symbol, g_tradeSymbol));
   else
      Print(StringFormat("[INIT] Single symbol mode: %s", _Symbol));

   trade.SetDeviationInPoints(20);
   trade.SetTypeFilling(FM());
   if(Magic > 0) trade.SetExpertMagicNumber(Magic);

   // Panel BB (display only — M1 for panel numbers)
   g_bb = iBands(_Symbol, PERIOD_M1, BB_P, 0, BB_D, PRICE_CLOSE);

   // BB exit handle — always created when EXIT_BB_STEP selected
   // v26: also added to chart window so bands are always visible on screen
   // regardless of whether BBExitTF matches the chart timeframe.
   if(ExitMode == EXIT_BB_STEP)
   {
      g_bbExitH = iBands(_Symbol, BBExitTF, BBExitPeriod, 0, BBExitDev, PRICE_CLOSE);
      if(g_bbExitH != INVALID_HANDLE)
      {
         // Add to main chart window (subwindow 0) — shows bands visually
         // even when BBExitTF differs from the chart timeframe.
         ChartIndicatorAdd(0, 0, g_bbExitH);
         Print(StringFormat("[BB] Exit BB added to chart: TF=%s Period=%d Dev=%.1f",
                            EnumToString(BBExitTF), BBExitPeriod, BBExitDev));
      }
   }

   // v13: Sync input booleans → runtime toggles (backtest-friendly)
   g_delev   = DeleverageMode;
   g_autoPT  = AutoProfitTake;

   // v13: Pre-load Alkemix if AutoSignalOn=true (no button click needed in backtest)
   if(AutoSignalOn)
   {
      g_alkHandle = iCustom(_Symbol, AlkTF, "Alkemix Trend");
      if(g_alkHandle == INVALID_HANDLE)
      {
         Print("FibHedge v13: Cannot load 'Alkemix Trend'. Is it installed? AutoSignal disabled.");
         g_autoSig = false;
      }
      else
      {
         g_autoSig    = true;
         g_lastSigBar = 0;
         g_lastAlkDir = 0;    // will be initialised from bar[1] on first CheckAutoSignal run
         Print("FibHedge v13: Alkemix Trend loaded OK. Auto signal ON (from input).");
      }
   }

   g_sessionStart     = TimeCurrent();
   g_sessionClosedPnL = 0.0;
   g_dailyClosedPnL   = 0.0;
   g_lastDeal         = 0;
   g_histCount        = 0;
   g_lastSigStr       = "---";
   g_lastSigClr       = DIM;
   g_ptPeakPnL        = 0.0;
   g_ptLastBar        = 0;
   g_anyPTBar         = 0;   // v15
   g_delevBar         = 0;   // v15
   g_delevSkipCount   = 0;   // v16
   g_sessionEqHigh    = AccountInfoDouble(ACCOUNT_EQUITY); // v16: baseline for equity DD
   g_x2Active         = false;  // v24: latch starts disarmed
   g_bbTouchFired     = false;  // v28: no BB touch fired yet
   // v25: filter state
   g_opensAllowed     = true;
   g_tfAllowed        = true;
   g_sfAllowed        = true;
   g_srcAllowed       = true;
   g_filterReason     = "";
   g_dailyTradeCount  = 0;
   g_srcTradeCountDate = 0;
   // v26: balance growth tracker
   g_bgBaseline    = AccountInfoDouble(ACCOUNT_BALANCE);
   g_bgLoop        = 0;
   g_bgNextTarget  = g_bgBaseline + BG_StepUSD;
   g_bgLastBar     = 0;
   Print(StringFormat("[BG] Initialised — baseline=%.2f  step=$%.2f  first target=%.2f",
                      g_bgBaseline, BG_StepUSD, g_bgNextTarget));

   MqlDateTime dt; TimeToStruct(TimeCurrent(),dt);
   dt.hour=0; dt.min=0; dt.sec=0;
   g_lastDayDate = StructToTime(dt);

   ScanStartupHistory();
   RefreshCache();

   // ================================================================
   // v29: RESTORE STATE FROM LIVE POSITIONS after reinit/setting change
   //
   // When the user changes an input, MT5 calls OnDeinit+OnInit but keeps
   // all open positions. Without restoration, the EA "forgets" where it
   // is on the fib ladder and re-initialises as if starting fresh.
   //
   // Everything is inferred from open positions — no persistent file needed.
   // ================================================================

   // 1. Restore g_lastAlkDir from current net direction.
   //    This prevents CheckAutoSignal from re-initialising and skipping
   //    or doubling the next signal after a settings change.
   if(g_autoSig && g_cN > 0)
   {
      if     (g_cNet >  0.005) g_lastAlkDir =  1;
      else if(g_cNet < -0.005) g_lastAlkDir = -1;
      // flat: leave at 0 — let CheckAutoSignal re-init cleanly
      if(g_lastAlkDir != 0)
         Print(StringFormat("[RESTORE] g_lastAlkDir=%d inferred from net=%.4f (pos=%d)",
                            g_lastAlkDir, g_cNet, g_cN));
   }

   // 2. Restore g_x2Active latch from current fib depth.
   //    If already at or above the arm threshold, re-arm immediately.
   if(DoubleStepEnable && g_cN > 0)
   {
      int ci = FibFloorIdx(g_cNet);
      if(ci >= DoubleStepOnFibIdx)
      {
         g_x2Active = true;
         Print(StringFormat("[RESTORE] g_x2Active=true — fib%d >= arm threshold fib%d  net=%.4f",
                            ci, DoubleStepOnFibIdx, g_cNet));
      }
   }

   // 3. Restore g_stepFired for fixed-ladder AutoStep mode.
   //    Infer which rung of the ladder we're on from current P&L vs thresholds.
   //    This is approximate — P&L fluctuates — so we use the fib depth as
   //    a proxy: deeper in the book = more steps have already fired.
   //    Only applies when UseAutoStep=true and AutoStepPerLot=false.
   if(UseAutoStep && !AutoStepPerLot && g_cN > 0)
   {
      // Can't know exactly which step fired, but we can cap g_stepFired
      // so the next threshold is at least FixedStep2 (not Step1 again).
      // Safest: reset to 0 and let it re-earn naturally — avoids double-bank.
      // User can manually bank if needed. Leave at 0 (already set above).
      // Just log the current state for awareness.
      Print(StringFormat("[RESTORE] AutoStep fixed-ladder: step=%d  pnl=%.2f — will re-sequence from Step1",
                         g_stepFired, g_cPnL));
   }

   // 4. Restore g_bgLoop and g_bgNextTarget from current balance vs baseline.
   //    Recompute how many BG_StepUSD increments have already been passed.
   if(BG_Enable && BG_StepUSD > 0 && g_bgBaseline > 0)
   {
      double bal = AccountInfoDouble(ACCOUNT_BALANCE);
      double grown = bal - g_bgBaseline;
      if(grown > 0 && BG_StepUSD > 0)
      {
         g_bgLoop       = (int)MathFloor(grown / BG_StepUSD);
         g_bgNextTarget = g_bgBaseline + (double)(g_bgLoop + 1) * BG_StepUSD;
         Print(StringFormat("[RESTORE] BG loop=%d  balance=%.2f  baseline=%.2f  next target=%.2f",
                            g_bgLoop, bal, g_bgBaseline, g_bgNextTarget));
      }
   }

   DelAll(); Build();
   UpdateLeftButtons();
   UpdateRightPanel();
   return INIT_SUCCEEDED;
}

void OnDeinit(const int r)
{
   DelAll();
   if(g_bb       != INVALID_HANDLE) IndicatorRelease(g_bb);
   if(g_bbExitH  != INVALID_HANDLE) IndicatorRelease(g_bbExitH);
   if(g_alkHandle!= INVALID_HANDLE) IndicatorRelease(g_alkHandle);
   ChartRedraw(0);
}

void OnTick()
{
   if(!g_ready) return;

   // 1. Refresh position/P&L cache ONCE per tick
   RefreshCache();

   // 2. Scan for newly closed deals
   ScanNewDeals();

   // 3. Flash toggle (once per second)
   datetime now = TimeCurrent();
   if(now != g_lf) { g_flash = !g_flash; g_lf = now; }

   // 3b. v25: Update filter state cache (used by UI and all open gates)
   CheckOpensAllowed();

   // 4. Auto step (P&L threshold — independent of AutoProfitTake)
   CheckAutoStep();

   // 5. v14: Auto-deleverage trigger (must run before signal check)
   CheckAutoDelev();

   // 6. Auto signal from Alkemix Trend (bar-based)
   CheckAutoSignal();

   // 7. v13 — Auto profit-taking suite (master: AutoProfitTake / g_autoPT)
   CheckAutoProfitTake();

   // 8. v14: Per-position profit take (tick-level, no bar cooldown)
   DoPerPosTake();

   // 9. BB exit (also gated by AutoProfitTake)
   CheckBBExit();

   // 10. v16: Swap drain — close swap-heavy positions (tick-level)
   CheckSwapDrain();

   // 11. v16: Hold-days close (bar-level)
   CheckHoldDays();

   // 12. v16: Equity drawdown close (tick-level)
   CheckEquityDD();

   // 13. v26: Balance growth DoBank (fires when balance crosses next milestone)
   CheckBalanceGrowth();

   // 13. Update UI
   UpdateLeftButtons();
   UpdateRightPanel();
   ChartRedraw(0);
}

void OnChartEvent(const int id, const long &lp, const double &dp, const string &sp)
{
   if(id==CHARTEVENT_OBJECT_CLICK && StringFind(sp,"FHP_")==0) Click(sp);
}
