//+------------------------------------------------------------------+
//| EntryInfinityMicroScalper_v4_80_P1_FINAL_HYBRID_75_DIRECT.mq5             |
//| Standalone XAUUSD/XAUUSDm M1 score-based micro scalper.          |
//| One setup creates one same-direction basket. No grid, martingale,|
//| averaging, rescue, pyramiding, external feed, API or process.    |
//+------------------------------------------------------------------+
#property copyright "SelfTrade"
#property version   "5.14" // LOG_BASED_ENTRY_TIMING_FIX
#property strict

#include <Trade\Trade.mqh>

// Fixed indicator definitions required by this build.
#define EMA_FAST_PERIOD       50
#define EMA_SLOW_PERIOD       200
#define DMI_FAST_PERIOD       10
#define DMI_SLOW_PERIOD       14
#define CRSI_PRICE_PERIOD      3
#define CRSI_STREAK_PERIOD     2
#define CRSI_RANK_PERIOD     100
#define CRSI_UPPER_LEVEL      60.0
#define CRSI_LOWER_LEVEL      40.0
#define MAX_LEGS               10
#define ROC_PERIOD               9
#define STATE_VERSION          5.14

enum ENUM_SIDE
  {
   SIDE_BUY=0,
   SIDE_SELL=1
  };

enum ENUM_FLOW
  {
   FLOW_NEUTRAL=0,
   FLOW_BUY=1,
   FLOW_SELL=2,
   FLOW_STRONG_BUY=3,
   FLOW_STRONG_SELL=4
  };

enum ENUM_STRUCTURE
  {
   STRUCTURE_UNKNOWN=0,
   STRUCTURE_BULLISH=1,
   STRUCTURE_BEARISH=2,
   STRUCTURE_RANGE=3
  };

enum ENUM_TP_MODE
  {
   TP_MODE_R_BASED=0,
   TP_MODE_CASH=1
  };

enum ENUM_STOP_MODE
  {
   STOP_MODE_STRUCTURE_ATR_FALLBACK=0
  };

enum ENUM_LEG_STATUS
  {
   LEG_UNUSED=0,
   LEG_REQUESTED=1,
   LEG_OPEN=2,
   LEG_RECONCILE_REQUIRED=3,
   LEG_CLOSE_PENDING=4,
   LEG_CLOSED=5,
   LEG_FAILED=6
  };

enum ENUM_INCOMPLETE_MODE
  {
   INCOMPLETE_CLOSE_ACCEPTED=0,
   INCOMPLETE_MANAGE_PARTIAL=1
  };

enum ENUM_OPPOSITE_STATE
  {
   OPP_IDLE=0,
   OPP_WATCHING=1,
   OPP_ARMED=2,
   OPP_REVALIDATE=3
  };

enum ENUM_HALT_REASON
  {
   HALT_NONE=0,
   HALT_DAILY_LOSS=1,
   HALT_WEEKLY_LOSS=2,
   HALT_EQUITY_DRAWDOWN=3,
   HALT_CONSECUTIVE_LOSSES=4,
   HALT_BASKETS_PER_HOUR=5,
   HALT_BASKETS_PER_DAY=6
  };

struct SwingPoint
  {
   double   price;
   datetime time;
   int      shift;
  };

struct LegConfig
  {
   int    leg_id;
   double target_money;
   double target_r;
   double stop_loss_money;
   double activate_money;
   double giveback_money;
  };

struct LegRuntime
  {
   int             leg_id;
   ulong           ticket;
   ulong           position_id;
   double          requested_volume;
   double          filled_volume;
   double          entry;
   double          sl;
   double          tp;
   double          target_money;
   double          risk_money;
   double          peak_profit;
   double          mfe;
   double          mae;
   double          entry_costs;
   bool            lock_armed;
   bool            break_even_applied;
   ENUM_LEG_STATUS status;
   string          pending_exit_reason;
   string          entry_route; // v4.94 FAST/NORMAL telemetry
  };

struct TickSample
  {
   long   time_msc;
   double bid;
   double ask;
  };

struct Candidate
  {
   ENUM_SIDE side;
   int       direction_score;
   int       structure_score;
   int       combined_score;
   int       entry_quality;
   string    setup_type;
   string    signal_id;
   bool      eligible;
   string    reject_reason;
  };

//====================================================================
// INPUTS
//====================================================================
input group "=== v4.97 = v4.94 BASE + AI SHADOW / 50 ORDER TEST ==="
input bool                  V494RouterEnabled           = true;
input int                   V494FastMinDirection        = 85;
input double                V494FastMinTickRatio        = 0.68;
input double                V494FastMinVelocityPoints   = 50.0;
input double                V494FastMaxDispATR          = 0.18; // FAST only before price is stretched
input int                   V494FastOppPredBlock        = 65;
input bool                  V494ThirtyOrderTest         = true;
input int                   V494AcceptedOrderLimit      = 50;   // v4.97 shadow study: broker-accepted positions
input bool                  V494StopNewEntriesAtLimit   = true;

input group "=== v4.97 AI SHADOW OBSERVER (TELEMETRY ONLY / NO ORDER CONTROL) ==="
input bool                  V497AIShadowEnabled         = true;
input bool                  V497AIShadowLogEveryDecision= true;
input bool                  V497AIShadowTagSetup        = true;   // telemetry text only; never blocks/delays/changes route

input group "=== EXHAUSTION + RETRACE ENTRY ==="
input bool                  ExhaustionGuardEnabled      = true;
input double                ExhaustionTickRatio         = 0.75;  // v4.94: detect one-sided impulse a little earlier
input double                ExhaustionVelocityPoints    = 100.0; // v4.94: earlier impulse/chase protection
input double                ExhaustionDispATR           = 0.20;  // v4.94: avoid chasing near the top/bottom
input double                ExhaustionStrongDispATR     = 0.32;  // strong-flow extension
input double                ExhaustionRetracePoints     = 70.0;  // wait for pullback from impulse extreme
input double                ExhaustionResumeTickRatio   = 0.55;  // then require side flow to resume
input double                ExhaustionResumeVelocity    = 20.0;
input int                   ExhaustionMaxArmSeconds     = 45;    // expiry inside same candle
input bool                  EmergencyWrongWayEnabled    = true;
input int                   EmergencyWrongWaySeconds    = 4;
input double                EmergencyWrongWayLossMoney  = 30.0;
input double                EmergencyOppVelocityPoints  = 80.0;
input double                EmergencyOppTickRatio       = 0.60;

input group "=== REVERSAL OVERRIDE ==="
input bool                  ReversalOverrideEnabled     = true;
input int                   ReversalOppPredMin          = 65;    // opposite next-candle predictor score
input double                ReversalOppTickRatioMin     = 0.65;  // opposite live tick ratio
input double                ReversalVelocityPointsMin   = 120.0; // strong opposite velocity
input double                ReversalBodyRatioMin        = 0.45;  // strong closed candle body
input double                ReversalWickRatioMin        = 0.30;  // rejection wick
input bool                  ReversalRequireFlow         = false; // allow strong velocity/predictor override even if flow neutral
input bool                  ReversalBlockP1Direct       = true;  // block P1 75 direct against confirmed reversal

input group "=== MARGIN PRECHECK ==="
input bool                  MarginPrecheckEnabled       = true;
input double                MarginSafetyReserveMoney    = 100.0; // keep this much free margin after each leg
input bool                  StopBasketOnNoMargin        = true;  // do not spam remaining legs after margin failure

input group "=== SAFE REGIME TELEMETRY + CHASE GUARD ==="
input bool                  SafeRegimeTelemetryEnabled  = true;  // telemetry only; never blocks by itself
input bool                  SafeFalseBreakTelemetry     = true;  // telemetry only; never blocks by itself
input bool                  SafeAdaptiveDelayEnabled    = true;  // short timing wait only
input int                   SafeDelayFastSeconds        = 1;
input int                   SafeDelayNormalSeconds      = 2;
input double                ChaseGuardMaxDispATR        = 0.45;  // if live displacement already too large, wait
input double                ChaseGuardResumeDispATR     = 0.28;  // resume only after movement cools/retraces
input int                   ChaseGuardMaxWaitSeconds    = 8;
input double                TelemetrySpikeRangeATR      = 1.70;
input double                TelemetryTrendADX           = 25.0;
input double                TelemetryTrendEMADistATR    = 0.25;
input double                FalseBreakPiercePoints      = 20.0;
input double                FalseBreakReturnPoints      = 10.0;

input group "=== EARLY WRONG-WAY EXIT + MICRO LOCK ==="
input bool                  EarlyWrongWayExitEnabled    = true;
input int                   EarlyWrongWaySeconds        = 5;     // only during first seconds after fill
input double                EarlyWrongWayMinLossMoney   = 12.0;  // do not react to tiny noise
input int                   EarlyWrongWayOppPredScore   = 65;
input double                EarlyWrongWayOppTickRatio   = 0.65;
input bool                  EarlyWrongWayRequireFlow    = true;
input bool                  MicroProfitLockEnabled      = true;
input double                MicroLockArmMoney           = 10.0;  // once MFE reaches this...
input double                MicroLockFloorMoney         = 2.0;   // ...protect a small positive result
input int                   MicroLockOppPredScore       = 60;    // close only when reversal confirms
input bool                  MicroLockRequireOppFlow     = true;

input group "=== SPIKE WAIT + VIRTUAL REVERSE LIMIT SEEK ==="
input bool                  SpikeWaitEnabled            = true;
input int                   SpikeWaitSeconds            = 3;     // do not enter immediately on a fresh M1 candle
input int                   SpikeConfirmMaxSeconds      = 8;     // after this, normal P1 logic may resume
input bool                  SpikeRequireFlowConfirm     = true;
input bool                  ReverseSeekEnabled          = true;
input int                   ReverseSeekFreshSeconds     = 60;
input double                ReverseSeekReboundPoints    = 80.0;  // XAUUSD points from tracked extreme
input int                   ReverseSeekMinPredictor     = 65;    // opposite-side predictor score
input bool                  ReverseSeekRequireFlow      = true;

input group "=== NEXT CANDLE PREDICTOR (M1) ==="
input bool                  NextCandlePredictorEnabled  = true;
input bool                  PredictorHardFilter         = false; // TEST FIRST: telemetry only by default
input int                   PredictorMinScore           = 70;    // used only if hard filter=true
input int                   PredictorEarlySeconds       = 5;     // first seconds of the new M1 candle
input double                PredictorMinWickRatio       = 0.12;  // wick/range rejection threshold
input double                PredictorStrongTickRatio    = 0.65;
input double                PredictorVelocityPoints     = 25.0;

input group "=== SMART FIRST-MOVE ENTRY (v4.97 BASE) ==="
input bool                  SmartFirstMoveEnabled       = true;  // 5-factor next-candle timing layer
input int                   SmartObserveMinSeconds      = 2;     // V2: collect more live ticks before entry
input int                   SmartObserveMaxSeconds      = 4;     // V2: main first-move decision window
input int                   SmartMinAlignedScore        = 68;    // V2: require stronger aligned first-move
input int                   SmartLateMinScore           = 64;    // log fix: block weak late entries seen in v5.11
input bool                  SmartLateFreshnessGuard     = true;  // require live momentum before any late entry
input double                SmartLateMinVelocityPoints  = 40.0;  // v5.11: <40 points/sec produced no winners in this sample
input double                SmartLateMinAccelPoints     = 0.0;   // intended-side acceleration must not be fading/reversing
input int                   SmartMinScoreAdvantage      = 12;    // V2: selected side must clearly beat opposite
input double                SmartTickRatioGood          = 0.60;
input double                SmartTickRatioStrong        = 0.70;
input double                SmartVelocityGood           = 25.0;  // points/sec, signed to side
input double                SmartLiquidityNearPoints    = 80.0;  // proximity to previous H/L
input bool                  SmartBlockStrongOpposite    = true;

input group "=== ADAPTIVE PROFIT LOCK (virtual cash, per leg) ==="
input bool                  AdaptiveProfitLockEnabled   = true;
input double                APL_Arm1                    = 20.0;
input double                APL_Lock1                   = 8.0;  // V2
input double                APL_Arm2                    = 30.0; // V2
input double                APL_Lock2                   = 15.0; // V2
input double                APL_Arm3                    = 40.0; // V2
input double                APL_Lock3                   = 25.0; // V2
input double                APL_Arm4                    = 50.0; // V2
input double                APL_Lock4                   = 38.0; // V2
input double                APL_Arm5                    = 75.0; // V2
input double                APL_Giveback5               = 20.0; // V2: peak 75+ trails by max $20

input group "=== Lot and basket ==="
input bool                 UseFixedLot                  = true;
input double               FixedLot                    = 1.00;
input double               RiskPercent                 = 0.50; // only used when fixed lot is OFF
input int                  LegCount                    = 10;
input ENUM_INCOMPLETE_MODE IncompleteBasketMode        = INCOMPLETE_MANAGE_PARTIAL;
input bool                 CloseBasketAfterTwoM1Bars   = true;
input int                  MaxBasketM1Bars             = 2;
input bool                 UseMinimumHoldTime           = false;
input int                  MinimumHoldSeconds           = 0; // v4.61: no forced 1-minute hold

input group "=== THREE PROFILE AUTO TEST (10 positions each) ==="
input bool                 RunThreeProfileTest          = false;
input int                  PositionsPerProfile          = 10; // each profile opens this many accepted positions total
input bool                 StopAfterThreeProfiles       = true;

input group "=== P1 FINAL HYBRID (75 DIRECT + P2/P3 telemetry) ==="
input bool                 HybridDirect75Entry          = true; // >=75 base-v4.61 direction enters after hard guards
input bool                 HybridTelemetry              = true; // log P2/P3/ROC9/ATR confirmations without blocking
input bool                 UseGlobalTPSequence          = true; // accepted positions use TP1..TP10 globally across baskets
input int                  GlobalTPSequenceLength       = 10;   // 1..10, then cycles back to TP1

input group "=== CASH TP per leg (NO ATR) ==="
input ENUM_TP_MODE          TargetMode                  = TP_MODE_CASH;
input double               Leg1TargetR                 = 1.00; // legacy/unused in CASH
input double               Leg2TargetR                 = 1.50;
input double               Leg3TargetR                 = 2.00;
input double               Leg4TargetR                 = 2.50;
input double               Leg5TargetR                 = 3.00;
input double               Leg6TargetR                 = 4.00;
input double               Leg7TargetR                 = 5.00;
input double               Leg8TargetR                 = 6.00;
input double               Leg9TargetR                 = 7.00;
input double               Leg10TargetR                = 8.00;
input double               Leg1TargetMoney             = 25.0;
input double               Leg2TargetMoney             = 50.0;
input double               Leg3TargetMoney             = 75.0;
input double               Leg4TargetMoney             = 100.0;
input double               Leg5TargetMoney             = 150.0;
input double               Leg6TargetMoney             = 200.0;
input double               Leg7TargetMoney             = 300.0;
input double               Leg8TargetMoney             = 400.0;
input double               Leg9TargetMoney             = 500.0;
input double               Leg10TargetMoney            = 700.0;

input group "=== CASH SL per leg (NO ATR / NO structure sizing) ==="
// Interpreted literally from your last message; every value is editable in Inputs.
input double               Leg1StopLossMoney           = 50.0;
input double               Leg2StopLossMoney           = 50.0;
input double               Leg3StopLossMoney           = 75.0;
input double               Leg4StopLossMoney           = 100.0;
input double               Leg5StopLossMoney           = 150.0;
input double               Leg6StopLossMoney           = 100.0;
input double               Leg7StopLossMoney           = 1.0;
input double               Leg8StopLossMoney           = 1.0;
input double               Leg9StopLossMoney           = 1.0;
input double               Leg10StopLossMoney          = 1.0;

input group "=== Virtual CASH exits + broker SL backstop ==="
input bool                  UseVirtualCashSLTP          = true; // keep original virtual cash TP/SL logic
input bool                  ServerCashSLBackstopEnabled = true; // log-proven safety fix: same cash SL is also sent to broker

input group "=== Peak profit lock per leg ==="
input double               Leg1LockActivateMoney       = 18.0;
input double               Leg1LockGivebackMoney       = 5.0;
input double               Leg2LockActivateMoney       = 35.0;
input double               Leg2LockGivebackMoney       = 7.0;
input double               Leg3LockActivateMoney       = 55.0;
input double               Leg3LockGivebackMoney       = 10.0;
input double               Leg4LockActivateMoney       = 75.0;
input double               Leg4LockGivebackMoney       = 10.0;
input double               Leg5LockActivateMoney       = 110.0;
input double               Leg5LockGivebackMoney       = 15.0;
input double               Leg6LockActivateMoney       = 150.0;
input double               Leg6LockGivebackMoney       = 20.0;
input double               Leg7LockActivateMoney       = 220.0;
input double               Leg7LockGivebackMoney       = 25.0;
input double               Leg8LockActivateMoney       = 300.0;
input double               Leg8LockGivebackMoney       = 30.0;
input double               Leg9LockActivateMoney       = 400.0;
input double               Leg9LockGivebackMoney       = 40.0;
input double               Leg10LockActivateMoney      = 550.0;
input double               Leg10LockGivebackMoney      = 50.0;

input group "=== Legacy ATR/structure diagnostics only ==="
input ENUM_STOP_MODE        StopMode                    = STOP_MODE_STRUCTURE_ATR_FALLBACK; // unused for entry SL in CASH-SL build
input int                   ATRPeriod                   = 14; // diagnostics only
input double                StructureSLBufferPoints     = 30.0; // diagnostics only
input bool                  RejectIfNoStructureStop     = false; // unused for cash SL

input group "=== Basket risk ==="
input double                MaxLossMoneyPerLeg          = 0.0; // per-leg CASH SL already defines loss
input double                MaxRiskPerSignalMoney       = 600.0;
input double                MaxRiskPerSignalPercent     = 0.0; // disabled; use explicit cash basket cap above
input double                MarginSafetyFactor          = 1.10;

input group "=== TP1 to break-even ==="
input bool                  MoveRemainingToBEAfterTP1   = false;
input bool                  BEIncludeCosts              = true;
input double                BESpreadBufferPoints        = 0.0;
input double                BECommissionBufferMoneyLot  = 0.0;

input group "=== FastFlow live ticks ==="
input int                   TickWindowSeconds           = 5;
input int                   MinDirectionalTicks         = 3;
input double                MinTickRatio                = 0.58;
input double                StrongTickRatio             = 0.68;
input int                   StrongFlowMinTicks          = 5;
input double                StrongVelocityPointsPerSec  = 5.0;
input int                   MaxFlowSamples              = 1024;

input group "=== Closed-bar market structure ==="
input int                   SwingLeftBars               = 2;
input int                   SwingRightBars              = 2;
input int                   StructureLookbackBars       = 100;
input double                BOSMinDisplacementATR       = 0.03;
input int                   StructureEventMaxAgeBars    = 8;
input double                SweepMinPenetrationATR      = 0.05;
input int                   SweepMaxAgeBars             = 3;
input int                   FVGSearchBars               = 20;
input double                MinFVGSizeATR               = 0.05;
input double                FVGToleranceATR             = 0.08;
input double                SRNearATR                   = 0.25;

input group "=== Scores and entry quality ==="
input int                   MinDirectionPercent         = 75; // v4.61: slightly stronger BUY/SELL confirmation
input int                   MinSetupScore               = 50;
input int                   MinEntryQuality             = 50;
input double                MaxChaseATR                 = 0.60;
input double                DesiredRoomATR              = 1.20;
input double                MinimumRoomATR              = 0.50;

input group "=== Strong FastFlow entry route ==="
input bool                  UseStrongFastFlowEntry      = true;
input int                   StrongFlowMinDirectionScore = 75;
input int                   StrongFlowMinEntryQuality   = 50;
input bool                  StrongFlowAllowZeroStructure= true;

input group "=== Liquidity reversal entry route ==="
input bool                  UseLiquidityReversalEntry    = true;
input int                   LiquidityMinDirectionScore   = 45;
input int                   LiquidityMinStructureScore   = 30;
input int                   LiquidityMinCombinedScore    = 45;
input int                   LiquidityMinEntryQuality     = 70;

input group "=== Spread and execution guards ==="
input int                   MaxSpreadPoints             = 300;
input int                   SpreadEWMASpan              = 60;
input double                SpreadShockRatio            = 3.00; // v4.71 relaxed for 3-profile comparison
input double                MaxSpreadATRRatio           = 0.25;
input int                   MaxTickAgeSeconds           = 3;
input int                   MaxSlippagePoints           = 30;
input int                   CooldownSeconds             = 2;
input int                   EntryReconcileSeconds       = 5;

input group "=== Standalone risk guards ==="
input bool                  DailyLossLimitEnabled       = false; // v4.57 TEST: loss halts disabled
input double                MaxDailyLossPercent         = 0.0; // DISABLED
input double                MaxWeeklyLossPercent        = 0.0; // DISABLED
input double                MaxEquityDrawdownPercent    = 0.0; // DISABLED
input int                   MaxConsecutiveLosses        = 0; // DISABLED
input int                   MaxBasketsPerHour           = 0;
input int                   MaxBasketsPerDay            = 0;

input group "=== Opposite watcher ==="
input int                   OppositeArmScore            = 65;
input int                   OppositeFreshSeconds        = 20;

input group "=== Identity and display ==="
input ulong                 MagicNumber                 = 990500;
input bool                  DebugAuditLog               = true;
input bool                  ShowPanel                   = true;

//====================================================================
// GLOBAL STATE
//====================================================================
CTrade trade;
int hEMA50=INVALID_HANDLE;
int hEMA200=INVALID_HANDLE;
int hADX10=INVALID_HANDLE;
int hADX14=INVALID_HANDLE;

LegConfig  g_legConfig[MAX_LEGS];
LegRuntime g_legs[MAX_LEGS];
TickSample g_ticks[];

datetime g_currentBarTime=0;
bool     g_closedDataReady=false;
double   g_ema50=0.0;
double   g_ema200=0.0;
double   g_ema50SlopePoints=0.0;
double   g_ema200SlopePoints=0.0;
double   g_adx10=0.0;
double   g_plusDI10=0.0;
double   g_minusDI10=0.0;
double   g_adx14=0.0;
double   g_plusDI14=0.0;
double   g_minusDI14=0.0;
double   g_crsi=50.0;
double   g_crsiPrevious=50.0;
double   g_atr=0.0;
double   g_atrPrevious=0.0;
double   g_lastClosedOpen=0.0;
double   g_lastClosedHigh=0.0;
double   g_lastClosedLow=0.0;
double   g_lastClosedClose=0.0;
double   g_lastClosedRange=0.0;
bool     g_volatilitySpike=false;
int      g_nextBuyScore=0;
int      g_nextSellScore=0;
string   g_nextCandleBias="NEUTRAL";
double   g_prevBodyRatio=0.0;
double   g_prevUpperWickRatio=0.0;
double   g_prevLowerWickRatio=0.0;
string   g_safeRegime="UNKNOWN";
bool     g_safeFalseBreakUp=false;
bool     g_safeFalseBreakDown=false;
double   g_safeBarHigh=0.0;
double   g_safeBarLow=0.0;
bool     g_safeChaseBlockedBuy=false;
bool     g_safeChaseBlockedSell=false;

// v4.97 uses v4.94 FAST/NORMAL logic; adds AI shadow telemetry and 50 accepted-order study
string   g_v494SelectedRoute="NORMAL";
// v4.97 AI shadow state is telemetry only. It must never control trade decisions.
int      g_v497LastAIScore=50;
string   g_v497LastAIState="NEUTRAL";
string   g_v497LastAIWould="WAIT";
int      g_v494Accepted=0;
int      g_v494Closed=0;
int      g_v494FastAccepted=0;
int      g_v494NormalAccepted=0;
int      g_v494FastClosed=0;
int      g_v494NormalClosed=0;
int      g_v494FastWins=0;
int      g_v494NormalWins=0;
int      g_v494FastLosses=0;
int      g_v494NormalLosses=0;
double   g_v494FastNet=0.0;
double   g_v494NormalNet=0.0;
double   g_v494FastMFE=0.0;
double   g_v494NormalMFE=0.0;
double   g_v494FastMAE=0.0;
double   g_v494NormalMAE=0.0;
int      g_v494BuyAccepted=0;
int      g_v494SellAccepted=0;
int      g_v494ExitTP=0;
int      g_v494ExitSL=0;
int      g_v494ExitProfitLock=0;
int      g_v494ExitWrongWay=0;
bool     g_v494SummaryPrinted=false;
bool     g_exhaustBuyArmed=false;
bool     g_exhaustSellArmed=false;
bool     g_exhaustBuyRetraceSeen=false;
bool     g_exhaustSellRetraceSeen=false;
double   g_exhaustBuyExtreme=0.0;
double   g_exhaustSellExtreme=0.0;
datetime g_exhaustBuyArmTime=0;
datetime g_exhaustSellArmTime=0;
datetime g_exhaustArmBar=0;
bool     g_reverseSeekActive=false;
ENUM_SIDE g_reverseSeekSide=SIDE_BUY;
datetime g_reverseSeekStart=0;
double   g_reverseSeekExtreme=0.0;

ENUM_STRUCTURE g_structure=STRUCTURE_UNKNOWN;
SwingPoint g_swingHigh;
SwingPoint g_previousSwingHigh;
SwingPoint g_swingLow;
SwingPoint g_previousSwingLow;
bool       g_hh=false;
bool       g_hl=false;
bool       g_lh=false;
bool       g_ll=false;
datetime   g_bullBOSTime=0;
datetime   g_bearBOSTime=0;
datetime   g_bullCHoCHTime=0;
datetime   g_bearCHoCHTime=0;
double     g_bullBOSLevel=0.0;
double     g_bearBOSLevel=0.0;
datetime   g_bullSweepTime=0;
datetime   g_bearSweepTime=0;
double     g_bullSweepExtreme=0.0;
double     g_bearSweepExtreme=0.0;

bool       g_bullFibValid=false;
bool       g_bearFibValid=false;
double     g_bullFibOrigin=0.0;
double     g_bullFibExtreme=0.0;
double     g_bearFibOrigin=0.0;
double     g_bearFibExtreme=0.0;
datetime   g_bullFibTime=0;
datetime   g_bearFibTime=0;

bool       g_bullFVGActive=false;
bool       g_bearFVGActive=false;
double     g_bullFVGLow=0.0;
double     g_bullFVGHigh=0.0;
double     g_bearFVGLow=0.0;
double     g_bearFVGHigh=0.0;
datetime   g_bullFVGTime=0;
datetime   g_bearFVGTime=0;

ENUM_FLOW  g_flow=FLOW_NEUTRAL;
int        g_totalTicks=0;
int        g_upTicks=0;
int        g_downTicks=0;
int        g_neutralTicks=0;
double     g_ticksPerSecond=0.0;
double     g_buyTickRatio=0.0;
double     g_sellTickRatio=0.0;
double     g_bidVelocityPoints=0.0;
double     g_askVelocityPoints=0.0;
double     g_accelerationPoints=0.0;
double     g_displacementPoints=0.0;
long       g_lastBuyFlowMsc=0;
long       g_lastSellFlowMsc=0;

double     g_currentSpreadPoints=0.0;
double     g_spreadBaselinePoints=0.0;
bool       g_spreadShock=false;
double     g_tickAgeSeconds=0.0;

// Three-profile experiment telemetry. Profiles switch only after all positions
// of the current profile are closed, so P/L attribution stays unambiguous.
int        g_testProfile=1; // 1=V4.61 BASE, 2=DMI/ADX10 + CRSI70/30, 3=ATR14 + ROC9 zero-line
int        g_profileOpened[3]={0,0,0};
int        g_profileClosed[3]={0,0,0};
double     g_profileNet[3]={0.0,0.0,0.0};
int        g_globalTpAcceptedCount=0; // persisted count of broker-accepted positions for TP1..TP10 sequencing
bool       g_threeProfileFinished=false;
double     g_roc9=0.0;
double     g_roc9Previous=0.0;

int        g_buyDirectionScore=0;
int        g_sellDirectionScore=0;
int        g_buyStructureScore=0;
int        g_sellStructureScore=0;
int        g_buyEntryQuality=0;
int        g_sellEntryQuality=0;
int        g_buyCombinedScore=0;
int        g_sellCombinedScore=0;

bool       g_entryPending=false;
bool       g_allEntryRequestsSent=false;
bool       g_incompleteBasket=false;
datetime   g_entryRequestTime=0;
int        g_expectedLegs=0;
ENUM_SIDE  g_pendingEntrySide=SIDE_BUY;
string     g_pendingSignalId="";
string     g_pendingSetupType="";
int        g_pendingDirectionScore=0;
int        g_pendingStructureScore=0;
int        g_pendingEntryQuality=0;
double     g_projectedBasketRisk=0.0;

bool       g_basketActive=false;
ulong      g_basketId=0;
ENUM_SIDE  g_basketSide=SIDE_BUY;
datetime   g_basketStartTime=0;
double     g_basketRealized=0.0;
bool       g_tp1Reached=false;
datetime   g_lastTradeCloseTime=0;
datetime   g_lastSignalBarTime=0;
int        g_lastSignalSide=-1;

ENUM_OPPOSITE_STATE g_oppositeState=OPP_IDLE;
ENUM_SIDE           g_oppositeSide=SIDE_SELL;
datetime            g_oppositeArmedTime=0;

datetime   g_dayStamp=0;
datetime   g_weekStamp=0;
datetime   g_hourStamp=0;
double     g_dayStartBalance=0.0;
double     g_dayStartEquity=0.0;
double     g_weekStartBalance=0.0;
double     g_peakEquity=0.0;
double     g_dailyRealized=0.0;
double     g_weeklyRealized=0.0;
double     g_equityDrawdownPercent=0.0;
int        g_consecutiveLosses=0;
int        g_basketsThisHour=0;
int        g_basketsToday=0;
bool       g_riskHalt=false;
ENUM_HALT_REASON g_haltReason=HALT_NONE;
string     g_gvPrefix="";

datetime   g_lastRiskRefresh=0;
datetime   g_lastReconcile=0;
datetime   g_lastPersist=0;
long       g_lastPanelMsc=0;
string     g_statusText="INITIALIZING";
string     g_lastRejectKey="";
datetime   g_lastRejectTime=0;
string     g_lastLifecycleLogKey="";

//====================================================================
// FORWARD DECLARATIONS
//====================================================================
void ConfigureLegs();
void ResetLegRuntime(const int index);
bool ValidateInputs(string &reason);
bool IsNewM1Bar();
bool RefreshClosedBarData();
bool RefreshMarketStructure();
void UpdateFastFlow(const MqlTick &tick);
void UpdateSpreadState(const MqlTick &tick);
void UpdateNextCandlePredictor();
int SmartFirstMoveScore(const ENUM_SIDE side,string &detail);
bool SmartFirstMoveAllows(const ENUM_SIDE side,string &reason,string &tag);
void UpdateSafeTelemetry(const MqlTick &tick,const bool new_bar);
bool UpdateReverseSeek();
void UpdateRiskState(const bool force_history);
void ReconcilePositions(const bool startup);
double AdaptiveProfitLockFloor(const double peak_profit)
  {
   if(!AdaptiveProfitLockEnabled) return -DBL_MAX;
   if(peak_profit>=APL_Arm5) return MathMax(APL_Lock4,peak_profit-APL_Giveback5);
   if(peak_profit>=APL_Arm4) return APL_Lock4;
   if(peak_profit>=APL_Arm3) return APL_Lock3;
   if(peak_profit>=APL_Arm2) return APL_Lock2;
   if(peak_profit>=APL_Arm1) return APL_Lock1;
   return -DBL_MAX;
  }


bool OppositeMicroReversalConfirmed(const ENUM_SIDE position_side,const int min_pred_score,const double min_tick_ratio,const bool require_flow)
  {
   ENUM_SIDE opposite=(position_side==SIDE_BUY ? SIDE_SELL : SIDE_BUY);
   int pred=(opposite==SIDE_BUY ? g_nextBuyScore : g_nextSellScore);
   double ratio=(opposite==SIDE_BUY ? g_buyTickRatio : g_sellTickRatio);
   bool bias=(g_nextCandleBias==(opposite==SIDE_BUY ? "BUY" : "SELL"));
   bool flow=(opposite==SIDE_BUY ? (g_flow==FLOW_BUY || g_flow==FLOW_STRONG_BUY)
                                 : (g_flow==FLOW_SELL || g_flow==FLOW_STRONG_SELL));
   if(!require_flow) flow=true;
   return (bias && pred>=min_pred_score && ratio>=min_tick_ratio && flow);
  }

void ManageOpenPositions();
void HandleEntryPending();
void EvaluateNewEntry();
void UpdateOppositeWatcher();
void UpdatePanel();
void LoadPersistentState();
void PersistState(const bool force_write);
void RecalculateRealizedHistory();
void FinalizeBasket();

//====================================================================
// BASIC UTILITIES
//====================================================================
string SideText(const ENUM_SIDE side)
  {
   return side==SIDE_BUY ? "BUY" : "SELL";
  }

string FlowText(const ENUM_FLOW flow)
  {
   if(flow==FLOW_BUY) return "FLOW_BUY";
   if(flow==FLOW_SELL) return "FLOW_SELL";
   if(flow==FLOW_STRONG_BUY) return "FLOW_STRONG_BUY";
   if(flow==FLOW_STRONG_SELL) return "FLOW_STRONG_SELL";
   return "FLOW_NEUTRAL";
  }

string StructureText(const ENUM_STRUCTURE value)
  {
   if(value==STRUCTURE_BULLISH) return "STRUCTURE_BULLISH";
   if(value==STRUCTURE_BEARISH) return "STRUCTURE_BEARISH";
   if(value==STRUCTURE_RANGE) return "STRUCTURE_RANGE";
   return "STRUCTURE_UNKNOWN";
  }

string OppositeStateText()
  {
   if(g_oppositeState==OPP_WATCHING) return "OPP_WATCHING";
   if(g_oppositeState==OPP_ARMED) return "OPP_ARMED";
   if(g_oppositeState==OPP_REVALIDATE) return "OPP_REVALIDATE";
   return "OPP_IDLE";
  }

string HaltReasonText()
  {
   if(g_haltReason==HALT_DAILY_LOSS) return "DAILY_LOSS";
   if(g_haltReason==HALT_WEEKLY_LOSS) return "WEEKLY_LOSS";
   if(g_haltReason==HALT_EQUITY_DRAWDOWN) return "EQUITY_DRAWDOWN";
   if(g_haltReason==HALT_CONSECUTIVE_LOSSES) return "CONSECUTIVE_LOSSES";
   if(g_haltReason==HALT_BASKETS_PER_HOUR) return "BASKETS_PER_HOUR";
   if(g_haltReason==HALT_BASKETS_PER_DAY) return "BASKETS_PER_DAY";
   return "NONE";
  }

double PointSize()
  {
   return SymbolInfoDouble(_Symbol,SYMBOL_POINT);
  }

double TickSize()
  {
   double tick=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE);
   if(tick<=0.0) tick=PointSize();
   return tick;
  }

double NormalizePriceDown(const double price)
  {
   double tick=TickSize();
   int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
   if(tick<=0.0) return NormalizeDouble(price,digits);
   return NormalizeDouble(MathFloor(price/tick+1e-9)*tick,digits);
  }

double NormalizePriceUp(const double price)
  {
   double tick=TickSize();
   int digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
   if(tick<=0.0) return NormalizeDouble(price,digits);
   return NormalizeDouble(MathCeil(price/tick-1e-9)*tick,digits);
  }

double BrokerMinimumDistance()
  {
   long stops=SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   long freeze=SymbolInfoInteger(_Symbol,SYMBOL_TRADE_FREEZE_LEVEL);
   return (double)MathMax(stops,freeze)*PointSize();
  }

bool IsRequestSuccessRetcode(const uint retcode)
  {
   return retcode==TRADE_RETCODE_DONE;
  }

bool IsEntryAcceptedRetcode(const uint retcode)
  {
   return retcode==TRADE_RETCODE_DONE || retcode==TRADE_RETCODE_DONE_PARTIAL;
  }

datetime StartOfDay(const datetime value)
  {
   MqlDateTime parts;
   TimeToStruct(value,parts);
   parts.hour=0;
   parts.min=0;
   parts.sec=0;
   return StructToTime(parts);
  }

datetime StartOfHour(const datetime value)
  {
   MqlDateTime parts;
   TimeToStruct(value,parts);
   parts.min=0;
   parts.sec=0;
   return StructToTime(parts);
  }

datetime StartOfWeek(const datetime value)
  {
   MqlDateTime parts;
   TimeToStruct(value,parts);
   int days_back=(parts.day_of_week+6)%7;
   return StartOfDay(value)-days_back*86400;
  }

double GVGet(const string key,const double fallback)
  {
   string name=g_gvPrefix+key;
   if(!GlobalVariableCheck(name)) return fallback;
   return GlobalVariableGet(name);
  }

void GVSet(const string key,const double value)
  {
   GlobalVariableSet(g_gvPrefix+key,value);
  }

bool IsVolumeStepValid(const double volume)
  {
   double minimum=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   double maximum=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
   double step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
   if(volume<minimum-1e-10 || volume>maximum+1e-10 || step<=0.0) return false;
   double steps=(volume-minimum)/step;
   return MathAbs(steps-MathRound(steps))<=1e-6;
  }

double NormalizeVolumeDown(const double requested)
  {
   double minimum=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   double maximum=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
   double step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
   if(step<=0.0 || requested<minimum) return 0.0;
   double clipped=MathMin(requested,maximum);
   double steps=MathFloor((clipped-minimum)/step+1e-9);
   double volume=minimum+steps*step;
   int volume_digits=2;
   if(step<0.01) volume_digits=3;
   if(step<0.001) volume_digits=4;
   return NormalizeDouble(volume,volume_digits);
  }

int ActivePositionCount()
  {
   int count=0;
   for(int i=PositionsTotal()-1;i>=0;i--)
     {
      ulong ticket=PositionGetTicket(i);
      if(ticket==0) continue;
      if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
      if((ulong)PositionGetInteger(POSITION_MAGIC)!=MagicNumber) continue;
      count++;
     }
   return count;
  }

bool HasOwnPositions()
  {
   return ActivePositionCount()>0;
  }

int ParseLegId(const string comment)
  {
   int pos=StringFind(comment,"LEG=");
   if(pos<0) return 0;
   string suffix=StringSubstr(comment,pos+4);
   int slash=StringFind(suffix,"/");
   if(slash>=0) suffix=StringSubstr(suffix,0,slash);
   int value=(int)StringToInteger(suffix);
   if(value<1 || value>MAX_LEGS) return 0;
   return value;
  }

ulong ParseBasketId(const string comment)
  {
   int pos=StringFind(comment," B=");
   if(pos<0) return 0;
   return (ulong)StringToInteger(StringSubstr(comment,pos+3));
  }

int FindLegByTicket(const ulong ticket)
  {
   for(int i=0;i<MAX_LEGS;i++)
      if(g_legs[i].ticket==ticket && ticket>0) return i;
   return -1;
  }

int FindLegByPositionId(const ulong position_id)
  {
   for(int i=0;i<MAX_LEGS;i++)
      if(g_legs[i].position_id==position_id && position_id>0) return i;
   return -1;
  }

double PositionNetProfitBySelection(const int leg_index)
  {
   double value=PositionGetDouble(POSITION_PROFIT)+PositionGetDouble(POSITION_SWAP);
   if(leg_index>=0 && leg_index<MAX_LEGS) value+=g_legs[leg_index].entry_costs;
   return value;
  }

double BasketFloatingRaw()
  {
   double total=0.0;
   for(int i=PositionsTotal()-1;i>=0;i--)
     {
      ulong ticket=PositionGetTicket(i);
      if(ticket==0) continue;
      if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
      if((ulong)PositionGetInteger(POSITION_MAGIC)!=MagicNumber) continue;
      total+=PositionGetDouble(POSITION_PROFIT)+PositionGetDouble(POSITION_SWAP);
     }
   return total;
  }

//====================================================================
// CONFIGURATION, INIT AND MAIN LOOP
//====================================================================
void ConfigureLegs()
  {
   double target_r[MAX_LEGS]={Leg1TargetR,Leg2TargetR,Leg3TargetR,Leg4TargetR,Leg5TargetR,Leg6TargetR,Leg7TargetR,Leg8TargetR,Leg9TargetR,Leg10TargetR};
   double target_money[MAX_LEGS]={Leg1TargetMoney,Leg2TargetMoney,Leg3TargetMoney,Leg4TargetMoney,Leg5TargetMoney,Leg6TargetMoney,Leg7TargetMoney,Leg8TargetMoney,Leg9TargetMoney,Leg10TargetMoney};
   double stop_loss_money[MAX_LEGS]={Leg1StopLossMoney,Leg2StopLossMoney,Leg3StopLossMoney,Leg4StopLossMoney,Leg5StopLossMoney,Leg6StopLossMoney,Leg7StopLossMoney,Leg8StopLossMoney,Leg9StopLossMoney,Leg10StopLossMoney};
   double activate[MAX_LEGS]={Leg1LockActivateMoney,Leg2LockActivateMoney,Leg3LockActivateMoney,Leg4LockActivateMoney,Leg5LockActivateMoney,Leg6LockActivateMoney,Leg7LockActivateMoney,Leg8LockActivateMoney,Leg9LockActivateMoney,Leg10LockActivateMoney};
   double giveback[MAX_LEGS]={Leg1LockGivebackMoney,Leg2LockGivebackMoney,Leg3LockGivebackMoney,Leg4LockGivebackMoney,Leg5LockGivebackMoney,Leg6LockGivebackMoney,Leg7LockGivebackMoney,Leg8LockGivebackMoney,Leg9LockGivebackMoney,Leg10LockGivebackMoney};
   for(int i=0;i<MAX_LEGS;i++)
     {
      g_legConfig[i].leg_id=i+1;
      g_legConfig[i].target_r=target_r[i];
      g_legConfig[i].target_money=target_money[i];
      g_legConfig[i].stop_loss_money=stop_loss_money[i];
      g_legConfig[i].activate_money=activate[i];
      g_legConfig[i].giveback_money=giveback[i];
      ResetLegRuntime(i);
     }
  }

void ResetLegRuntime(const int index)
  {
   g_legs[index].leg_id=index+1;
   g_legs[index].ticket=0;
   g_legs[index].position_id=0;
   g_legs[index].requested_volume=0.0;
   g_legs[index].filled_volume=0.0;
   g_legs[index].entry=0.0;
   g_legs[index].sl=0.0;
   g_legs[index].tp=0.0;
   g_legs[index].target_money=g_legConfig[index].target_money;
   g_legs[index].risk_money=0.0;
   g_legs[index].peak_profit=-DBL_MAX;
   g_legs[index].mfe=-DBL_MAX;
   g_legs[index].mae=DBL_MAX;
   g_legs[index].entry_costs=0.0;
   g_legs[index].lock_armed=false;
   g_legs[index].break_even_applied=false;
   g_legs[index].status=LEG_UNUSED;
   g_legs[index].pending_exit_reason="";
   g_legs[index].entry_route="";
  }

bool ValidateInputs(string &reason)
  {
   if(RunThreeProfileTest && PositionsPerProfile<1)
     {
      reason="PositionsPerProfile must be >= 1";
      return false;
     }
   if(V494AcceptedOrderLimit<1)
     {
      reason="V494AcceptedOrderLimit must be >= 1";
      return false;
     }
   if(V494FastMinDirection<MinDirectionPercent || V494FastMinDirection>100 ||
      V494FastMinTickRatio<=0.50 || V494FastMinTickRatio>1.0 ||
      V494FastMaxDispATR<=0.0)
     {
      reason="invalid v4.94 FAST router parameters";
      return false;
     }
   if(LegCount<1 || LegCount>MAX_LEGS)
     {
      reason="LegCount must be 1..10";
      return false;
     }
   if(GlobalTPSequenceLength<1 || GlobalTPSequenceLength>MAX_LEGS)
     {
      reason="GlobalTPSequenceLength must be 1..10";
      return false;
     }
   if(LegCount>1 && (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
     {
      reason="LegCount > 1 requires ACCOUNT_MARGIN_MODE_RETAIL_HEDGING";
      return false;
     }
   if(ATRPeriod<2 || SwingLeftBars<1 || SwingRightBars<1 || StructureLookbackBars<20)
     {
      reason="invalid indicator or swing parameters";
      return false;
     }
   if(StructureSLBufferPoints<0.0)
     {
      reason="StructureSLBufferPoints must be >= 0";
      return false;
     }
   if(CloseBasketAfterTwoM1Bars && MaxBasketM1Bars<1)
     {
      reason="MaxBasketM1Bars must be >= 1";
      return false;
     }
   if(MinDirectionPercent<0 || MinDirectionPercent>100 || MinSetupScore<0 || MinSetupScore>100 || MinEntryQuality<0 || MinEntryQuality>100 ||
      StrongFlowMinDirectionScore<0 || StrongFlowMinDirectionScore>100 ||
      StrongFlowMinEntryQuality<0 || StrongFlowMinEntryQuality>100 ||
      LiquidityMinDirectionScore<0 || LiquidityMinDirectionScore>100 ||
      LiquidityMinStructureScore<0 || LiquidityMinStructureScore>100 ||
      LiquidityMinCombinedScore<0 || LiquidityMinCombinedScore>100 ||
      LiquidityMinEntryQuality<0 || LiquidityMinEntryQuality>100)
     {
      reason="score thresholds must be 0..100";
      return false;
     }
   if(TickWindowSeconds<1 || MinTickRatio<=0.5 || StrongTickRatio<MinTickRatio || StrongTickRatio>1.0)
     {
      reason="invalid FastFlow parameters";
      return false;
     }
   if(UseFixedLot && !IsVolumeStepValid(FixedLot))
     {
      reason="FixedLot is outside broker volume range or not aligned to volume step";
      return false;
     }
   double previous=0.0;
   for(int i=0;i<LegCount;i++)
     {
      double target=TargetMode==TP_MODE_CASH ? g_legConfig[i].target_money : g_legConfig[i].target_r;
      if(target<=0.0 || target<=previous)
        {
         reason="active leg targets must be positive and strictly increasing";
         return false;
        }
      if(g_legConfig[i].stop_loss_money<=0.0)
        {
         reason="active leg cash SL must be positive";
         return false;
        }
      if(g_legConfig[i].activate_money<0.0 || g_legConfig[i].giveback_money<=0.0)
        {
         reason="profit-lock activation must be nonnegative and giveback must be positive";
         return false;
        }
      previous=target;
     }
   return true;
  }

int OnInit()
  {
   ConfigureLegs();
   string reason="";
   if(!ValidateInputs(reason))
     {
      Print("EntryInfinityMicroScalper init failed: ",reason);
      return INIT_PARAMETERS_INCORRECT;
     }

   hEMA50=iMA(_Symbol,PERIOD_M1,EMA_FAST_PERIOD,0,MODE_EMA,PRICE_CLOSE);
   hEMA200=iMA(_Symbol,PERIOD_M1,EMA_SLOW_PERIOD,0,MODE_EMA,PRICE_CLOSE);
   hADX10=iADX(_Symbol,PERIOD_M1,DMI_FAST_PERIOD);
   hADX14=iADX(_Symbol,PERIOD_M1,DMI_SLOW_PERIOD);
   if(hEMA50==INVALID_HANDLE || hEMA200==INVALID_HANDLE || hADX10==INVALID_HANDLE || hADX14==INVALID_HANDLE)
     {
      Print("EntryInfinityMicroScalper init failed: required indicator handle creation failed");
      return INIT_FAILED;
     }

   trade.SetExpertMagicNumber((long)MagicNumber);
   trade.SetDeviationInPoints(MaxSlippagePoints);
   trade.SetAsyncMode(false);
   trade.SetTypeFillingBySymbol(_Symbol);
   ArrayResize(g_ticks,0);
   g_gvPrefix=StringFormat("EIMS4.%I64u.%s.",MagicNumber,_Symbol);
   LoadPersistentState();
   RecalculateRealizedHistory();
   UpdateRiskState(false);
   g_currentBarTime=iTime(_Symbol,PERIOD_M1,0);
   g_closedDataReady=RefreshClosedBarData() && RefreshMarketStructure();
   ReconcilePositions(true);
   g_statusText=g_basketActive ? "MANAGING REBUILT BASKET" : "WAIT";
   if(ShowPanel) UpdatePanel();
   PrintFormat("EntryInfinityMicroScalper v4.80 P1_FINAL_HYBRID_75_DIRECT initialized symbol=%s magic=%I64u legs=%d mode=%s maxBars=%d MIN_HOLD=OFF TP_ACTIVE=YES VIRTUAL_CASH_SLTP=%s ATR_SLTP=OFF SPREAD_SHOCK_BLOCK=OFF(test)",
               _Symbol,MagicNumber,LegCount,TargetMode==TP_MODE_CASH ? "CASH" : "R_BASED",MaxBasketM1Bars,UseVirtualCashSLTP ? "ON" : "OFF");
   return INIT_SUCCEEDED;
  }

void OnDeinit(const int reason)
  {
   PersistState(true);
   if(hEMA50!=INVALID_HANDLE) IndicatorRelease(hEMA50);
   if(hEMA200!=INVALID_HANDLE) IndicatorRelease(hEMA200);
   if(hADX10!=INVALID_HANDLE) IndicatorRelease(hADX10);
   if(hADX14!=INVALID_HANDLE) IndicatorRelease(hADX14);
   if(ShowPanel)
     {
      ObjectDelete(0,"EIMS_STATUS_TEXT");
      ObjectDelete(0,"EIMS_STATUS_BG");
      for(int i=0;i<40;i++)
         ObjectDelete(0,StringFormat("EIMS_STATUS_LINE_%02d",i));
      Comment("");
     }
  }

void OnTick()
  {
   MqlTick tick;
   if(!SymbolInfoTick(_Symbol,tick))
     {
      g_statusText="NO BROKER TICK";
      return;
     }

   bool new_m1_bar=IsNewM1Bar();
   if(new_m1_bar)
      ArrayResize(g_ticks,0); // keep first-tick predictor from inheriting the previous M1 flow window

   UpdateFastFlow(tick);
   UpdateSpreadState(tick);

   if(new_m1_bar)
      g_closedDataReady=RefreshClosedBarData() && RefreshMarketStructure();

   UpdateNextCandlePredictor();
   UpdateSafeTelemetry(tick,new_m1_bar);

   datetime now=TimeCurrent();
   if(now!=g_lastRiskRefresh)
     {
      g_lastRiskRefresh=now;
      UpdateRiskState(false);
     }
   if(now!=g_lastReconcile)
     {
      g_lastReconcile=now;
      ReconcilePositions(false);
     }

   if(HasOwnPositions())
     {
      g_basketActive=true;
      ManageOpenPositions();
      UpdateOppositeWatcher();
     }
   else
     {
      if(g_basketActive && !g_entryPending)
         FinalizeBasket();
      if(g_entryPending)
         HandleEntryPending();
      else if(g_oppositeState==OPP_REVALIDATE)
         UpdateOppositeWatcher();
      else if(!UpdateReverseSeek())
         EvaluateNewEntry();
     }

   PersistState(false);
   if(ShowPanel && (tick.time_msc-g_lastPanelMsc>=250 || g_lastPanelMsc==0))
     {
      g_lastPanelMsc=tick.time_msc;
      UpdatePanel();
     }
  }

bool IsNewM1Bar()
  {
   datetime value=iTime(_Symbol,PERIOD_M1,0);
   if(value<=0 || value==g_currentBarTime) return false;
   g_currentBarTime=value;
   return true;
  }

//====================================================================
// CLOSED-BAR INDICATORS: EMA50/200, DMI10, DMI14, TRUE CRSI, ATR
//====================================================================
bool CopyLatestTwo(const int handle,const int buffer,double &latest,double &previous)
  {
   double values[];
   ArrayResize(values,2);
   int copied=CopyBuffer(handle,buffer,1,2,values);
   if(copied!=2) return false;
   previous=values[0];
   latest=values[1];
   return MathIsValidNumber(latest) && MathIsValidNumber(previous);
  }

double WilderRSIAt(const double &values[],const int count,const int period,const int target)
  {
   if(period<1 || target<0 || count-target<=period) return 50.0;
   int seed_index=count-1-period;
   if(seed_index<target) return 50.0;
   double average_gain=0.0;
   double average_loss=0.0;
   for(int i=count-2;i>=seed_index;i--)
     {
      double change=values[i]-values[i+1];
      if(change>0.0) average_gain+=change;
      else average_loss-=change;
     }
   average_gain/=period;
   average_loss/=period;
   for(int i=seed_index-1;i>=target;i--)
     {
      double change=values[i]-values[i+1];
      double gain=change>0.0 ? change : 0.0;
      double loss=change<0.0 ? -change : 0.0;
      average_gain=(average_gain*(period-1)+gain)/period;
      average_loss=(average_loss*(period-1)+loss)/period;
     }
   if(average_loss<=DBL_EPSILON)
      return average_gain<=DBL_EPSILON ? 50.0 : 100.0;
   double rs=average_gain/average_loss;
   return 100.0-(100.0/(1.0+rs));
  }

void BuildStreakSeries(const double &closes[],const int count,double &streaks[])
  {
   ArrayResize(streaks,count);
   if(count<=0) return;
   streaks[count-1]=0.0;
   for(int i=count-2;i>=0;i--)
     {
      if(closes[i]>closes[i+1])
         streaks[i]=streaks[i+1]>0.0 ? streaks[i+1]+1.0 : 1.0;
      else if(closes[i]<closes[i+1])
         streaks[i]=streaks[i+1]<0.0 ? streaks[i+1]-1.0 : -1.0;
      else
         streaks[i]=0.0;
     }
  }

double PercentRankROCAt(const double &closes[],const int count,const int target)
  {
   if(target<0 || target+CRSI_RANK_PERIOD+1>=count) return 50.0;
   if(MathAbs(closes[target+1])<=DBL_EPSILON) return 50.0;
   double current_change=100.0*(closes[target]-closes[target+1])/MathAbs(closes[target+1]);
   int less=0;
   int equal=0;
   for(int i=target+1;i<=target+CRSI_RANK_PERIOD;i++)
     {
      if(MathAbs(closes[i+1])<=DBL_EPSILON) continue;
      double past_change=100.0*(closes[i]-closes[i+1])/MathAbs(closes[i+1]);
      if(past_change<current_change) less++;
      else if(MathAbs(past_change-current_change)<=1e-12) equal++;
     }
   return 100.0*((double)less+0.5*(double)equal)/(double)CRSI_RANK_PERIOD;
  }

double ConnorsRSIAt(const double &closes[],const int count,const double &streaks[],const int target)
  {
   double price_rsi=WilderRSIAt(closes,count,CRSI_PRICE_PERIOD,target);
   double streak_rsi=WilderRSIAt(streaks,count,CRSI_STREAK_PERIOD,target);
   double rank=PercentRankROCAt(closes,count,target);
   return (price_rsi+streak_rsi+rank)/3.0;
  }

double WilderATR(const MqlRates &rates[],const int count,const int period)
  {
   if(count<period+2) return 0.0;
   int oldest_tr=count-2;
   int seed_end=oldest_tr-(period-1);
   if(seed_end<0) return 0.0;
   double atr=0.0;
   for(int i=oldest_tr;i>=seed_end;i--)
     {
      double previous_close=rates[i+1].close;
      double tr=MathMax(rates[i].high-rates[i].low,
                        MathMax(MathAbs(rates[i].high-previous_close),MathAbs(rates[i].low-previous_close)));
      atr+=tr;
     }
   atr/=period;
   for(int i=seed_end-1;i>=0;i--)
     {
      double previous_close=rates[i+1].close;
      double tr=MathMax(rates[i].high-rates[i].low,
                        MathMax(MathAbs(rates[i].high-previous_close),MathAbs(rates[i].low-previous_close)));
      atr=(atr*(period-1)+tr)/period;
     }
   return atr;
  }

double WilderATRShift(const MqlRates &rates[],const int count,const int period,const int shift)
  {
   if(shift<0 || count<period+2+shift) return 0.0;
   int oldest_tr=count-2;
   int seed_end=oldest_tr-(period-1);
   if(seed_end<shift) return 0.0;

   double atr=0.0;
   for(int i=oldest_tr;i>=seed_end;i--)
     {
      double previous_close=rates[i+1].close;
      double tr=MathMax(rates[i].high-rates[i].low,
                        MathMax(MathAbs(rates[i].high-previous_close),MathAbs(rates[i].low-previous_close)));
      atr+=tr;
     }
   atr/=period;

   for(int i=seed_end-1;i>=shift;i--)
     {
      double previous_close=rates[i+1].close;
      double tr=MathMax(rates[i].high-rates[i].low,
                        MathMax(MathAbs(rates[i].high-previous_close),MathAbs(rates[i].low-previous_close)));
      atr=(atr*(period-1)+tr)/period;
     }
   return atr;
  }

bool RefreshClosedBarData()
  {
   double ema50_previous=0.0;
   double ema200_previous=0.0;
   if(!CopyLatestTwo(hEMA50,0,g_ema50,ema50_previous)) return false;
   if(!CopyLatestTwo(hEMA200,0,g_ema200,ema200_previous)) return false;
   double point=PointSize();
   if(point<=0.0) return false;
   g_ema50SlopePoints=(g_ema50-ema50_previous)/point;
   g_ema200SlopePoints=(g_ema200-ema200_previous)/point;

   double ignored=0.0;
   if(!CopyLatestTwo(hADX10,MAIN_LINE,g_adx10,ignored)) return false;
   if(!CopyLatestTwo(hADX10,PLUSDI_LINE,g_plusDI10,ignored)) return false;
   if(!CopyLatestTwo(hADX10,MINUSDI_LINE,g_minusDI10,ignored)) return false;
   if(!CopyLatestTwo(hADX14,MAIN_LINE,g_adx14,ignored)) return false;
   if(!CopyLatestTwo(hADX14,PLUSDI_LINE,g_plusDI14,ignored)) return false;
   if(!CopyLatestTwo(hADX14,MINUSDI_LINE,g_minusDI14,ignored)) return false;

   int required=MathMax(StructureLookbackBars+SwingLeftBars+SwingRightBars+10,
                        CRSI_RANK_PERIOD+MathMax(CRSI_PRICE_PERIOD,CRSI_STREAK_PERIOD)+80);
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   int copied=CopyRates(_Symbol,PERIOD_M1,1,required,rates);
   if(copied<CRSI_RANK_PERIOD+30 || copied<ATRPeriod+2) return false;

   double closes[];
   ArrayResize(closes,copied);
   for(int i=0;i<copied;i++) closes[i]=rates[i].close;
   double streaks[];
   BuildStreakSeries(closes,copied,streaks);
   g_crsi=ConnorsRSIAt(closes,copied,streaks,0);
   g_crsiPrevious=ConnorsRSIAt(closes,copied,streaks,1);
   if(copied>ROC_PERIOD+1 && closes[ROC_PERIOD]!=0.0 && closes[ROC_PERIOD+1]!=0.0)
     {
      g_roc9=100.0*(closes[0]-closes[ROC_PERIOD])/closes[ROC_PERIOD];
      g_roc9Previous=100.0*(closes[1]-closes[ROC_PERIOD+1])/closes[ROC_PERIOD+1];
     }
   else
     {
      g_roc9=0.0;
      g_roc9Previous=0.0;
     }
   g_atrPrevious=WilderATRShift(rates,copied,ATRPeriod,1);
   g_atr=WilderATR(rates,copied,ATRPeriod);
   if(g_atr<=0.0 || !MathIsValidNumber(g_atr)) return false;
   if(g_atrPrevious<=0.0 || !MathIsValidNumber(g_atrPrevious))
      g_atrPrevious=g_atr;

   g_lastClosedOpen=rates[0].open;
   g_lastClosedHigh=rates[0].high;
   g_lastClosedLow=rates[0].low;
   g_lastClosedClose=rates[0].close;
   g_lastClosedRange=rates[0].high-rates[0].low;
   g_volatilitySpike=g_lastClosedRange>g_atr*2.50;
   return true;
  }

//====================================================================
// NON-REPAINTING MARKET STRUCTURE, BOS, CHOCH, SWEEP, FIBONACCI, FVG
//====================================================================
void ClearSwing(SwingPoint &point)
  {
   point.price=0.0;
   point.time=0;
   point.shift=-1;
  }

bool FindConfirmedSwings(const MqlRates &rates[],const int count,
                         SwingPoint &latest_high,SwingPoint &previous_high,
                         SwingPoint &latest_low,SwingPoint &previous_low)
  {
   ClearSwing(latest_high);
   ClearSwing(previous_high);
   ClearSwing(latest_low);
   ClearSwing(previous_low);
   int newest_confirmable=SwingRightBars;
   int oldest_allowed=count-1-SwingLeftBars;
   for(int i=newest_confirmable;i<=oldest_allowed;i++)
     {
      bool is_high=true;
      bool is_low=true;
      for(int j=1;j<=SwingRightBars;j++)
        {
         if(rates[i].high<=rates[i-j].high) is_high=false;
         if(rates[i].low>=rates[i-j].low) is_low=false;
        }
      for(int j=1;j<=SwingLeftBars;j++)
        {
         if(rates[i].high<rates[i+j].high) is_high=false;
         if(rates[i].low>rates[i+j].low) is_low=false;
        }
      if(is_high)
        {
         if(latest_high.time==0)
           {
            latest_high.price=rates[i].high;
            latest_high.time=rates[i].time;
            latest_high.shift=i+1;
           }
         else if(previous_high.time==0)
           {
            previous_high.price=rates[i].high;
            previous_high.time=rates[i].time;
            previous_high.shift=i+1;
           }
        }
      if(is_low)
        {
         if(latest_low.time==0)
           {
            latest_low.price=rates[i].low;
            latest_low.time=rates[i].time;
            latest_low.shift=i+1;
           }
         else if(previous_low.time==0)
           {
            previous_low.price=rates[i].low;
            previous_low.time=rates[i].time;
            previous_low.shift=i+1;
           }
        }
      if(latest_high.time>0 && previous_high.time>0 && latest_low.time>0 && previous_low.time>0)
         break;
     }
   return latest_high.time>0 && latest_low.time>0;
  }

bool EventFresh(const datetime event_time,const int max_age_bars)
  {
   if(event_time<=0) return false;
   int shift=iBarShift(_Symbol,PERIOD_M1,event_time,false);
   return shift>=1 && shift<=MathMax(1,max_age_bars);
  }

void RefreshFVG(const MqlRates &rates[],const int count)
  {
   g_bullFVGActive=false;
   g_bearFVGActive=false;
   g_bullFVGLow=0.0;
   g_bullFVGHigh=0.0;
   g_bearFVGLow=0.0;
   g_bearFVGHigh=0.0;
   g_bullFVGTime=0;
   g_bearFVGTime=0;
   double minimum_gap=MathMax(g_atr*MathMax(0.0,MinFVGSizeATR),TickSize()*2.0);
   int limit=MathMin(FVGSearchBars,count-2);
   for(int i=0;i<limit;i++)
     {
      if(!g_bullFVGActive && rates[i+2].high<rates[i].low)
        {
         double lower=rates[i+2].high;
         double upper=rates[i].low;
         bool filled=false;
         for(int j=0;j<i;j++)
            if(rates[j].low<=lower) { filled=true; break; }
         if(!filled && upper-lower>=minimum_gap)
           {
            g_bullFVGActive=true;
            g_bullFVGLow=lower;
            g_bullFVGHigh=upper;
            g_bullFVGTime=rates[i].time;
           }
        }
      if(!g_bearFVGActive && rates[i+2].low>rates[i].high)
        {
         double lower=rates[i].high;
         double upper=rates[i+2].low;
         bool filled=false;
         for(int j=0;j<i;j++)
            if(rates[j].high>=upper) { filled=true; break; }
         if(!filled && upper-lower>=minimum_gap)
           {
            g_bearFVGActive=true;
            g_bearFVGLow=lower;
            g_bearFVGHigh=upper;
            g_bearFVGTime=rates[i].time;
           }
        }
      if(g_bullFVGActive && g_bearFVGActive) break;
     }
  }

bool RefreshMarketStructure()
  {
   int required=StructureLookbackBars+SwingLeftBars+SwingRightBars+10;
   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   int copied=CopyRates(_Symbol,PERIOD_M1,1,required,rates);
   if(copied<SwingLeftBars+SwingRightBars+10) return false;

   ENUM_STRUCTURE prior_structure=g_structure;
   SwingPoint latest_high,previous_high,latest_low,previous_low;
   if(!FindConfirmedSwings(rates,copied,latest_high,previous_high,latest_low,previous_low))
     {
      g_structure=STRUCTURE_UNKNOWN;
      RefreshFVG(rates,copied);
      return true;
     }
   g_swingHigh=latest_high;
   g_previousSwingHigh=previous_high;
   g_swingLow=latest_low;
   g_previousSwingLow=previous_low;
   g_hh=previous_high.time>0 && latest_high.price>previous_high.price;
   g_lh=previous_high.time>0 && latest_high.price<previous_high.price;
   g_hl=previous_low.time>0 && latest_low.price>previous_low.price;
   g_ll=previous_low.time>0 && latest_low.price<previous_low.price;
   if(g_hh && g_hl) g_structure=STRUCTURE_BULLISH;
   else if(g_lh && g_ll) g_structure=STRUCTURE_BEARISH;
   else if(previous_high.time>0 && previous_low.time>0) g_structure=STRUCTURE_RANGE;
   else g_structure=STRUCTURE_UNKNOWN;

   double displacement=MathMax(0.0,BOSMinDisplacementATR)*g_atr;
   bool break_up=rates[0].close>latest_high.price+displacement && rates[1].close<=latest_high.price+displacement;
   bool break_down=rates[0].close<latest_low.price-displacement && rates[1].close>=latest_low.price-displacement;

   if(break_up)
     {
      // Any confirmed upward structural break invalidates the older
      // bearish impulse map before a new continuation map is accepted.
      g_bearFibValid=false;
      if(prior_structure==STRUCTURE_BEARISH)
        {
         g_bullCHoCHTime=rates[0].time;
         g_bullBOSLevel=latest_high.price;
        }
      else
        {
         g_bullBOSTime=rates[0].time;
         g_bullBOSLevel=latest_high.price;
         if(latest_low.price>0.0 && latest_low.price<rates[0].high)
           {
            g_bullFibValid=true;
            g_bullFibOrigin=latest_low.price;
            g_bullFibExtreme=MathMax(rates[0].high,latest_high.price);
            g_bullFibTime=rates[0].time;
           }
        }
     }
   if(break_down)
     {
      // Mirror invalidation: a confirmed downward break retires the
      // older bullish impulse map.
      g_bullFibValid=false;
      if(prior_structure==STRUCTURE_BULLISH)
        {
         g_bearCHoCHTime=rates[0].time;
         g_bearBOSLevel=latest_low.price;
        }
      else
        {
         g_bearBOSTime=rates[0].time;
         g_bearBOSLevel=latest_low.price;
         if(latest_high.price>rates[0].low)
           {
            g_bearFibValid=true;
            g_bearFibOrigin=latest_high.price;
            g_bearFibExtreme=MathMin(rates[0].low,latest_low.price);
            g_bearFibTime=rates[0].time;
           }
        }
     }

   double penetration=MathMax(g_atr*MathMax(0.0,SweepMinPenetrationATR),TickSize()*2.0);
   if(latest_low.price>0.0 && rates[0].low<latest_low.price-penetration && rates[0].close>latest_low.price)
     {
      g_bullSweepTime=rates[0].time;
      g_bullSweepExtreme=rates[0].low;
     }
   if(latest_high.price>0.0 && rates[0].high>latest_high.price+penetration && rates[0].close<latest_high.price)
     {
      g_bearSweepTime=rates[0].time;
      g_bearSweepExtreme=rates[0].high;
     }

   if(g_bullFibValid && rates[0].close<g_bullFibOrigin-displacement)
      g_bullFibValid=false;
   if(g_bearFibValid && rates[0].close>g_bearFibOrigin+displacement)
      g_bearFibValid=false;

   RefreshFVG(rates,copied);
   return true;
  }

double FibLevel(const ENUM_SIDE side,const double ratio)
  {
   if(side==SIDE_BUY && g_bullFibValid)
      return g_bullFibExtreme-(g_bullFibExtreme-g_bullFibOrigin)*ratio;
   if(side==SIDE_SELL && g_bearFibValid)
      return g_bearFibExtreme+(g_bearFibOrigin-g_bearFibExtreme)*ratio;
   return 0.0;
  }

double FibExtension(const ENUM_SIDE side,const double ratio)
  {
   if(side==SIDE_BUY && g_bullFibValid)
      return g_bullFibExtreme+(g_bullFibExtreme-g_bullFibOrigin)*(ratio-1.0);
   if(side==SIDE_SELL && g_bearFibValid)
      return g_bearFibExtreme-(g_bearFibOrigin-g_bearFibExtreme)*(ratio-1.0);
   return 0.0;
  }

string FibLevelsText(const ENUM_SIDE side)
  {
   return StringFormat("23.6=%.5f,38.2=%.5f,50=%.5f,61.8=%.5f,78.6=%.5f,127.2=%.5f,161.8=%.5f",
                       FibLevel(side,0.236),FibLevel(side,0.382),FibLevel(side,0.500),
                       FibLevel(side,0.618),FibLevel(side,0.786),
                       FibExtension(side,1.272),FibExtension(side,1.618));
  }

bool InFibZone(const ENUM_SIDE side,const bool main_only)
  {
   MqlTick tick;
   if(!SymbolInfoTick(_Symbol,tick)) return false;
   double price=side==SIDE_BUY ? tick.ask : tick.bid;
   double r1=main_only ? 0.50 : 0.382;
   double r2=0.618;
   double first=FibLevel(side,r1);
   double second=FibLevel(side,r2);
   if(first<=0.0 || second<=0.0) return false;
   return price>=MathMin(first,second) && price<=MathMax(first,second);
  }

string FibZoneText(const ENUM_SIDE side)
  {
   if(InFibZone(side,true)) return "50-61.8";
   if(InFibZone(side,false)) return "38.2-61.8";
   return "OUTSIDE";
  }

bool InFVG(const ENUM_SIDE side)
  {
   MqlTick tick;
   if(!SymbolInfoTick(_Symbol,tick)) return false;
   double price=side==SIDE_BUY ? tick.ask : tick.bid;
   double tolerance=g_atr*MathMax(0.0,FVGToleranceATR);
   if(side==SIDE_BUY)
      return g_bullFVGActive && price>=g_bullFVGLow-tolerance && price<=g_bullFVGHigh+tolerance;
   return g_bearFVGActive && price>=g_bearFVGLow-tolerance && price<=g_bearFVGHigh+tolerance;
  }

bool NearSupportResistance(const ENUM_SIDE side)
  {
   MqlTick tick;
   if(!SymbolInfoTick(_Symbol,tick) || g_atr<=0.0) return false;
   double price=side==SIDE_BUY ? tick.ask : tick.bid;
   double level=side==SIDE_BUY ? g_swingLow.price : g_swingHigh.price;
   return level>0.0 && MathAbs(price-level)<=g_atr*MathMax(0.0,SRNearATR);
  }

//====================================================================
// LIVE FASTFLOW AND DYNAMIC SPREAD
//====================================================================
void UpdateFastFlow(const MqlTick &tick)
  {
   int size=ArraySize(g_ticks);
   ArrayResize(g_ticks,size+1);
   g_ticks[size].time_msc=tick.time_msc;
   g_ticks[size].bid=tick.bid;
   g_ticks[size].ask=tick.ask;

   long cutoff=tick.time_msc-(long)MathMax(1,TickWindowSeconds)*1000;
   int remove_count=0;
   int total_size=ArraySize(g_ticks);
   while(remove_count<total_size && g_ticks[remove_count].time_msc<cutoff) remove_count++;
   if(remove_count>0) ArrayRemove(g_ticks,0,remove_count);
   if(ArraySize(g_ticks)>MathMax(32,MaxFlowSamples))
      ArrayRemove(g_ticks,0,ArraySize(g_ticks)-MathMax(32,MaxFlowSamples));

   int count=ArraySize(g_ticks);
   g_totalTicks=count;
   g_upTicks=0;
   g_downTicks=0;
   g_neutralTicks=0;
   for(int i=1;i<count;i++)
     {
      if(g_ticks[i].bid>g_ticks[i-1].bid)
        {
         g_upTicks++;
         g_lastBuyFlowMsc=g_ticks[i].time_msc;
        }
      else if(g_ticks[i].bid<g_ticks[i-1].bid)
        {
         g_downTicks++;
         g_lastSellFlowMsc=g_ticks[i].time_msc;
        }
      else g_neutralTicks++;
     }

   int directional=g_upTicks+g_downTicks;
   if(directional>0)
     {
      g_buyTickRatio=(double)g_upTicks/(double)directional;
      g_sellTickRatio=(double)g_downTicks/(double)directional;
     }
   else
     {
      g_buyTickRatio=0.0;
      g_sellTickRatio=0.0;
     }

   double point=PointSize();
   double duration=0.0;
   if(count>=2)
      duration=(double)(g_ticks[count-1].time_msc-g_ticks[0].time_msc)/1000.0;
   g_ticksPerSecond=duration>0.0 ? (double)(count-1)/duration : 0.0;
   if(count>=2 && duration>0.0 && point>0.0)
     {
      g_bidVelocityPoints=(g_ticks[count-1].bid-g_ticks[0].bid)/point/duration;
      g_askVelocityPoints=(g_ticks[count-1].ask-g_ticks[0].ask)/point/duration;
      g_displacementPoints=(g_ticks[count-1].bid-g_ticks[0].bid)/point;
     }
   else
     {
      g_bidVelocityPoints=0.0;
      g_askVelocityPoints=0.0;
      g_displacementPoints=0.0;
     }

   g_accelerationPoints=0.0;
   if(count>=4 && point>0.0)
     {
      int middle=count/2;
      double first_dt=(double)(g_ticks[middle].time_msc-g_ticks[0].time_msc)/1000.0;
      double second_dt=(double)(g_ticks[count-1].time_msc-g_ticks[middle].time_msc)/1000.0;
      if(first_dt>0.0 && second_dt>0.0)
        {
         double first_velocity=(g_ticks[middle].bid-g_ticks[0].bid)/point/first_dt;
         double second_velocity=(g_ticks[count-1].bid-g_ticks[middle].bid)/point/second_dt;
         g_accelerationPoints=(second_velocity-first_velocity)/MathMax(0.001,(first_dt+second_dt)*0.5);
        }
     }

   bool strong_buy=directional>=StrongFlowMinTicks &&
                   g_upTicks>=StrongFlowMinTicks &&
                   g_buyTickRatio>=StrongTickRatio &&
                   g_bidVelocityPoints>=StrongVelocityPointsPerSec;
   bool strong_sell=directional>=StrongFlowMinTicks &&
                    g_downTicks>=StrongFlowMinTicks &&
                    g_sellTickRatio>=StrongTickRatio &&
                    g_bidVelocityPoints<=-StrongVelocityPointsPerSec;
   if(strong_buy) g_flow=FLOW_STRONG_BUY;
   else if(strong_sell) g_flow=FLOW_STRONG_SELL;
   else if(directional>=MinDirectionalTicks && g_upTicks>=MinDirectionalTicks &&
           g_buyTickRatio>=MinTickRatio && g_bidVelocityPoints>=0.0) g_flow=FLOW_BUY;
   else if(directional>=MinDirectionalTicks && g_downTicks>=MinDirectionalTicks &&
           g_sellTickRatio>=MinTickRatio && g_bidVelocityPoints<=0.0) g_flow=FLOW_SELL;
   else g_flow=FLOW_NEUTRAL;
  }

void UpdateSpreadState(const MqlTick &tick)
  {
   double point=PointSize();
   if(point<=0.0) return;
   g_currentSpreadPoints=(tick.ask-tick.bid)/point;
   double previous_baseline=g_spreadBaselinePoints;
   if(previous_baseline<=0.0) previous_baseline=g_currentSpreadPoints;
   double spread_price=tick.ask-tick.bid;
   bool ewma_shock=previous_baseline>0.0 && g_currentSpreadPoints>previous_baseline*SpreadShockRatio;
   bool atr_shock=g_atr>0.0 && spread_price/g_atr>MaxSpreadATRRatio;
   g_spreadShock=ewma_shock || atr_shock;

   double alpha=2.0/((double)MathMax(2,SpreadEWMASpan)+1.0);
   double capped_sample=g_currentSpreadPoints;
   if(g_spreadShock && previous_baseline>0.0)
      capped_sample=MathMin(capped_sample,previous_baseline*SpreadShockRatio);
   g_spreadBaselinePoints=previous_baseline+alpha*(capped_sample-previous_baseline);

   long now_msc=(long)TimeCurrent()*1000;
   long age_msc=now_msc-tick.time_msc;
   if(age_msc<0) age_msc=0;
   g_tickAgeSeconds=(double)age_msc/1000.0;
  }


void UpdateNextCandlePredictor()
  {
   g_nextBuyScore=0;
   g_nextSellScore=0;
   g_nextCandleBias="NEUTRAL";
   if(!NextCandlePredictorEnabled || g_lastClosedRange<=0.0) return;

   double range=MathMax(g_lastClosedRange,PointSize());
   double body=MathAbs(g_lastClosedClose-g_lastClosedOpen);
   double upper=g_lastClosedHigh-MathMax(g_lastClosedOpen,g_lastClosedClose);
   double lower=MathMin(g_lastClosedOpen,g_lastClosedClose)-g_lastClosedLow;
   g_prevBodyRatio=body/range;
   g_prevUpperWickRatio=MathMax(0.0,upper/range);
   g_prevLowerWickRatio=MathMax(0.0,lower/range);

   bool prev_bear=(g_lastClosedClose<g_lastClosedOpen);
   bool prev_bull=(g_lastClosedClose>g_lastClosedOpen);

   // 1) Previous candle anatomy / rejection (max ~30 points per side).
   // A red candle with meaningful lower rejection can rebound BUY;
   // a blue candle with meaningful upper rejection can rebound SELL.
   if(g_prevLowerWickRatio>=PredictorMinWickRatio)
     {
      g_nextBuyScore+=20;
      if(prev_bear) g_nextBuyScore+=10;
     }
   if(g_prevUpperWickRatio>=PredictorMinWickRatio)
     {
      g_nextSellScore+=20;
      if(prev_bull) g_nextSellScore+=10;
     }

   // Strong close at an extreme favors continuation.
   double close_pos=(g_lastClosedClose-g_lastClosedLow)/range; // 0=low, 1=high
   if(prev_bull && close_pos>=0.75) g_nextBuyScore+=10;
   if(prev_bear && close_pos<=0.25) g_nextSellScore+=10;

   // 2) First ticks of the newly opened candle (max ~45).
   int elapsed=(int)MathMax(0,(long)TimeCurrent()-(long)g_currentBarTime);
   if(elapsed<=MathMax(1,PredictorEarlySeconds))
     {
      if(g_buyTickRatio>=PredictorStrongTickRatio) g_nextBuyScore+=15;
      if(g_sellTickRatio>=PredictorStrongTickRatio) g_nextSellScore+=15;

      if(g_bidVelocityPoints>=PredictorVelocityPoints) g_nextBuyScore+=15;
      if(g_bidVelocityPoints<=-PredictorVelocityPoints) g_nextSellScore+=15;

      if(g_flow==FLOW_STRONG_BUY) g_nextBuyScore+=15;
      else if(g_flow==FLOW_BUY) g_nextBuyScore+=10;
      if(g_flow==FLOW_STRONG_SELL) g_nextSellScore+=15;
      else if(g_flow==FLOW_SELL) g_nextSellScore+=10;
     }
   else
     {
      // After the early window, flow is still useful but gets less weight.
      if(g_flow==FLOW_STRONG_BUY) g_nextBuyScore+=10;
      else if(g_flow==FLOW_BUY) g_nextBuyScore+=6;
      if(g_flow==FLOW_STRONG_SELL) g_nextSellScore+=10;
      else if(g_flow==FLOW_SELL) g_nextSellScore+=6;
     }

   // 3) Momentum and DMI confirmation (max ~25).
   if(g_roc9>0.0) g_nextBuyScore+=8;
   if(g_roc9<0.0) g_nextSellScore+=8;
   if(g_roc9>g_roc9Previous) g_nextBuyScore+=5;
   if(g_roc9<g_roc9Previous) g_nextSellScore+=5;

   if(g_plusDI10>g_minusDI10) g_nextBuyScore+=12;
   if(g_minusDI10>g_plusDI10) g_nextSellScore+=12;

   g_nextBuyScore=MathMin(100,g_nextBuyScore);
   g_nextSellScore=MathMin(100,g_nextSellScore);

   int diff=g_nextBuyScore-g_nextSellScore;
   if(diff>=15) g_nextCandleBias="BUY";
   else if(diff<=-15) g_nextCandleBias="SELL";
   else g_nextCandleBias="NEUTRAL";
  }

string NextCandlePredictorTag()
  {
   return StringFormat("PRED=%s,PB=%d,PS=%d,Body=%.2f,UW=%.2f,LW=%.2f",
                       g_nextCandleBias,g_nextBuyScore,g_nextSellScore,
                       g_prevBodyRatio,g_prevUpperWickRatio,g_prevLowerWickRatio);
  }

// v4.97 SMART FIRST-MOVE: five independent evidence blocks.
// It separates "direction is strong" from "this exact second is safe to enter".
int SmartFirstMoveScore(const ENUM_SIDE side,string &detail)
  {
   detail="";
   if(!SmartFirstMoveEnabled) return 100;

   double point=PointSize();
   if(point<=0.0 || g_lastClosedRange<=0.0) return 0;

   MqlTick tick;
   if(!SymbolInfoTick(_Symbol,tick)) return 0;
   double mid=(tick.bid+tick.ask)*0.5;
   double range=MathMax(g_lastClosedRange,point);
   bool buy=(side==SIDE_BUY);
   int anatomy=0,ticks=0,flow=0,liq=0,momentum=0;

   // 1) Previous-candle anatomy: continuation body/close + rejection wick. max 20.
   bool prev_same=buy ? (g_lastClosedClose>g_lastClosedOpen) : (g_lastClosedClose<g_lastClosedOpen);
   double close_pos=(g_lastClosedClose-g_lastClosedLow)/range;
   if(prev_same && g_prevBodyRatio>=0.55) anatomy+=10;
   else if(prev_same && g_prevBodyRatio>=0.35) anatomy+=6;
   if(buy && close_pos>=0.75) anatomy+=6;
   if(!buy && close_pos<=0.25) anatomy+=6;
   if(buy && g_prevLowerWickRatio>=PredictorMinWickRatio) anatomy+=4;
   if(!buy && g_prevUpperWickRatio>=PredictorMinWickRatio) anatomy+=4;
   anatomy=MathMin(20,anatomy);

   // 2) First 1-3 seconds: tick ratio + signed velocity + acceleration. max 25.
   double ratio=buy ? g_buyTickRatio : g_sellTickRatio;
   double signed_vel=buy ? g_bidVelocityPoints : -g_bidVelocityPoints;
   double signed_acc=buy ? g_accelerationPoints : -g_accelerationPoints;
   if(ratio>=SmartTickRatioStrong) ticks+=15;
   else if(ratio>=SmartTickRatioGood) ticks+=10;
   if(signed_vel>=SmartVelocityGood) ticks+=7;
   if(signed_acc>0.0) ticks+=3;
   ticks=MathMin(25,ticks);

   // 3) FastFlow itself. max 20.
   if(buy && g_flow==FLOW_STRONG_BUY) flow=20;
   else if(!buy && g_flow==FLOW_STRONG_SELL) flow=20;
   else if(buy && g_flow==FLOW_BUY) flow=12;
   else if(!buy && g_flow==FLOW_SELL) flow=12;

   // 4) Previous high/low + liquidity/BOS context. max 15.
   double boundary=buy ? g_lastClosedHigh : g_lastClosedLow;
   double dist_points=MathAbs(mid-boundary)/point;
   bool directional_break=buy ? (mid>g_lastClosedHigh) : (mid<g_lastClosedLow);
   bool sweep=buy ? EventFresh(g_bullSweepTime,SweepMaxAgeBars) : EventFresh(g_bearSweepTime,SweepMaxAgeBars);
   bool bos=buy ? EventFresh(g_bullBOSTime,StructureEventMaxAgeBars) : EventFresh(g_bearBOSTime,StructureEventMaxAgeBars);
   if(directional_break) liq+=6;
   else if(dist_points<=SmartLiquidityNearPoints) liq+=3;
   if(sweep) liq+=5;
   if(bos) liq+=4;
   liq=MathMin(15,liq);

   // 5) ROC9 + DMI10 + structure alignment. max 20.
   bool roc_side=buy ? (g_roc9>0.0) : (g_roc9<0.0);
   bool roc_improve=buy ? (g_roc9>g_roc9Previous) : (g_roc9<g_roc9Previous);
   bool dmi_side=buy ? (g_plusDI10>g_minusDI10) : (g_minusDI10>g_plusDI10);
   bool struct_side=buy ? (g_structure==STRUCTURE_BULLISH) : (g_structure==STRUCTURE_BEARISH);
   if(roc_side) momentum+=5;
   if(roc_improve) momentum+=3;
   if(dmi_side) momentum+=6;
   if(struct_side) momentum+=6;
   momentum=MathMin(20,momentum);

   int score=MathMin(100,anatomy+ticks+flow+liq+momentum);
   detail=StringFormat("SFM=%d[A=%d,T=%d,F=%d,L=%d,M=%d,R=%.2f,V=%.1f,Acc=%.1f]",
                       score,anatomy,ticks,flow,liq,momentum,ratio,signed_vel,signed_acc);
   return score;
  }

bool SmartFirstMoveAllows(const ENUM_SIDE side,string &reason,string &tag)
  {
   reason=""; tag="SFM=OFF";
   if(!SmartFirstMoveEnabled) return true;

   string side_detail="",opp_detail="";
   int side_score=SmartFirstMoveScore(side,side_detail);
   ENUM_SIDE opp=(side==SIDE_BUY ? SIDE_SELL : SIDE_BUY);
   int opp_score=SmartFirstMoveScore(opp,opp_detail);
   tag=side_detail+StringFormat(",OPP=%d",opp_score);

   int elapsed=(int)MathMax(0,(long)TimeCurrent()-(long)g_currentBarTime);
   if(elapsed<MathMax(1,SmartObserveMinSeconds))
     {
      reason=StringFormat("collect first ticks %ds/%ds",elapsed,SmartObserveMinSeconds);
      return false;
     }

   bool strong_opp=(opp_score>=SmartMinAlignedScore && opp_score>=side_score+SmartMinScoreAdvantage);
   if(SmartBlockStrongOpposite && strong_opp)
     {
      reason=StringFormat("strong opposite first-move side=%d opp=%d",side_score,opp_score);
      return false;
     }

   if(elapsed<=MathMax(SmartObserveMinSeconds,SmartObserveMaxSeconds))
     {
      bool buy=(side==SIDE_BUY);
      double ratio=buy ? g_buyTickRatio : g_sellTickRatio;
      double signed_vel=buy ? g_bidVelocityPoints : -g_bidVelocityPoints;
      bool flow_side=buy ? (g_flow==FLOW_BUY || g_flow==FLOW_STRONG_BUY)
                         : (g_flow==FLOW_SELL || g_flow==FLOW_STRONG_SELL);
      bool dmi_side=buy ? (g_plusDI10>g_minusDI10) : (g_minusDI10>g_plusDI10);
      bool roc_side=buy ? (g_roc9>0.0) : (g_roc9<0.0);
      bool struct_side=buy ? (g_structure==STRUCTURE_BULLISH) : (g_structure==STRUCTURE_BEARISH);
      bool live_ok=(ratio>=SmartTickRatioGood && signed_vel>=SmartVelocityGood*0.60 && flow_side);
      bool context_ok=(dmi_side && (roc_side || struct_side));

      if(side_score>=SmartMinAlignedScore &&
         side_score>=opp_score+SmartMinScoreAdvantage &&
         live_ok && context_ok)
         return true;
      reason=StringFormat("V2 first-move wait %ds side=%d opp=%d ratio=%.2f vel=%.1f flow=%d ctx=%d",
                          elapsed,side_score,opp_score,ratio,signed_vel,(int)flow_side,(int)context_ok);
      return false;
     }

   // SMART V2: never fall back to a weak NORMAL entry after the opening window.
   // Direction strength alone is not enough: live flow, tick ratio, velocity and
   // DMI + (ROC or structure) must still confirm the intended side.
   bool buy=(side==SIDE_BUY);
   double ratio=buy ? g_buyTickRatio : g_sellTickRatio;
   double signed_vel=buy ? g_bidVelocityPoints : -g_bidVelocityPoints;
   bool flow_side=buy ? (g_flow==FLOW_BUY || g_flow==FLOW_STRONG_BUY)
                      : (g_flow==FLOW_SELL || g_flow==FLOW_STRONG_SELL);
   bool dmi_side=buy ? (g_plusDI10>g_minusDI10) : (g_minusDI10>g_plusDI10);
   bool roc_side=buy ? (g_roc9>0.0) : (g_roc9<0.0);
   bool struct_side=buy ? (g_structure==STRUCTURE_BULLISH) : (g_structure==STRUCTURE_BEARISH);
   bool live_ok=(ratio>=SmartTickRatioGood && signed_vel>=SmartVelocityGood*0.60 && flow_side);
   bool context_ok=(dmi_side && (roc_side || struct_side));
   double signed_acc=buy ? g_accelerationPoints : -g_accelerationPoints;
   bool freshness_ok=(!SmartLateFreshnessGuard ||
                      (signed_vel>=SmartLateMinVelocityPoints && signed_acc>=SmartLateMinAccelPoints));

   if(side_score>=SmartLateMinScore &&
      side_score>=opp_score+SmartMinScoreAdvantage &&
      live_ok && context_ok && freshness_ok)
      return true;

   reason=StringFormat("V5.14 late-fresh wait side=%d opp=%d ratio=%.2f vel=%.1f acc=%.1f flow=%d dmi=%d roc=%d struct=%d fresh=%d",
                       side_score,opp_score,ratio,signed_vel,signed_acc,(int)flow_side,(int)dmi_side,(int)roc_side,(int)struct_side,(int)freshness_ok);
   return false;
  }


void UpdateSafeTelemetry(const MqlTick &tick,const bool new_bar)
  {
   double point=PointSize();
   if(point<=0.0) return;

   double px=(tick.bid+tick.ask)*0.5;
   if(new_bar || g_safeBarHigh<=0.0 || g_safeBarLow<=0.0)
     {
      g_safeBarHigh=px;
      g_safeBarLow=px;
      g_safeFalseBreakUp=false;
      g_safeFalseBreakDown=false;
      g_safeChaseBlockedBuy=false;
      g_safeChaseBlockedSell=false;
     }
   else
     {
      g_safeBarHigh=MathMax(g_safeBarHigh,px);
      g_safeBarLow=MathMin(g_safeBarLow,px);
     }

   int elapsed=(int)MathMax(0,(long)TimeCurrent()-(long)g_currentBarTime);

   // TELEMETRY ONLY: detect false breaks but do not block on them directly.
   if(SafeFalseBreakTelemetry && elapsed<=12 &&
      g_lastClosedHigh>0.0 && g_lastClosedLow>0.0)
     {
      bool pierced_up=((g_safeBarHigh-g_lastClosedHigh)/point)>=FalseBreakPiercePoints;
      bool pierced_dn=((g_lastClosedLow-g_safeBarLow)/point)>=FalseBreakPiercePoints;

      if(pierced_up && px<=g_lastClosedHigh-FalseBreakReturnPoints*point)
         g_safeFalseBreakUp=true;   // bearish false breakout
      if(pierced_dn && px>=g_lastClosedLow+FalseBreakReturnPoints*point)
         g_safeFalseBreakDown=true; // bullish false breakout
     }

   if(!SafeRegimeTelemetryEnabled)
     {
      g_safeRegime="OFF";
      return;
     }

   double ema_dist_atr=(g_atr>0.0 ? MathAbs(g_ema50-g_ema200)/g_atr : 0.0);
   double closed_range_atr=(g_atr>0.0 ? g_lastClosedRange/g_atr : 0.0);

   if(closed_range_atr>=TelemetrySpikeRangeATR)
      g_safeRegime="SPIKE";
   else if(g_safeFalseBreakUp || g_safeFalseBreakDown ||
           g_prevUpperWickRatio>=0.35 || g_prevLowerWickRatio>=0.35)
      g_safeRegime="REVERSAL";
   else if(g_adx10>=TelemetryTrendADX && ema_dist_atr>=TelemetryTrendEMADistATR)
      g_safeRegime="TREND";
   else
      g_safeRegime="RANGE";
  }

double CurrentLiveDisplacementATR()
  {
   if(g_atr<=0.0) return 0.0;
   return MathAbs(g_displacementPoints*PointSize())/g_atr;
  }

bool SafeTimingAllows(const ENUM_SIDE side,string &reason)
  {
   reason="";
   if(!SafeAdaptiveDelayEnabled) return true;

   int elapsed=(int)MathMax(0,(long)TimeCurrent()-(long)g_currentBarTime);
   bool strong_side=(side==SIDE_BUY ? g_flow==FLOW_STRONG_BUY : g_flow==FLOW_STRONG_SELL);
   int pred=(side==SIDE_BUY ? g_nextBuyScore : g_nextSellScore);
   int delay=(strong_side && pred>=75 ? SafeDelayFastSeconds : SafeDelayNormalSeconds);

   if(elapsed<delay)
     {
      reason=StringFormat("safe-delay %ds/%ds",elapsed,delay);
      return false;
     }

   double disp_atr=CurrentLiveDisplacementATR();
   bool blocked=(side==SIDE_BUY ? g_safeChaseBlockedBuy : g_safeChaseBlockedSell);

   // Do not chase a move that is already stretched. This is a timing guard,
   // not a direction filter. Once the live move cools/retraces, entry can resume.
   if(!blocked && disp_atr>=ChaseGuardMaxDispATR)
      blocked=true;

   if(blocked)
     {
      if(disp_atr<=ChaseGuardResumeDispATR)
         blocked=false;
      else
        {
         // v5.14 LOG FIX: never auto-release a stretched/chased entry merely because
         // a timer expired.  The same candle must actually cool/retrace first.
         // New-bar telemetry resets this state naturally.
         if(side==SIDE_BUY) g_safeChaseBlockedBuy=blocked;
         else               g_safeChaseBlockedSell=blocked;
         if(elapsed<ChaseGuardMaxWaitSeconds)
            reason=StringFormat("chase-wait dispATR=%.2f > resume=%.2f",disp_atr,ChaseGuardResumeDispATR);
         else
            reason=StringFormat("chase-stale block dispATR=%.2f > resume=%.2f; wait retrace/new bar",disp_atr,ChaseGuardResumeDispATR);
         return false;
        }
     }

   if(side==SIDE_BUY) g_safeChaseBlockedBuy=blocked;
   else               g_safeChaseBlockedSell=blocked;

   return true;
  }

string SafeTelemetryTag()
  {
   return StringFormat("SAFE_REG=%s,FBU=%d,FBD=%d,DispATR=%.2f",
                       g_safeRegime,(int)g_safeFalseBreakUp,(int)g_safeFalseBreakDown,
                       CurrentLiveDisplacementATR());
  }


void ResetExhaustionStateForNewBar()
  {
   if(g_exhaustArmBar==g_currentBarTime) return;
   g_exhaustArmBar=g_currentBarTime;
   g_exhaustBuyArmed=false;
   g_exhaustSellArmed=false;
   g_exhaustBuyRetraceSeen=false;
   g_exhaustSellRetraceSeen=false;
   g_exhaustBuyExtreme=0.0;
   g_exhaustSellExtreme=0.0;
   g_exhaustBuyArmTime=0;
   g_exhaustSellArmTime=0;
  }

bool ExhaustionRetraceAllows(const ENUM_SIDE side,string &reason)
  {
   reason="";
   if(!ExhaustionGuardEnabled) return true;

   ResetExhaustionStateForNewBar();

   MqlTick tick;
   if(!SymbolInfoTick(_Symbol,tick))
     {
      reason="exhaustion tick unavailable";
      return false;
     }

   double point=PointSize();
   if(point<=0.0) return true;

   double disp_atr=CurrentLiveDisplacementATR();
   double side_ratio=(side==SIDE_BUY ? g_buyTickRatio : g_sellTickRatio);
   double side_velocity=(side==SIDE_BUY ? g_bidVelocityPoints : -g_bidVelocityPoints);
   bool strong_flow=(side==SIDE_BUY ? g_flow==FLOW_STRONG_BUY : g_flow==FLOW_STRONG_SELL);

   bool burst=((side_ratio>=ExhaustionTickRatio &&
                side_velocity>=ExhaustionVelocityPoints &&
                disp_atr>=ExhaustionDispATR) ||
               (strong_flow && disp_atr>=ExhaustionStrongDispATR));

   bool armed=(side==SIDE_BUY ? g_exhaustBuyArmed : g_exhaustSellArmed);
   if(!armed && burst)
     {
      if(side==SIDE_BUY)
        {
         g_exhaustBuyArmed=true;
         g_exhaustBuyExtreme=tick.ask;
         g_exhaustBuyArmTime=TimeCurrent();
         g_exhaustBuyRetraceSeen=false;
        }
      else
        {
         g_exhaustSellArmed=true;
         g_exhaustSellExtreme=tick.bid;
         g_exhaustSellArmTime=TimeCurrent();
         g_exhaustSellRetraceSeen=false;
        }

      if(DebugAuditLog)
         PrintFormat("AUDIT|%s|EXHAUSTION_ARMED|side=%s|DispATR=%.2f|ratio=%.2f|velocity=%.1f|flow=%s|price=%.5f",
                     TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),SideText(side),disp_atr,
                     side_ratio,side_velocity,FlowText(g_flow),
                     side==SIDE_BUY ? tick.ask : tick.bid);
     }

   armed=(side==SIDE_BUY ? g_exhaustBuyArmed : g_exhaustSellArmed);
   if(!armed) return true;

   if(side==SIDE_BUY)
      g_exhaustBuyExtreme=MathMax(g_exhaustBuyExtreme,tick.ask);
   else
      g_exhaustSellExtreme=(g_exhaustSellExtreme<=0.0 ? tick.bid : MathMin(g_exhaustSellExtreme,tick.bid));

   datetime arm_time=(side==SIDE_BUY ? g_exhaustBuyArmTime : g_exhaustSellArmTime);
   int armed_seconds=(int)MathMax(0,(long)(TimeCurrent()-arm_time));
   if(armed_seconds>ExhaustionMaxArmSeconds)
     {
      if(side==SIDE_BUY)
        {
         g_exhaustBuyArmed=false;
         g_exhaustBuyRetraceSeen=false;
        }
      else
        {
         g_exhaustSellArmed=false;
         g_exhaustSellRetraceSeen=false;
        }
      reason=StringFormat("exhaustion expired %ds; wait next setup",armed_seconds);
      return false;
     }

   double retrace_points=0.0;
   if(side==SIDE_BUY)
      retrace_points=(g_exhaustBuyExtreme-tick.ask)/point;
   else
      retrace_points=(tick.bid-g_exhaustSellExtreme)/point;

   if(side==SIDE_BUY && retrace_points>=ExhaustionRetracePoints)
      g_exhaustBuyRetraceSeen=true;
   if(side==SIDE_SELL && retrace_points>=ExhaustionRetracePoints)
      g_exhaustSellRetraceSeen=true;

   bool retrace_seen=(side==SIDE_BUY ? g_exhaustBuyRetraceSeen : g_exhaustSellRetraceSeen);
   if(!retrace_seen)
     {
      reason=StringFormat("exhaustion wait retrace %.0f/%.0f pts DispATR=%.2f",
                          retrace_points,ExhaustionRetracePoints,disp_atr);
      return false;
     }

   // After a retrace, enter only when the intended direction starts to re-accelerate.
   double resume_ratio=(side==SIDE_BUY ? g_buyTickRatio : g_sellTickRatio);
   double resume_velocity=(side==SIDE_BUY ? g_bidVelocityPoints : -g_bidVelocityPoints);
   bool resume_flow=(side==SIDE_BUY ? (g_flow==FLOW_BUY || g_flow==FLOW_STRONG_BUY)
                                   : (g_flow==FLOW_SELL || g_flow==FLOW_STRONG_SELL));
   bool predictor_conflict=(side==SIDE_BUY ? (g_nextCandleBias=="SELL" && g_nextSellScore>=65)
                                          : (g_nextCandleBias=="BUY"  && g_nextBuyScore>=65));

   if(resume_ratio<ExhaustionResumeTickRatio ||
      resume_velocity<ExhaustionResumeVelocity ||
      !resume_flow || predictor_conflict)
     {
      reason=StringFormat("retrace seen %.0f pts; waiting resume ratio=%.2f vel=%.1f flow=%s",
                          retrace_points,resume_ratio,resume_velocity,FlowText(g_flow));
      return false;
     }

   if(DebugAuditLog)
      PrintFormat("AUDIT|%s|EXHAUSTION_RETRACE_RELEASE|side=%s|retrace=%.0f|ratio=%.2f|velocity=%.1f|flow=%s",
                  TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),SideText(side),
                  retrace_points,resume_ratio,resume_velocity,FlowText(g_flow));

   if(side==SIDE_BUY)
     {
      g_exhaustBuyArmed=false;
      g_exhaustBuyRetraceSeen=false;
     }
   else
     {
      g_exhaustSellArmed=false;
      g_exhaustSellRetraceSeen=false;
     }
   return true;
  }

bool EmergencyWrongWayConfirmed(const ENUM_SIDE position_side)
  {
   if(position_side==SIDE_BUY)
     {
      bool flow_opp=(g_flow==FLOW_SELL || g_flow==FLOW_STRONG_SELL);
      return ((g_sellTickRatio>=EmergencyOppTickRatio &&
               g_bidVelocityPoints<=-EmergencyOppVelocityPoints) || flow_opp);
     }

   bool flow_opp=(g_flow==FLOW_BUY || g_flow==FLOW_STRONG_BUY);
   return ((g_buyTickRatio>=EmergencyOppTickRatio &&
            g_bidVelocityPoints>=EmergencyOppVelocityPoints) || flow_opp);
  }

//====================================================================
// DIRECTION SCORE (20 + 20 + 20 + 15 + 25 = 100)
//====================================================================
int ADXStrengthPoints(const double adx)
  {
   if(adx>25.0) return 8;
   if(adx>=18.0) return 6;
   if(adx>=12.0) return 3;
   return 0;
  }

string TestProfileName()
  {
   if(g_testProfile==1) return "P1_V461_BASE";
   if(g_testProfile==2) return "P2_V461_DMIADX10_CRSI7030";
   if(g_testProfile==3) return "P3_V461_ATR14_ROC9_ZERO";
   return "P_DONE";
  }

int ExperimentRemaining()
  {
   if(!RunThreeProfileTest) return LegCount;
   if(g_threeProfileFinished || g_testProfile<1 || g_testProfile>3) return 0;
   return MathMax(0,PositionsPerProfile-g_profileOpened[g_testProfile-1]);
  }

int BuildDirectionScoreBaseV461(const ENUM_SIDE side)
  {
   int score=0;
   if(side==SIDE_BUY)
     {
      if(g_ema50>g_ema200) score+=15;
      if(g_ema50SlopePoints>0.0 && g_ema200SlopePoints>=0.0) score+=5;
      if(g_plusDI10>g_minusDI10) score+=12;
      score+=ADXStrengthPoints(g_adx10);
      if(g_plusDI14>g_minusDI14) score+=12;
      score+=ADXStrengthPoints(g_adx14);
      if(g_crsi>CRSI_UPPER_LEVEL) score+=15;
      else if(g_crsi>=CRSI_LOWER_LEVEL && g_crsi<=CRSI_UPPER_LEVEL && g_crsi>g_crsiPrevious) score+=8;
      if(g_flow==FLOW_STRONG_BUY) score+=25;
      else if(g_flow==FLOW_BUY) score+=18;
     }
   else
     {
      if(g_ema50<g_ema200) score+=15;
      if(g_ema50SlopePoints<0.0 && g_ema200SlopePoints<=0.0) score+=5;
      if(g_minusDI10>g_plusDI10) score+=12;
      score+=ADXStrengthPoints(g_adx10);
      if(g_minusDI14>g_plusDI14) score+=12;
      score+=ADXStrengthPoints(g_adx14);
      if(g_crsi<CRSI_LOWER_LEVEL) score+=15;
      else if(g_crsi>=CRSI_LOWER_LEVEL && g_crsi<=CRSI_UPPER_LEVEL && g_crsi<g_crsiPrevious) score+=8;
      if(g_flow==FLOW_STRONG_SELL) score+=25;
      else if(g_flow==FLOW_SELL) score+=18;
     }
   return MathMin(100,score);
  }

int BuildDirectionScoreProfile2(const ENUM_SIDE side)
  {
   // P2 keeps the COMPLETE v4.61 trading engine, entry/exit, structure,
   // FastFlow, cash TP/SL and no-minimum-hold behavior. Only the direction
   // confirmation variant uses DMI/ADX10 and CRSI zones 70/30.
   int score=0;
   if(side==SIDE_BUY)
     {
      if(g_ema50>g_ema200) score+=15;
      if(g_ema50SlopePoints>0.0 && g_ema200SlopePoints>=0.0) score+=5;
      if(g_plusDI10>g_minusDI10) score+=24;
      score+=2*ADXStrengthPoints(g_adx10); // max 16; DMI/ADX10 block totals 40
      if(g_crsi>70.0) score+=15;
      else if(g_crsi>=30.0 && g_crsi<=70.0 && g_crsi>g_crsiPrevious) score+=8;
      if(g_flow==FLOW_STRONG_BUY) score+=25;
      else if(g_flow==FLOW_BUY) score+=18;
     }
   else
     {
      if(g_ema50<g_ema200) score+=15;
      if(g_ema50SlopePoints<0.0 && g_ema200SlopePoints<=0.0) score+=5;
      if(g_minusDI10>g_plusDI10) score+=24;
      score+=2*ADXStrengthPoints(g_adx10);
      if(g_crsi<30.0) score+=15;
      else if(g_crsi>=30.0 && g_crsi<=70.0 && g_crsi<g_crsiPrevious) score+=8;
      if(g_flow==FLOW_STRONG_SELL) score+=25;
      else if(g_flow==FLOW_SELL) score+=18;
     }
   return MathMin(100,score);
  }

int BuildDirectionScoreProfile3(const ENUM_SIDE side)
  {
   // P3 keeps the COMPLETE v4.61 direction logic as its base.
   // Only ATR14 + ROC9 zero-line confirmations are ADDED on top.
   // ATR14 is NOT used for SL/TP sizing.
   int score=BuildDirectionScoreBaseV461(side);

   double range_ratio=(g_atr>0.0 ? g_lastClosedRange/g_atr : 0.0);
   bool atr_ok=(range_ratio>=0.25 && range_ratio<=2.00);

   if(side==SIDE_BUY)
     {
      if(g_roc9>0.0) score+=10;                  // ROC9 above zero line
      if(g_roc9>g_roc9Previous) score+=5;        // ROC9 momentum improving
      if(atr_ok) score+=5;                       // ATR14 volatility quality
     }
   else
     {
      if(g_roc9<0.0) score+=10;                  // ROC9 below zero line
      if(g_roc9<g_roc9Previous) score+=5;        // ROC9 momentum weakening
      if(atr_ok) score+=5;                       // ATR14 volatility quality
     }

   return MathMin(100,score);
  }

double GlobalTargetMoneyForAcceptedOffset(const int accepted_offset)
  {
   int seq_len=MathMax(1,MathMin(MAX_LEGS,GlobalTPSequenceLength));
   int slot=(g_globalTpAcceptedCount+MathMax(0,accepted_offset))%seq_len;
   return g_legConfig[slot].target_money;
  }

int GlobalTargetSlotForAcceptedOffset(const int accepted_offset)
  {
   int seq_len=MathMax(1,MathMin(MAX_LEGS,GlobalTPSequenceLength));
   return ((g_globalTpAcceptedCount+MathMax(0,accepted_offset))%seq_len)+1;
  }

string HybridConfirmationTag(const ENUM_SIDE side)
  {
   int p2=BuildDirectionScoreProfile2(side);
   int p3=BuildDirectionScoreProfile3(side);
   bool roc_ok=(side==SIDE_BUY ? g_roc9>0.0 : g_roc9<0.0);
   bool roc_mom=(side==SIDE_BUY ? g_roc9>g_roc9Previous : g_roc9<g_roc9Previous);
   double atr_ratio=(g_atrPrevious>0.0 ? g_atr/g_atrPrevious : 1.0);
   bool atr_ok=(atr_ratio>=0.25 && atr_ratio<=2.0);
   return StringFormat("P2=%d,P3=%d,ROC=%s,ROCm=%s,ATR=%s",p2,p3,roc_ok?"OK":"CONFLICT",roc_mom?"OK":"FLAT",atr_ok?"OK":"OUT");
  }


string NextCandleContextTag(const ENUM_SIDE side)
  {
   // Diagnostic only: does NOT hard-block the P1 >=75 direct trigger.
   // Combines the exact ingredients requested for short-horizon reversal/continuation:
   // zone + liquidity sweep + ROC9 + DMI + FastFlow.
   bool zone_ok=NearSupportResistance(side) || InFibZone(side,false) || InFVG(side);
   bool sweep_ok=(side==SIDE_BUY ? EventFresh(g_bullSweepTime,SweepMaxAgeBars)
                                 : EventFresh(g_bearSweepTime,SweepMaxAgeBars));
   bool roc_ok=(side==SIDE_BUY ? (g_roc9>0.0 && g_roc9>=g_roc9Previous)
                               : (g_roc9<0.0 && g_roc9<=g_roc9Previous));
   bool dmi_ok=(side==SIDE_BUY ? (g_plusDI10>g_minusDI10)
                               : (g_minusDI10>g_plusDI10));
   bool flow_ok=(side==SIDE_BUY ? (g_flow==FLOW_BUY || g_flow==FLOW_STRONG_BUY)
                                : (g_flow==FLOW_SELL || g_flow==FLOW_STRONG_SELL));

   int confirm=(zone_ok?1:0)+(sweep_ok?1:0)+(roc_ok?1:0)+(dmi_ok?1:0)+(flow_ok?1:0);
   string bias=(confirm>=4 ? "CONFIRMED" : (confirm==3 ? "WATCH" : "CONFLICT"));

   return StringFormat("NEXT=%s(%d/5),ZONE=%s,SWEEP=%s,ROC9=%s,DMI10=%s,FLOW=%s",
                       bias,confirm,
                       zone_ok?"Y":"N",
                       sweep_ok?"Y":"N",
                       roc_ok?"Y":"N",
                       dmi_ok?"Y":"N",
                       flow_ok?"Y":"N");
  }

int BuildDirectionScore(const ENUM_SIDE side)
  {
   if(!RunThreeProfileTest || g_testProfile==1) return BuildDirectionScoreBaseV461(side);
   if(g_testProfile==2) return BuildDirectionScoreProfile2(side);
   if(g_testProfile==3) return BuildDirectionScoreProfile3(side);
   return 0;
  }

//====================================================================
// STRUCTURE SCORE (15 + 20 + 15 + 20 + 15 + 10 + 5 = 100)
//====================================================================
int BuildStructureScore(const ENUM_SIDE side)
  {
   int score=0;
   if((side==SIDE_BUY && g_structure==STRUCTURE_BULLISH) ||
      (side==SIDE_SELL && g_structure==STRUCTURE_BEARISH)) score+=15;
   if((side==SIDE_BUY && EventFresh(g_bullBOSTime,StructureEventMaxAgeBars)) ||
      (side==SIDE_SELL && EventFresh(g_bearBOSTime,StructureEventMaxAgeBars))) score+=20;
   if((side==SIDE_BUY && EventFresh(g_bullCHoCHTime,StructureEventMaxAgeBars)) ||
      (side==SIDE_SELL && EventFresh(g_bearCHoCHTime,StructureEventMaxAgeBars))) score+=15;
   if((side==SIDE_BUY && EventFresh(g_bullSweepTime,SweepMaxAgeBars)) ||
      (side==SIDE_SELL && EventFresh(g_bearSweepTime,SweepMaxAgeBars))) score+=20;
   if(InFibZone(side,false)) score+=15;
   if(InFVG(side)) score+=10;
   if(NearSupportResistance(side)) score+=5;
   return MathMin(100,score);
  }

double OpposingStructureDistanceATR(const ENUM_SIDE side,const double price)
  {
   if(g_atr<=0.0) return 0.0;
   double nearest=DBL_MAX;
   if(side==SIDE_BUY)
     {
      if(g_swingHigh.price>price) nearest=MathMin(nearest,g_swingHigh.price-price);
      if(g_previousSwingHigh.price>price) nearest=MathMin(nearest,g_previousSwingHigh.price-price);
      if(g_bearFVGActive && g_bearFVGLow>price) nearest=MathMin(nearest,g_bearFVGLow-price);
     }
   else
     {
      if(g_swingLow.price>0.0 && g_swingLow.price<price) nearest=MathMin(nearest,price-g_swingLow.price);
      if(g_previousSwingLow.price>0.0 && g_previousSwingLow.price<price) nearest=MathMin(nearest,price-g_previousSwingLow.price);
      if(g_bullFVGActive && g_bullFVGHigh<price) nearest=MathMin(nearest,price-g_bullFVGHigh);
     }
   if(nearest==DBL_MAX) return DesiredRoomATR+1.0;
   return nearest/g_atr;
  }

int BuildEntryQuality(const ENUM_SIDE side)
  {
   if(g_atr<=0.0) return 0;
   MqlTick tick;
   if(!SymbolInfoTick(_Symbol,tick)) return 0;
   double price=side==SIDE_BUY ? tick.ask : tick.bid;
   int score=0;

   // 25 points: distance to nearest opposing structure / room to target.
   double room_atr=OpposingStructureDistanceATR(side,price);
   if(room_atr>=DesiredRoomATR) score+=25;
   else if(room_atr>=1.0) score+=17;
   else if(room_atr>=MinimumRoomATR) score+=8;

   // 10 points: completed-candle exhaustion.
   double candle_atr=g_lastClosedRange/g_atr;
   if(candle_atr<=1.25) score+=10;
   else if(candle_atr<=1.75) score+=5;

   // 15 points: live chase distance from the last completed close.
   double chase=side==SIDE_BUY ? MathMax(0.0,price-g_lastClosedClose)/g_atr
                               : MathMax(0.0,g_lastClosedClose-price)/g_atr;
   if(chase<=0.20) score+=15;
   else if(chase<=0.35) score+=10;
   else if(chase<=MaxChaseATR) score+=5;

   // 10 points: static and baseline spread quality.
   if(g_currentSpreadPoints<=MaxSpreadPoints*0.50 &&
      (g_spreadBaselinePoints<=0.0 || g_currentSpreadPoints<=g_spreadBaselinePoints*1.10)) score+=10;
   else if(g_currentSpreadPoints<=MaxSpreadPoints*0.75 &&
           (g_spreadBaselinePoints<=0.0 || g_currentSpreadPoints<=g_spreadBaselinePoints*1.35)) score+=5;

   // 10 points: no spread shock.
   if(RunThreeProfileTest || !g_spreadShock) score+=10; // v4.73: spread shock telemetry-only during profile comparison

   // 10 points: broker tick freshness.
   if(g_tickAgeSeconds<=1.0) score+=10;
   else if(g_tickAgeSeconds<=MaxTickAgeSeconds) score+=5;

   // 5 points: side-specific FastFlow freshness.
   long flow_time=side==SIDE_BUY ? g_lastBuyFlowMsc : g_lastSellFlowMsc;
   if(flow_time>0 && tick.time_msc-flow_time<=2000) score+=5;

   // 10 points: Fibonacci/FVG entry location.
   if(InFibZone(side,true) || InFVG(side)) score+=10;
   else if(InFibZone(side,false)) score+=5;

   // 5 points: liquidity location.
   if((side==SIDE_BUY && EventFresh(g_bullSweepTime,SweepMaxAgeBars)) ||
      (side==SIDE_SELL && EventFresh(g_bearSweepTime,SweepMaxAgeBars)) ||
      NearSupportResistance(side)) score+=5;

   // Excessive chasing is expressed through the only relevant hard gate:
   // EntryQuality cannot pass even if unrelated quality components are strong.
   if(chase>MaxChaseATR) score=MathMin(score,MathMax(0,MinEntryQuality-1));
   return MathMin(100,score);
  }

string SetupType(const ENUM_SIDE side)
  {
   if((side==SIDE_BUY && EventFresh(g_bullSweepTime,SweepMaxAgeBars)) ||
      (side==SIDE_SELL && EventFresh(g_bearSweepTime,SweepMaxAgeBars))) return "LIQUIDITY_REVERSAL";
   if((side==SIDE_BUY && EventFresh(g_bullCHoCHTime,StructureEventMaxAgeBars)) ||
      (side==SIDE_SELL && EventFresh(g_bearCHoCHTime,StructureEventMaxAgeBars))) return "CHOCH_TRANSITION";
   if((side==SIDE_BUY && EventFresh(g_bullBOSTime,StructureEventMaxAgeBars)) ||
      (side==SIDE_SELL && EventFresh(g_bearBOSTime,StructureEventMaxAgeBars))) return "BOS_CONTINUATION";
   if(InFibZone(side,false) || InFVG(side)) return "RETRACEMENT_CONFLUENCE";
   return "TREND_FASTFLOW";
  }

bool SideTradingAvailable(const ENUM_SIDE side,string &reason)
  {
   ENUM_SYMBOL_TRADE_MODE mode=(ENUM_SYMBOL_TRADE_MODE)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_MODE);
   if(mode==SYMBOL_TRADE_MODE_DISABLED || mode==SYMBOL_TRADE_MODE_CLOSEONLY)
     {
      reason="symbol trading unavailable";
      return false;
     }
   if(side==SIDE_BUY && mode==SYMBOL_TRADE_MODE_SHORTONLY)
     {
      reason="symbol is short-only";
      return false;
     }
   if(side==SIDE_SELL && mode==SYMBOL_TRADE_MODE_LONGONLY)
     {
      reason="symbol is long-only";
      return false;
     }
   long order_mode=SymbolInfoInteger(_Symbol,SYMBOL_ORDER_MODE);
   if((order_mode & SYMBOL_ORDER_MARKET)==0 || (order_mode & SYMBOL_ORDER_SL)==0 || (order_mode & SYMBOL_ORDER_TP)==0)
     {
      reason="market orders with server SL/TP are unavailable";
      return false;
     }
   return true;
  }

bool PassesEntryGuards(const ENUM_SIDE side,string &reason)
  {
   if(RunThreeProfileTest && (g_threeProfileFinished || ExperimentRemaining()<=0)) { reason="profile quota reached / waiting switch"; return false; }
   if(HasOwnPositions()) { reason="existing basket"; return false; }
   if(g_entryPending) { reason="entry already pending"; return false; }
   if(g_riskHalt) { reason="risk halt: "+HaltReasonText(); return false; }
   if(!g_closedDataReady) { reason="closed-bar data unavailable"; return false; }
   if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !MQLInfoInteger(MQL_TRADE_ALLOWED) ||
      !AccountInfoInteger(ACCOUNT_TRADE_ALLOWED) || !AccountInfoInteger(ACCOUNT_TRADE_EXPERT))
     { reason="trading disabled"; return false; }
   if(!SideTradingAvailable(side,reason)) return false;
   if(g_tickAgeSeconds>MaxTickAgeSeconds) { reason="stale broker tick"; return false; }
   if(g_currentSpreadPoints>MaxSpreadPoints) { reason="abnormal spread"; return false; }
   if(!RunThreeProfileTest && g_spreadShock) { reason="spread shock"; return false; } // v4.73: non-blocking in 3-profile test
   if(TimeCurrent()-g_lastTradeCloseTime<CooldownSeconds) { reason="cooldown"; return false; }
   if((side==SIDE_BUY && g_flow==FLOW_STRONG_SELL) || (side==SIDE_SELL && g_flow==FLOW_STRONG_BUY))
     { reason="extreme opposite FastFlow"; return false; }
   if((datetime)g_lastSignalBarTime==g_currentBarTime && g_lastSignalSide==(int)side)
     { reason="duplicate signal"; return false; }
   return true;
  }

bool IsStrongFlowForSide(const ENUM_SIDE side)
  {
   return side==SIDE_BUY ? g_flow==FLOW_STRONG_BUY : g_flow==FLOW_STRONG_SELL;
  }

bool PassStrongFastFlowEntryRoute(const Candidate &candidate,string &reason)
  {
   reason="";
   if(!UseStrongFastFlowEntry)
     {
      reason="strong FastFlow route disabled";
      return false;
     }
   if(!IsStrongFlowForSide(candidate.side))
     {
      reason="strong FastFlow not present";
      return false;
     }
   if(candidate.direction_score<StrongFlowMinDirectionScore)
     {
      reason=StringFormat("strong-flow direction %d < %d",candidate.direction_score,StrongFlowMinDirectionScore);
      return false;
     }
   if(candidate.entry_quality<StrongFlowMinEntryQuality)
     {
      reason=StringFormat("strong-flow EntryQuality %d < %d",candidate.entry_quality,StrongFlowMinEntryQuality);
      return false;
     }
   if(!StrongFlowAllowZeroStructure && candidate.structure_score<=0)
     {
      reason="strong-flow route requires structure";
      return false;
     }
   return true;
  }

bool PassLiquidityReversalEntryRoute(const Candidate &candidate,string &reason)
  {
   reason="";
   if(!UseLiquidityReversalEntry)
     {
      reason="liquidity-reversal route disabled";
      return false;
     }
   if(candidate.setup_type!="LIQUIDITY_REVERSAL")
     {
      reason="not a liquidity reversal";
      return false;
     }
   if(candidate.direction_score<LiquidityMinDirectionScore)
     {
      reason=StringFormat("liquidity direction %d < %d",candidate.direction_score,LiquidityMinDirectionScore);
      return false;
     }
   if(candidate.structure_score<LiquidityMinStructureScore)
     {
      reason=StringFormat("liquidity structure %d < %d",candidate.structure_score,LiquidityMinStructureScore);
      return false;
     }
   if(candidate.combined_score<LiquidityMinCombinedScore)
     {
      reason=StringFormat("liquidity combined %d < %d",candidate.combined_score,LiquidityMinCombinedScore);
      return false;
     }
   if(candidate.entry_quality<LiquidityMinEntryQuality)
     {
      reason=StringFormat("liquidity EntryQuality %d < %d",candidate.entry_quality,LiquidityMinEntryQuality);
      return false;
     }
   return true;
  }

Candidate BuildCandidate(const ENUM_SIDE side)
  {
   Candidate candidate;
   candidate.side=side;
   candidate.direction_score=BuildDirectionScore(side);
   candidate.structure_score=BuildStructureScore(side);
   candidate.combined_score=(int)MathRound(candidate.direction_score*0.65+candidate.structure_score*0.35);
   candidate.entry_quality=BuildEntryQuality(side);
   candidate.setup_type=(RunThreeProfileTest ? TestProfileName()+"_"+SetupType(side) : SetupType(side));
   candidate.signal_id=RunThreeProfileTest
                       ? StringFormat("%s|%I64u|P%d|%s|%I64d",_Symbol,MagicNumber,g_testProfile,SideText(side),(long)g_currentBarTime)
                       : StringFormat("%s|%I64u|%s|%I64d",_Symbol,MagicNumber,SideText(side),(long)g_currentBarTime);
   candidate.eligible=false;
   candidate.reject_reason="";
   if(!PassesEntryGuards(side,candidate.reject_reason)) return candidate;

   // v4.61 confirmation gate: BUY and SELL require >=75/100 direction score.
   if(candidate.direction_score<MinDirectionPercent)
     {
      candidate.reject_reason=StringFormat("direction %d%% < %d%%",candidate.direction_score,MinDirectionPercent);
      return candidate;
     }

   // v4.80 FINAL HYBRID rule:
   // Keep the user-observed P1 behaviour: base-v4.61 Direction >=75 is the
   // direct trigger after hard execution/spread/stale-tick guards. P2/P3
   // calculations are telemetry/confirmation only and DO NOT reduce signals.
   if(HybridDirect75Entry && candidate.direction_score>=75 && (!RunThreeProfileTest || g_testProfile==1))
     {
      if(NextCandlePredictorEnabled && PredictorHardFilter)
        {
         int predictor_side_score=(side==SIDE_BUY ? g_nextBuyScore : g_nextSellScore);
         string predictor_side_bias=(side==SIDE_BUY ? "BUY" : "SELL");
         if(predictor_side_score<PredictorMinScore || g_nextCandleBias!=predictor_side_bias)
           {
            candidate.reject_reason=StringFormat("next-candle predictor %s score=%d bias=%s",
                                                 predictor_side_bias,predictor_side_score,g_nextCandleBias);
            return candidate;
           }
        }
      candidate.eligible=true;
      candidate.setup_type="P1_FINAL_75_DIRECT";
      if(HybridTelemetry) {
         string rev_reason="";
         string rev_tag=StrongOppositeReversal(side,rev_reason) ? "REV=BLOCK" : "REV=OK";
         candidate.setup_type += "["+HybridConfirmationTag(side)+"]["+NextCandleContextTag(side)+"]["+NextCandlePredictorTag()+"]["+SafeTelemetryTag()+"]["+rev_tag+"]";
        }
      return candidate;
     }

   // Route 1: normal structure/combined-score entry.
   bool normal_route=(candidate.combined_score>=MinSetupScore &&
                      candidate.entry_quality>=MinEntryQuality);

   // Route 2: strong live FastFlow can enter even when M1 structure has not
   // produced a fresh BOS/CHoCH/Fib/FVG score yet. Safety/risk/execution
   // guards above and all preflight checks in ExecuteCandidate still apply.
   string strong_reason="";
   bool strong_route=PassStrongFastFlowEntryRoute(candidate,strong_reason);

   // Route 3: liquidity reversal. Reversal setups naturally can have a
   // weaker trend-direction score, so require real structure + high entry
   // quality instead of forcing them through the same trend threshold.
   // All hard guards and ExecuteCandidate risk/execution checks still apply.
   string liquidity_reason="";
   bool liquidity_route=PassLiquidityReversalEntryRoute(candidate,liquidity_reason);

   if(normal_route || strong_route || liquidity_route)
     {
      candidate.eligible=true;
      if(liquidity_route && !normal_route) candidate.setup_type="LIQUIDITY_REVERSAL";
      else if(strong_route && !normal_route) candidate.setup_type="STRONG_FASTFLOW";
      return candidate;
     }

   if(candidate.combined_score<MinSetupScore)
     {
      candidate.reject_reason=StringFormat("normal C=%d<%d; strong=%s; liquidity=%s",
                                           candidate.combined_score,MinSetupScore,strong_reason,liquidity_reason);
      return candidate;
     }
   candidate.reject_reason=StringFormat("EntryQuality %d < %d; strong=%s; liquidity=%s",
                                        candidate.entry_quality,MinEntryQuality,strong_reason,liquidity_reason);
   return candidate;
  }

void LogRejectThrottled(const Candidate &candidate)
  {
   string key=candidate.signal_id+"|"+candidate.reject_reason;
   if(key==g_lastRejectKey && TimeCurrent()-g_lastRejectTime<10) return;
   g_lastRejectKey=key;
   g_lastRejectTime=TimeCurrent();
   if(DebugAuditLog)
      PrintFormat("AUDIT|%s|SIGNAL_REJECT|signal=%s|side=%s|setup=%s|Dir=%d|Struct=%d|Combined=%d|EQ=%d|reason=%s",
                  TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),candidate.signal_id,SideText(candidate.side),
                  candidate.setup_type,candidate.direction_score,candidate.structure_score,
                  candidate.combined_score,candidate.entry_quality,candidate.reject_reason);
  }

string RecentStructureEvents(const ENUM_SIDE side)
  {
   bool bos=side==SIDE_BUY ? EventFresh(g_bullBOSTime,StructureEventMaxAgeBars) : EventFresh(g_bearBOSTime,StructureEventMaxAgeBars);
   bool choch=side==SIDE_BUY ? EventFresh(g_bullCHoCHTime,StructureEventMaxAgeBars) : EventFresh(g_bearCHoCHTime,StructureEventMaxAgeBars);
   bool sweep=side==SIDE_BUY ? EventFresh(g_bullSweepTime,SweepMaxAgeBars) : EventFresh(g_bearSweepTime,SweepMaxAgeBars);
   return StringFormat("BOS=%d,CHoCH=%d,Sweep=%d",(int)bos,(int)choch,(int)sweep);
  }

void AuditLifecycle(const string stage,const Candidate &candidate,const int leg_index,
                    const double entry,const double sl,const string sl_source,const double tp,
                    const double requested,const double filled,const double risk,const string extra)
  {
   if(!DebugAuditLog) return;
   string key=stage+"|"+candidate.signal_id+"|"+IntegerToString(leg_index);
   if(stage=="SIGNAL" && key==g_lastLifecycleLogKey) return;
   if(stage=="SIGNAL") g_lastLifecycleLogKey=key;
   string fvg=InFVG(candidate.side) ? "TOUCH" : "NO";
   PrintFormat("AUDIT|%s|%s|signal=%s|basket=%I64u|side=%s|setup=%s|Dir=%d|StructScore=%d|EQ=%d|EMA50=%.5f|EMA200=%.5f|ADX10=%.2f|+DI10=%.2f|-DI10=%.2f|ADX14=%.2f|+DI14=%.2f|-DI14=%.2f|CRSI=%.2f|Flow=%s|TPS=%.1f|BuyR=%.2f|SellR=%.2f|BidVel=%.1f|AskVel=%.1f|Accel=%.1f|Disp=%.1f|ATR=%.5f|ROC9=%.5f|ROC9Prev=%.5f|P2Dir=%d|P3Dir=%d|spread=%.1f|baseline=%.1f|tickAge=%.2f|structure=%s|SwingH=%.5f|SwingL=%.5f|BOSLevel=%.5f|%s|FibOrigin=%.5f|FibExtreme=%.5f|FibZone=%s|FibLevels=%s|FVG=%s|SR=%d|entry=%.5f|SL=%.5f|SLSource=%s|leg=%d|TP=%.5f|targetMoney=%.2f|requested=%.4f|filled=%.4f|risk=%.2f|%s",
               TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),stage,candidate.signal_id,g_basketId,
               SideText(candidate.side),candidate.setup_type,candidate.direction_score,candidate.structure_score,
               candidate.entry_quality,g_ema50,g_ema200,g_adx10,g_plusDI10,g_minusDI10,g_adx14,
               g_plusDI14,g_minusDI14,g_crsi,FlowText(g_flow),g_ticksPerSecond,g_buyTickRatio,g_sellTickRatio,
               g_bidVelocityPoints,g_askVelocityPoints,g_accelerationPoints,g_displacementPoints,
               g_atr,g_roc9,g_roc9Previous,BuildDirectionScoreProfile2(candidate.side),BuildDirectionScoreProfile3(candidate.side),
               g_currentSpreadPoints,g_spreadBaselinePoints,g_tickAgeSeconds,StructureText(g_structure),g_swingHigh.price,
               g_swingLow.price,candidate.side==SIDE_BUY ? g_bullBOSLevel : g_bearBOSLevel,
               RecentStructureEvents(candidate.side),
               candidate.side==SIDE_BUY ? g_bullFibOrigin : g_bearFibOrigin,
               candidate.side==SIDE_BUY ? g_bullFibExtreme : g_bearFibExtreme,
               FibZoneText(candidate.side),FibLevelsText(candidate.side),fvg,(int)NearSupportResistance(candidate.side),
               entry,sl,sl_source,leg_index,tp,
               leg_index>=1 && leg_index<=MAX_LEGS ? g_legConfig[leg_index-1].target_money : 0.0,
               requested,filled,risk,extra);
  }

//====================================================================
// RISK HISTORY AND TERMINAL-GLOBAL PERSISTENCE
//====================================================================
double RealizedSince(const datetime from_time)
  {
   datetime to_time=TimeCurrent()+60;
   if(!HistorySelect(from_time,to_time)) return 0.0;
   double total=0.0;
   int deals=HistoryDealsTotal();
   for(int i=0;i<deals;i++)
     {
      ulong deal=HistoryDealGetTicket(i);
      if(deal==0) continue;
      if(HistoryDealGetString(deal,DEAL_SYMBOL)!=_Symbol) continue;
      if((ulong)HistoryDealGetInteger(deal,DEAL_MAGIC)!=MagicNumber) continue;
      total+=HistoryDealGetDouble(deal,DEAL_PROFIT);
      total+=HistoryDealGetDouble(deal,DEAL_SWAP);
      total+=HistoryDealGetDouble(deal,DEAL_COMMISSION);
      total+=HistoryDealGetDouble(deal,DEAL_FEE);
     }
   return total;
  }

void RecalculateRealizedHistory()
  {
   if(g_dayStamp<=0) g_dayStamp=StartOfDay(TimeCurrent());
   if(g_weekStamp<=0) g_weekStamp=StartOfWeek(TimeCurrent());
   g_dailyRealized=RealizedSince(g_dayStamp);
   g_weeklyRealized=RealizedSince(g_weekStamp);
  }

void UpdateRiskState(const bool force_history)
  {
   datetime now=TimeCurrent();
   datetime today=StartOfDay(now);
   datetime week=StartOfWeek(now);
   datetime hour=StartOfHour(now);
   bool period_changed=false;
   if(g_dayStamp!=today)
     {
      g_dayStamp=today;
      g_dayStartBalance=AccountInfoDouble(ACCOUNT_BALANCE);
      g_dayStartEquity=AccountInfoDouble(ACCOUNT_EQUITY);
      g_basketsToday=0;
      period_changed=true;
     }
   if(g_weekStamp!=week)
     {
      g_weekStamp=week;
      g_weekStartBalance=AccountInfoDouble(ACCOUNT_BALANCE);
      period_changed=true;
     }
   if(g_hourStamp!=hour)
     {
      g_hourStamp=hour;
      g_basketsThisHour=0;
     }
   if(force_history || period_changed) RecalculateRealizedHistory();

   double equity=AccountInfoDouble(ACCOUNT_EQUITY);
   if(g_peakEquity<=0.0 || equity>g_peakEquity) g_peakEquity=equity;
   g_equityDrawdownPercent=g_peakEquity>0.0 ? 100.0*(g_peakEquity-equity)/g_peakEquity : 0.0;
   double daily_total=g_dailyRealized+BasketFloatingRaw();
   double weekly_total=g_weeklyRealized+BasketFloatingRaw();

   g_riskHalt=false;
   g_haltReason=HALT_NONE;
   // v4.57 TEST MODE: all loss-based trading halts are intentionally disabled.
   // DAILY_LOSS, WEEKLY_LOSS, EQUITY_DRAWDOWN and CONSECUTIVE_LOSSES
   // are still measured/logged where applicable, but never set g_riskHalt.
   // v4.58 TEST MODE: basket frequency halts disabled.
   // Counters remain for telemetry/reporting only and never block entries.
   // BASKETS_PER_HOUR and BASKETS_PER_DAY are intentionally non-blocking.
  }

void LoadPersistentState()
  {
   datetime now=TimeCurrent();
   bool valid=GlobalVariableCheck(g_gvPrefix+"version") && MathAbs(GVGet("version",0.0)-STATE_VERSION)<0.001;
   if(!valid)
     {
      g_dayStamp=StartOfDay(now);
      g_weekStamp=StartOfWeek(now);
      g_hourStamp=StartOfHour(now);
      g_dayStartBalance=AccountInfoDouble(ACCOUNT_BALANCE);
      g_dayStartEquity=AccountInfoDouble(ACCOUNT_EQUITY);
      g_weekStartBalance=AccountInfoDouble(ACCOUNT_BALANCE);
      g_peakEquity=AccountInfoDouble(ACCOUNT_EQUITY);
      PersistState(true);
      return;
     }

   g_dayStamp=(datetime)(long)GVGet("day_stamp",(double)StartOfDay(now));
   g_weekStamp=(datetime)(long)GVGet("week_stamp",(double)StartOfWeek(now));
   g_hourStamp=(datetime)(long)GVGet("hour_stamp",(double)StartOfHour(now));
   g_dayStartBalance=GVGet("day_balance",AccountInfoDouble(ACCOUNT_BALANCE));
   g_dayStartEquity=GVGet("day_equity",AccountInfoDouble(ACCOUNT_EQUITY));
   g_weekStartBalance=GVGet("week_balance",AccountInfoDouble(ACCOUNT_BALANCE));
   g_peakEquity=GVGet("peak_equity",AccountInfoDouble(ACCOUNT_EQUITY));
   g_dailyRealized=GVGet("daily_realized",0.0);
   g_weeklyRealized=GVGet("weekly_realized",0.0);
   g_consecutiveLosses=(int)GVGet("consecutive",0.0);
   g_basketsThisHour=(int)GVGet("hour_count",0.0);
   g_basketsToday=(int)GVGet("day_count",0.0);
   g_riskHalt=GVGet("halt",0.0)>0.5;
   g_haltReason=(ENUM_HALT_REASON)(int)GVGet("halt_reason",0.0);
   g_basketId=(ulong)GVGet("basket_id",0.0);
   g_lastTradeCloseTime=(datetime)(long)GVGet("cooldown",0.0);
   g_lastSignalBarTime=(datetime)(long)GVGet("signal_time",0.0);
   g_lastSignalSide=(int)GVGet("signal_side",-1.0);
   g_globalTpAcceptedCount=(int)GVGet("global_tp_accepted",0.0);
   g_testProfile=(int)GVGet("test_profile",1.0);
   g_threeProfileFinished=GVGet("test_finished",0.0)>0.5;
   for(int p=0;p<3;p++)
     {
      string ps=IntegerToString(p+1);
      g_profileOpened[p]=(int)GVGet("test_opened"+ps,0.0);
      g_profileClosed[p]=(int)GVGet("test_closed"+ps,0.0);
      g_profileNet[p]=GVGet("test_net"+ps,0.0);
     }
   for(int i=0;i<MAX_LEGS;i++)
     {
      string suffix=IntegerToString(i+1);
      g_legs[i].ticket=(ulong)GVGet("ticket"+suffix,0.0);
      g_legs[i].position_id=(ulong)GVGet("position"+suffix,0.0);
      g_legs[i].target_money=GVGet("targetmoney"+suffix,g_legConfig[i].target_money);
      g_legs[i].peak_profit=GVGet("peak"+suffix,-DBL_MAX);
      g_legs[i].mfe=GVGet("mfe"+suffix,-DBL_MAX);
      g_legs[i].mae=GVGet("mae"+suffix,DBL_MAX);
      g_legs[i].lock_armed=GVGet("lock"+suffix,0.0)>0.5;
      g_legs[i].break_even_applied=GVGet("be"+suffix,0.0)>0.5;
     }
  }

void PersistState(const bool force_write)
  {
   datetime now=TimeCurrent();
   if(!force_write && now==g_lastPersist) return;
   g_lastPersist=now;
   GVSet("version",STATE_VERSION);
   GVSet("day_stamp",(double)g_dayStamp);
   GVSet("week_stamp",(double)g_weekStamp);
   GVSet("hour_stamp",(double)g_hourStamp);
   GVSet("day_balance",g_dayStartBalance);
   GVSet("day_equity",g_dayStartEquity);
   GVSet("week_balance",g_weekStartBalance);
   GVSet("peak_equity",g_peakEquity);
   GVSet("daily_realized",g_dailyRealized);
   GVSet("weekly_realized",g_weeklyRealized);
   GVSet("consecutive",(double)g_consecutiveLosses);
   GVSet("hour_count",(double)g_basketsThisHour);
   GVSet("day_count",(double)g_basketsToday);
   GVSet("halt",g_riskHalt ? 1.0 : 0.0);
   GVSet("halt_reason",(double)g_haltReason);
   GVSet("basket_id",(double)g_basketId);
   GVSet("cooldown",(double)g_lastTradeCloseTime);
   GVSet("signal_time",(double)g_lastSignalBarTime);
   GVSet("signal_side",(double)g_lastSignalSide);
   GVSet("global_tp_accepted",(double)g_globalTpAcceptedCount);
   GVSet("test_profile",(double)g_testProfile);
   GVSet("test_finished",g_threeProfileFinished ? 1.0 : 0.0);
   for(int p=0;p<3;p++)
     {
      string ps=IntegerToString(p+1);
      GVSet("test_opened"+ps,(double)g_profileOpened[p]);
      GVSet("test_closed"+ps,(double)g_profileClosed[p]);
      GVSet("test_net"+ps,g_profileNet[p]);
     }
   for(int i=0;i<MAX_LEGS;i++)
     {
      string suffix=IntegerToString(i+1);
      GVSet("ticket"+suffix,(double)g_legs[i].ticket);
      GVSet("position"+suffix,(double)g_legs[i].position_id);
      GVSet("targetmoney"+suffix,g_legs[i].target_money);
      GVSet("peak"+suffix,g_legs[i].peak_profit);
      GVSet("mfe"+suffix,g_legs[i].mfe);
      GVSet("mae"+suffix,g_legs[i].mae);
      GVSet("lock"+suffix,g_legs[i].lock_armed ? 1.0 : 0.0);
      GVSet("be"+suffix,g_legs[i].break_even_applied ? 1.0 : 0.0);
     }
   GlobalVariablesFlush();
  }

double PositionHistoryCosts(const ulong position_id)
  {
   if(position_id==0 || !HistorySelectByPosition(position_id)) return 0.0;
   double costs=0.0;
   int deals=HistoryDealsTotal();
   for(int i=0;i<deals;i++)
     {
      ulong deal=HistoryDealGetTicket(i);
      if(deal==0) continue;
      ENUM_DEAL_ENTRY entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY);
      if(entry==DEAL_ENTRY_IN || entry==DEAL_ENTRY_INOUT)
        {
         costs+=HistoryDealGetDouble(deal,DEAL_COMMISSION);
         costs+=HistoryDealGetDouble(deal,DEAL_FEE);
        }
     }
   return costs;
  }

double ProjectedLossMoney(const ENUM_SIDE side,const double volume,const double entry,const double sl)
  {
   double result=0.0;
   ENUM_ORDER_TYPE type=side==SIDE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
   if(!OrderCalcProfit(type,_Symbol,volume,entry,sl,result)) return -1.0;
   return MathAbs(MathMin(0.0,result));
  }

void ReconcilePositions(const bool startup)
  {
   bool seen[MAX_LEGS];
   for(int i=0;i<MAX_LEGS;i++) seen[i]=false;
   int found=0;
   ulong maximum_basket=g_basketId;
   for(int position_index=PositionsTotal()-1;position_index>=0;position_index--)
     {
      ulong ticket=PositionGetTicket(position_index);
      if(ticket==0) continue;
      if(PositionGetString(POSITION_SYMBOL)!=_Symbol) continue;
      if((ulong)PositionGetInteger(POSITION_MAGIC)!=MagicNumber) continue;
      string comment=PositionGetString(POSITION_COMMENT);
      int leg_id=ParseLegId(comment);
      int index=leg_id-1;
      if(index<0 || index>=MAX_LEGS || seen[index])
        {
         index=-1;
         for(int j=0;j<MAX_LEGS;j++) if(!seen[j]) { index=j; break; }
         if(index<0) continue;
        }
      ulong position_id=(ulong)PositionGetInteger(POSITION_IDENTIFIER);
      bool logical_entry_request=g_entryPending &&
                                 (g_legs[index].status==LEG_REQUESTED || g_legs[index].status==LEG_RECONCILE_REQUIRED);
      bool same_position=(g_legs[index].ticket==ticket || g_legs[index].position_id==position_id || logical_entry_request);
      double saved_peak=same_position ? g_legs[index].peak_profit : -DBL_MAX;
      double saved_mfe=same_position ? g_legs[index].mfe : -DBL_MAX;
      double saved_mae=same_position ? g_legs[index].mae : DBL_MAX;
      bool saved_lock=same_position && g_legs[index].lock_armed;
      bool saved_be=same_position && g_legs[index].break_even_applied;
      if(!same_position) ResetLegRuntime(index);
      g_legs[index].leg_id=index+1;
      g_legs[index].ticket=ticket;
      g_legs[index].position_id=position_id;
      g_legs[index].filled_volume=PositionGetDouble(POSITION_VOLUME);
      if(g_legs[index].requested_volume<=0.0) g_legs[index].requested_volume=g_legs[index].filled_volume;
      g_legs[index].entry=PositionGetDouble(POSITION_PRICE_OPEN);
      g_legs[index].sl=PositionGetDouble(POSITION_SL);
      g_legs[index].tp=PositionGetDouble(POSITION_TP);
      g_legs[index].target_money=g_legConfig[index].target_money;
      g_legs[index].entry_costs=PositionHistoryCosts(position_id);
      g_legs[index].status=LEG_OPEN;
      g_legs[index].risk_money=ProjectedLossMoney((ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY ? SIDE_BUY : SIDE_SELL,
                                                  g_legs[index].filled_volume,g_legs[index].entry,g_legs[index].sl);
      double current=PositionNetProfitBySelection(index);
      g_legs[index].peak_profit=saved_peak<=-DBL_MAX/2.0 ? current : MathMax(saved_peak,current);
      g_legs[index].mfe=saved_mfe<=-DBL_MAX/2.0 ? current : MathMax(saved_mfe,current);
      g_legs[index].mae=saved_mae>=DBL_MAX/2.0 ? current : MathMin(saved_mae,current);
      g_legs[index].lock_armed=saved_lock;
      g_legs[index].break_even_applied=saved_be;
      seen[index]=true;
      found++;
      ulong parsed=ParseBasketId(comment);
      if(parsed>maximum_basket) maximum_basket=parsed;
      g_basketSide=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY ? SIDE_BUY : SIDE_SELL;
      datetime position_time=(datetime)PositionGetInteger(POSITION_TIME);
      if(g_basketStartTime==0 || position_time<g_basketStartTime) g_basketStartTime=position_time;
     }

   g_basketId=maximum_basket;
   for(int i=0;i<MAX_LEGS;i++)
     {
      if(!seen[i] && (g_legs[i].status==LEG_OPEN || g_legs[i].status==LEG_CLOSE_PENDING || g_legs[i].status==LEG_RECONCILE_REQUIRED))
         g_legs[i].status=LEG_CLOSED;
     }
   if(found>0)
     {
      g_basketActive=true;
      if(startup) g_expectedLegs=MathMax(found,LegCount);
     }
   else if(startup)
      g_basketActive=false;
  }

//====================================================================
// STRUCTURE-FIRST STOP, TARGETS, PREFLIGHT AND BASKET EXECUTION
//====================================================================
bool BuildStructureStop(const ENUM_SIDE side,const double entry,double &sl,string &source,string &reason)
  {
   sl=0.0;
   source="";
   reason="";

   // IMPORTANT: SL distance is structure-only. ATR is deliberately NOT used here.
   double candidate=0.0;
   datetime candidate_time=0;
   if(side==SIDE_BUY)
     {
      if(g_swingLow.price>0.0 && g_swingLow.price<entry)
        {
         candidate=g_swingLow.price;
         candidate_time=g_swingLow.time;
         source="SWING_LOW";
        }
      if(g_bullFibValid && g_bullFibOrigin>0.0 && g_bullFibOrigin<entry && g_bullFibTime>=candidate_time)
        {
         candidate=g_bullFibOrigin;
         candidate_time=g_bullFibTime;
         source="BOS_IMPULSE_ORIGIN";
        }
      if(EventFresh(g_bullSweepTime,SweepMaxAgeBars) && g_bullSweepExtreme>0.0 &&
         g_bullSweepExtreme<entry && g_bullSweepTime>=candidate_time)
        {
         candidate=g_bullSweepExtreme;
         candidate_time=g_bullSweepTime;
         source="LIQUIDITY_SWEEP_LOW";
        }
     }
   else
     {
      if(g_swingHigh.price>entry)
        {
         candidate=g_swingHigh.price;
         candidate_time=g_swingHigh.time;
         source="SWING_HIGH";
        }
      if(g_bearFibValid && g_bearFibOrigin>entry && g_bearFibTime>=candidate_time)
        {
         candidate=g_bearFibOrigin;
         candidate_time=g_bearFibTime;
         source="BOS_IMPULSE_ORIGIN";
        }
      if(EventFresh(g_bearSweepTime,SweepMaxAgeBars) && g_bearSweepExtreme>entry &&
         g_bearSweepExtreme>=entry && g_bearSweepTime>=candidate_time)
        {
         candidate=g_bearSweepExtreme;
         candidate_time=g_bearSweepTime;
         source="LIQUIDITY_SWEEP_HIGH";
        }
     }

   if(candidate<=0.0)
     {
      reason="no valid structural stop; ATR fallback disabled";
      return false;
     }

   double buffer=MathMax(0.0,StructureSLBufferPoints)*_Point;
   double minimum_distance=BrokerMinimumDistance()+TickSize();
   sl=side==SIDE_BUY ? candidate-buffer : candidate+buffer;

   // Broker minimum stop distance only; this is NOT ATR based.
   if(MathAbs(entry-sl)<minimum_distance)
      sl=side==SIDE_BUY ? entry-minimum_distance : entry+minimum_distance;

   sl=side==SIDE_BUY ? NormalizePriceDown(sl) : NormalizePriceUp(sl);
   if(sl<=0.0 || (side==SIDE_BUY && sl>=entry) || (side==SIDE_SELL && sl<=entry))
     {
      reason="invalid normalized structural server SL";
      return false;
     }
   return true;
  }

bool CashStopPrice(const ENUM_SIDE side,const double entry,const double volume,const double loss_money,double &sl)
  {
   sl=0.0;
   double tick_size=TickSize();
   double tick_value=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_LOSS);
   if(tick_value<=0.0) tick_value=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE);
   if(tick_size<=0.0 || tick_value<=0.0 || volume<=0.0 || loss_money<=0.0) return false;
   double required_ticks=loss_money/(tick_value*volume);
   double distance=required_ticks*tick_size;
   sl=side==SIDE_BUY ? entry-distance : entry+distance;
   double minimum_distance=BrokerMinimumDistance()+TickSize();
   if(side==SIDE_BUY) sl=MathMin(sl,entry-minimum_distance);
   else sl=MathMax(sl,entry+minimum_distance);
   sl=side==SIDE_BUY ? NormalizePriceDown(sl) : NormalizePriceUp(sl);
   return sl>0.0 && MathIsValidNumber(sl) &&
          ((side==SIDE_BUY && sl<entry) || (side==SIDE_SELL && sl>entry));
  }

bool CashTargetPrice(const ENUM_SIDE side,const double entry,const double volume,const double target_money,double &tp)
  {
   double tick_size=TickSize();
   double tick_value=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_PROFIT);
   if(tick_size<=0.0 || tick_value<=0.0 || volume<=0.0 || target_money<=0.0) return false;
   double required_ticks=target_money/(tick_value*volume);
   double distance=required_ticks*tick_size;
   tp=side==SIDE_BUY ? entry+distance : entry-distance;
   return tp>0.0 && MathIsValidNumber(tp);
  }

bool BuildTargetPrice(const ENUM_SIDE side,const int leg_index,const double entry,const double sl,
                      const double volume,double &tp,string &reason)
  {
   tp=0.0;
   reason="";
   if(leg_index<0 || leg_index>=LegCount) { reason="invalid leg index"; return false; }
   if(TargetMode==TP_MODE_CASH)
     {
      if(!CashTargetPrice(side,entry,volume,g_legConfig[leg_index].target_money,tp))
        {
         reason="cash TP conversion unavailable";
         return false;
        }
     }
   else
     {
      double distance=MathAbs(entry-sl)*g_legConfig[leg_index].target_r;
      tp=side==SIDE_BUY ? entry+distance : entry-distance;
     }
   double minimum_distance=BrokerMinimumDistance()+TickSize();
   if(side==SIDE_BUY) tp=MathMax(tp,entry+minimum_distance);
   else tp=MathMin(tp,entry-minimum_distance);
   tp=side==SIDE_BUY ? NormalizePriceUp(tp) : NormalizePriceDown(tp);
   if(tp<=0.0 || (side==SIDE_BUY && tp<=entry) || (side==SIDE_SELL && tp>=entry))
     {
      reason="invalid normalized server TP";
      return false;
     }
   return true;
  }

double CalculateEntryVolume(const ENUM_SIDE side,const double entry,const double sl,string &reason)
  {
   reason="";
   if(UseFixedLot) return FixedLot;
   if(RiskPercent<=0.0)
     {
      reason="RiskPercent must be positive when fixed lot is off";
      return 0.0;
     }
   double one_lot_loss=ProjectedLossMoney(side,1.0,entry,sl);
   if(one_lot_loss<=0.0)
     {
      reason="unable to calculate one-lot stop risk";
      return 0.0;
     }
   double risk_budget=AccountInfoDouble(ACCOUNT_EQUITY)*RiskPercent/100.0;
   double raw_volume=risk_budget/(one_lot_loss*(double)LegCount);
   double volume=NormalizeVolumeDown(raw_volume);
   if(volume<=0.0 || !IsVolumeStepValid(volume))
     {
      reason="risk-sized volume is below broker minimum or invalid";
      return 0.0;
     }
   return volume;
  }

ENUM_ORDER_TYPE_FILLING FillingMode()
  {
   long filling=SymbolInfoInteger(_Symbol,SYMBOL_FILLING_MODE);
   if((filling & SYMBOL_FILLING_FOK)!=0) return ORDER_FILLING_FOK;
   if((filling & SYMBOL_FILLING_IOC)!=0) return ORDER_FILLING_IOC;
   return ORDER_FILLING_RETURN;
  }

bool ValidateEntryStops(const ENUM_SIDE side,const double entry,const double sl,const double tp,string &reason)
  {
   double minimum=BrokerMinimumDistance();
   if(entry<=0.0 || sl<=0.0 || tp<=0.0)
     { reason="zero entry/SL/TP"; return false; }
   if(side==SIDE_BUY)
     {
      if(sl>=entry || tp<=entry) { reason="BUY stop/target is on wrong side"; return false; }
      if(entry-sl+TickSize()*0.5<minimum || tp-entry+TickSize()*0.5<minimum)
        { reason="BUY stops violate stops/freeze level"; return false; }
     }
   else
     {
      if(sl<=entry || tp>=entry) { reason="SELL stop/target is on wrong side"; return false; }
      if(sl-entry+TickSize()*0.5<minimum || entry-tp+TickSize()*0.5<minimum)
        { reason="SELL stops violate stops/freeze level"; return false; }
     }
   return true;
  }

bool OrderCheckLeg(const ENUM_SIDE side,const int leg_index,const double volume,const double entry,
                   const double sl,const double tp,const string comment,MqlTradeCheckResult &check,string &reason)
  {
   if(!IsVolumeStepValid(volume)) { reason="invalid volume"; return false; }
   if(!UseVirtualCashSLTP && !ValidateEntryStops(side,entry,sl,tp,reason)) return false;
   MqlTradeRequest request;
   ZeroMemory(request);
   request.action=TRADE_ACTION_DEAL;
   request.magic=MagicNumber;
   request.symbol=_Symbol;
   request.volume=volume;
   request.type=side==SIDE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
   request.price=entry;
   request.sl=UseVirtualCashSLTP ? 0.0 : sl;
   request.tp=UseVirtualCashSLTP ? 0.0 : tp;
   request.deviation=MaxSlippagePoints;
   request.type_filling=FillingMode();
   request.type_time=ORDER_TIME_GTC;
   request.comment=comment;
   ZeroMemory(check);
   bool request_ok=OrderCheck(request,check);
   if(!request_ok || (check.retcode!=0 && check.retcode!=TRADE_RETCODE_DONE))
     {
      reason=StringFormat("OrderCheck leg %d failed retcode=%u comment=%s",leg_index+1,check.retcode,check.comment);
      return false;
     }
   return true;
  }


bool MarginPrecheckLeg(const ENUM_SIDE side,const double volume,const double entry,
                       double &required_margin,double &free_margin,double &margin_after,string &reason)
  {
   reason="";
   required_margin=0.0;
   free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE);
   margin_after=free_margin;

   if(!MarginPrecheckEnabled)
      return true;

   ENUM_ORDER_TYPE order_type=(side==SIDE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
   ResetLastError();
   if(!OrderCalcMargin(order_type,_Symbol,volume,entry,required_margin))
     {
      int err=GetLastError();
      reason=StringFormat("OrderCalcMargin failed err=%d",err);
      return false;
     }

   margin_after=free_margin-required_margin;

   if(DebugAuditLog)
      PrintFormat("AUDIT|%s|MARGIN_PRECHECK|side=%s|volume=%.4f|entry=%.5f|FreeMargin=%.2f|RequiredMargin=%.2f|MarginAfter=%.2f|Reserve=%.2f",
                  TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),SideText(side),volume,entry,
                  free_margin,required_margin,margin_after,MarginSafetyReserveMoney);

   if(required_margin<0.0 || !MathIsValidNumber(required_margin))
     {
      reason="invalid required margin";
      return false;
     }

   if(margin_after<MarginSafetyReserveMoney-0.01)
     {
      reason=StringFormat("NO_MARGIN free=%.2f required=%.2f after=%.2f reserve=%.2f",
                          free_margin,required_margin,margin_after,MarginSafetyReserveMoney);
      return false;
     }

   return true;
  }

bool RiskWithinSignalLimits(const double basket_risk,string &reason)
  {
   if(basket_risk<=0.0 || !MathIsValidNumber(basket_risk))
     { reason="invalid projected basket risk"; return false; }
   if(MaxRiskPerSignalMoney>0.0 && basket_risk>MaxRiskPerSignalMoney+0.01)
     {
      reason=StringFormat("basket risk %.2f exceeds money limit %.2f",basket_risk,MaxRiskPerSignalMoney);
      return false;
     }
   double equity_limit=AccountInfoDouble(ACCOUNT_EQUITY)*MaxRiskPerSignalPercent/100.0;
   if(MaxRiskPerSignalPercent>0.0 && basket_risk>equity_limit+0.01)
     {
      reason=StringFormat("basket risk %.2f exceeds %.2f%% equity limit %.2f",basket_risk,MaxRiskPerSignalPercent,equity_limit);
      return false;
     }
   return true;
  }

void PrepareNewBasket(const Candidate &candidate)
  {
   for(int i=0;i<MAX_LEGS;i++) ResetLegRuntime(i);
   g_basketId++;
   g_basketSide=candidate.side;
   g_basketStartTime=TimeCurrent();
   g_basketRealized=0.0;
   g_tp1Reached=false;
   g_projectedBasketRisk=0.0;
   g_expectedLegs=(RunThreeProfileTest ? MathMin(LegCount,ExperimentRemaining()) : LegCount);
   g_pendingEntrySide=candidate.side;
   g_pendingSignalId=candidate.signal_id;
   g_pendingSetupType=candidate.setup_type;
   g_pendingDirectionScore=candidate.direction_score;
   g_pendingStructureScore=candidate.structure_score;
   g_pendingEntryQuality=candidate.entry_quality;
   g_entryRequestTime=TimeCurrent();
   g_entryPending=true;
   g_allEntryRequestsSent=false;
   g_incompleteBasket=false;
  }

Candidate PendingCandidate()
  {
   Candidate candidate;
   candidate.side=g_pendingEntrySide;
   candidate.direction_score=g_pendingDirectionScore;
   candidate.structure_score=g_pendingStructureScore;
   candidate.combined_score=(int)MathRound(candidate.direction_score*0.65+candidate.structure_score*0.35);
   candidate.entry_quality=g_pendingEntryQuality;
   candidate.setup_type=g_pendingSetupType;
   candidate.signal_id=g_pendingSignalId;
   candidate.eligible=true;
   candidate.reject_reason="";
   return candidate;
  }

bool EveryOpenPositionHasServerProtection()
  {
   int count=0;
   for(int i=PositionsTotal()-1;i>=0;i--)
     {
      ulong ticket=PositionGetTicket(i);
      if(ticket==0) continue;
      if(PositionGetString(POSITION_SYMBOL)!=_Symbol || (ulong)PositionGetInteger(POSITION_MAGIC)!=MagicNumber) continue;
      count++;
      if(PositionGetDouble(POSITION_SL)<=0.0 || PositionGetDouble(POSITION_TP)<=0.0) return false;
     }
   return count>0;
  }

void ConfirmBasketEntry()
  {
   ReconcilePositions(false);
   int count=ActivePositionCount();
   if(count!=g_expectedLegs || (!UseVirtualCashSLTP && !EveryOpenPositionHasServerProtection()))
     {
      g_incompleteBasket=true;
      g_statusText="RECONCILE REQUIRED";
      return;
     }
   g_entryPending=false;
   g_allEntryRequestsSent=true;
   g_basketActive=true;
   g_statusText=StringFormat("%s BASKET %I64u OPEN",SideText(g_basketSide),g_basketId);
   Candidate candidate=PendingCandidate();
   AuditLifecycle("BASKET_CONFIRMED",candidate,0,0.0,0.0,"-",0.0,0.0,0.0,g_projectedBasketRisk,
                  StringFormat("positions=%d",count));
   AdvanceThreeProfileTestIfReady();
   PersistState(true);
  }

void ExecuteCandidate(const Candidate &candidate)
  {
   string execution_route=(StringFind(candidate.setup_type,"[ROUTE=FAST]")>=0 ? "FAST" : "NORMAL");
   g_v494SelectedRoute=execution_route;
   MqlTick initial_tick;
   if(!SymbolInfoTick(_Symbol,initial_tick)) return;
   string guard_reason="";
   if(!PassesEntryGuards(candidate.side,guard_reason))
     {
      Candidate rejected=candidate;
      rejected.reject_reason=guard_reason;
      rejected.eligible=false;
      LogRejectThrottled(rejected);
      return;
     }
   int request_legs=RunThreeProfileTest ? MathMin(LegCount,ExperimentRemaining()) : LegCount;
   if(V494ThirtyOrderTest)
      request_legs=MathMin(request_legs,MathMax(0,V494AcceptedOrderLimit-g_v494Accepted));
   if(request_legs<=0)
     {
      Candidate rejected=candidate;
      rejected.reject_reason="profile position quota reached";
      LogRejectThrottled(rejected);
      return;
     }
   double initial_entry=candidate.side==SIDE_BUY ? initial_tick.ask : initial_tick.bid;
   double initial_sl=0.0;
   string sl_source="CASH_SL";
   string reason="";
   double seed_volume=UseFixedLot ? FixedLot : MathMax(SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN),FixedLot);
   if(!CashStopPrice(candidate.side,initial_entry,seed_volume,g_legConfig[0].stop_loss_money,initial_sl))
     {
      Candidate rejected=candidate;
      rejected.reject_reason="cash SL conversion unavailable";
      LogRejectThrottled(rejected);
      return;
     }
   double volume=CalculateEntryVolume(candidate.side,initial_entry,initial_sl,reason);
   if(volume<=0.0 || !IsVolumeStepValid(volume))
     {
      Candidate rejected=candidate;
      rejected.reject_reason=reason=="" ? "invalid volume" : reason;
      LogRejectThrottled(rejected);
      return;
     }

   double proposed_sl[MAX_LEGS];
   double proposed_tp[MAX_LEGS];
   double proposed_risk[MAX_LEGS];
   double basket_risk=0.0;
   double total_margin=0.0;
   ENUM_ORDER_TYPE order_type=candidate.side==SIDE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
   for(int i=0;i<request_legs;i++)
     {
      if(!CashStopPrice(candidate.side,initial_entry,volume,g_legConfig[i].stop_loss_money,proposed_sl[i]))
        {
         Candidate rejected=candidate;
         rejected.reject_reason=StringFormat("leg %d cash SL conversion unavailable",i+1);
         LogRejectThrottled(rejected);
         return;
        }
      if(TargetMode==TP_MODE_CASH && UseGlobalTPSequence)
        {
         double seq_target=GlobalTargetMoneyForAcceptedOffset(i);
         if(!CashTargetPrice(candidate.side,initial_entry,volume,seq_target,proposed_tp[i]))
           {
            Candidate rejected=candidate;
            rejected.reject_reason=StringFormat("global TP%d cash conversion unavailable",GlobalTargetSlotForAcceptedOffset(i));
            LogRejectThrottled(rejected);
            return;
           }
        }
      else if(!BuildTargetPrice(candidate.side,i,initial_entry,proposed_sl[i],volume,proposed_tp[i],reason))
        {
         Candidate rejected=candidate;
         rejected.reject_reason=reason;
         LogRejectThrottled(rejected);
         return;
        }
      proposed_risk[i]=ProjectedLossMoney(candidate.side,volume,initial_entry,proposed_sl[i]);
      if(proposed_risk[i]<=0.0)
        {
         Candidate rejected=candidate;
         rejected.reject_reason="unable to calculate projected leg loss";
         LogRejectThrottled(rejected);
         return;
        }
      if(MaxLossMoneyPerLeg>0.0 && proposed_risk[i]>MaxLossMoneyPerLeg+0.01)
        {
         Candidate rejected=candidate;
         rejected.reject_reason=StringFormat("leg %d risk %.2f exceeds per-leg limit %.2f",i+1,proposed_risk[i],MaxLossMoneyPerLeg);
         LogRejectThrottled(rejected);
         return;
        }
      basket_risk+=proposed_risk[i];
      double margin=0.0;
      if(!OrderCalcMargin(order_type,_Symbol,volume,initial_entry,margin))
        {
         Candidate rejected=candidate;
         rejected.reject_reason="margin calculation failed";
         LogRejectThrottled(rejected);
         return;
        }
      total_margin+=margin;
      string comment=StringFormat("LEG=%d/%d B=%I64u R=%s",i+1,request_legs,g_basketId+1,(execution_route=="FAST" ? "F" : "N"));
      MqlTradeCheckResult check;
      if(!OrderCheckLeg(candidate.side,i,volume,initial_entry,(UseVirtualCashSLTP && !ServerCashSLBackstopEnabled) ? 0.0 : proposed_sl[i],UseVirtualCashSLTP ? 0.0 : proposed_tp[i],comment,check,reason))
        {
         Candidate rejected=candidate;
         rejected.reject_reason=reason;
         LogRejectThrottled(rejected);
         return;
        }
     }
   if(!RiskWithinSignalLimits(basket_risk,reason))
     {
      Candidate rejected=candidate;
      rejected.reject_reason=reason;
      LogRejectThrottled(rejected);
      return;
     }
   // v4.55: Do not require enough free margin for all legs at once.
   // Each leg is checked/sent individually below. If later legs cannot be opened
   // because margin is exhausted, already-open legs remain active and the
   // basket is managed as a partial basket.

   PrepareNewBasket(candidate);
   g_expectedLegs=request_legs;
   g_projectedBasketRisk=basket_risk;
   g_lastSignalBarTime=g_currentBarTime;
   g_lastSignalSide=(int)candidate.side;
   AuditLifecycle("SIGNAL",candidate,0,initial_entry,initial_sl,sl_source,0.0,volume,0.0,basket_risk,"preflight=PASS");
   PersistState(true);

   int accepted_requests=0;
   double accepted_risk=0.0;
   for(int i=0;i<request_legs;i++)
     {
      MqlTick live_tick;
      if(!SymbolInfoTick(_Symbol,live_tick))
        {
         g_legs[i].status=LEG_FAILED;
         g_incompleteBasket=true;
         break;
        }
      UpdateSpreadState(live_tick);
      if(g_tickAgeSeconds>MaxTickAgeSeconds || g_currentSpreadPoints>MaxSpreadPoints || (!RunThreeProfileTest && g_spreadShock)) // v4.73: keep MaxSpreadPoints hard guard, ignore spread-shock blocker in test
        {
         g_legs[i].status=LEG_FAILED;
         g_incompleteBasket=true;
         break;
        }
      double entry=candidate.side==SIDE_BUY ? live_tick.ask : live_tick.bid;
      double sl=0.0;
      string live_source="CASH_SL";
      if(!CashStopPrice(candidate.side,entry,volume,g_legConfig[i].stop_loss_money,sl))
        {
         reason=StringFormat("leg %d cash SL conversion unavailable",i+1);
         g_legs[i].status=LEG_FAILED;
         g_incompleteBasket=true;
         break;
        }
      double tp=0.0;
      double active_target_money=g_legConfig[i].target_money;
      int active_target_slot=i+1;
      if(TargetMode==TP_MODE_CASH && UseGlobalTPSequence)
        {
         active_target_money=GlobalTargetMoneyForAcceptedOffset(0);
         active_target_slot=GlobalTargetSlotForAcceptedOffset(0);
         if(!CashTargetPrice(candidate.side,entry,volume,active_target_money,tp))
           {
            reason=StringFormat("global TP%d cash conversion unavailable",active_target_slot);
            g_legs[i].status=LEG_FAILED;
            g_incompleteBasket=true;
            break;
           }
        }
      else if(!BuildTargetPrice(candidate.side,i,entry,sl,volume,tp,reason))
        {
         g_legs[i].status=LEG_FAILED;
         g_incompleteBasket=true;
         break;
        }
      double live_risk=ProjectedLossMoney(candidate.side,volume,entry,sl);
      double forward_projection=accepted_risk+live_risk*(double)(request_legs-i);
      if((MaxLossMoneyPerLeg>0.0 && live_risk>MaxLossMoneyPerLeg+0.01) ||
         !RiskWithinSignalLimits(forward_projection,reason))
        {
         g_legs[i].status=LEG_FAILED;
         g_incompleteBasket=true;
         break;
        }
      string comment=StringFormat("LEG=%d/%d B=%I64u R=%s",i+1,request_legs,g_basketId,(execution_route=="FAST" ? "F" : "N"));

      double required_margin=0.0;
      double free_margin=0.0;
      double margin_after=0.0;
      if(!MarginPrecheckLeg(candidate.side,volume,entry,required_margin,free_margin,margin_after,reason))
        {
         g_legs[i].status=LEG_FAILED;
         g_incompleteBasket=true;
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|MARGIN_BLOCK|basket=%I64u|leg=%d|side=%s|volume=%.4f|FreeMargin=%.2f|RequiredMargin=%.2f|MarginAfter=%.2f|reason=%s",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_basketId,i+1,SideText(candidate.side),
                        volume,free_margin,required_margin,margin_after,reason);
         if(StopBasketOnNoMargin) break;
        }

      if(g_legs[i].status==LEG_FAILED && StopBasketOnNoMargin)
         break;

      MqlTradeCheckResult check;
      if(!OrderCheckLeg(candidate.side,i,volume,entry,(UseVirtualCashSLTP && !ServerCashSLBackstopEnabled) ? 0.0 : sl,UseVirtualCashSLTP ? 0.0 : tp,comment,check,reason))
        {
         g_legs[i].status=LEG_FAILED;
         g_incompleteBasket=true;
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|ORDERCHECK_BLOCK|basket=%I64u|leg=%d|retcode=%u|FreeMargin=%.2f|RequiredMargin=%.2f|MarginAfter=%.2f|comment=%s",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_basketId,i+1,check.retcode,
                        free_margin,required_margin,margin_after,check.comment);
         break;
        }

      g_legs[i].requested_volume=volume;
      g_legs[i].sl=sl;
      g_legs[i].tp=tp;
      g_legs[i].target_money=active_target_money;
      g_legs[i].risk_money=live_risk;
      g_legs[i].status=LEG_REQUESTED;
      AuditLifecycle("ENTRY_REQUEST",candidate,i+1,entry,sl,live_source,tp,volume,0.0,live_risk,
                     StringFormat("OrderCheck=PASS,FreeMargin=%.2f,RequiredMargin=%.2f,MarginAfter=%.2f",
                                  free_margin,required_margin,margin_after));
      ResetLastError();
      bool request_ok=candidate.side==SIDE_BUY
                      ? trade.Buy(volume,_Symbol,entry,(UseVirtualCashSLTP && !ServerCashSLBackstopEnabled) ? 0.0 : sl,UseVirtualCashSLTP ? 0.0 : tp,comment)
                      : trade.Sell(volume,_Symbol,entry,(UseVirtualCashSLTP && !ServerCashSLBackstopEnabled) ? 0.0 : sl,UseVirtualCashSLTP ? 0.0 : tp,comment);
      uint retcode=trade.ResultRetcode();
      ulong result_deal=trade.ResultDeal();
      ulong result_order=trade.ResultOrder();
      double raw_result_volume=trade.ResultVolume();
      bool accepted=request_ok && IsEntryAcceptedRetcode(retcode) &&
                    (result_deal>0 || result_order>0) && raw_result_volume>0.0;
      double result_volume=(accepted ? raw_result_volume : 0.0);
      double free_after_send=AccountInfoDouble(ACCOUNT_MARGIN_FREE);
      string result_extra=StringFormat("requestOk=%d,retcode=%u,deal=%I64u,order=%I64u,FreeMarginBefore=%.2f,RequiredMargin=%.2f,FreeMarginAfter=%.2f,comment=%s",
                                       (int)request_ok,retcode,result_deal,result_order,
                                       free_margin,required_margin,free_after_send,trade.ResultComment());
      AuditLifecycle("ENTRY_RESULT",candidate,i+1,trade.ResultPrice(),sl,live_source,tp,volume,result_volume,live_risk,result_extra);
      if(!accepted)
        {
         g_legs[i].status=LEG_FAILED;
         g_incompleteBasket=true;
         if(DebugAuditLog && retcode==TRADE_RETCODE_NO_MONEY)
            PrintFormat("AUDIT|%s|NO_MONEY_DIAG|basket=%I64u|leg=%d|volume=%.4f|FreeMarginBefore=%.2f|RequiredMargin=%.2f|FreeMarginAfter=%.2f|retcode=%u|comment=%s",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_basketId,i+1,volume,
                        free_margin,required_margin,free_after_send,retcode,trade.ResultComment());
         break;
        }
      if(UseGlobalTPSequence)
        {
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|GLOBAL_TP_ACCEPTED|slot=TP%d|targetMoney=%.2f|acceptedCountBefore=%d|basket=%I64u|leg=%d",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),active_target_slot,active_target_money,g_globalTpAcceptedCount,g_basketId,i+1);
         g_globalTpAcceptedCount++;
         PersistState(true);
        }
      if(RunThreeProfileTest && g_testProfile>=1 && g_testProfile<=3)
        {
         g_profileOpened[g_testProfile-1]++;
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|TEST_POSITION_OPENED|profile=%s|opened=%d/%d|lot=%.2f|side=%s|basket=%I64u|leg=%d",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),TestProfileName(),
                        g_profileOpened[g_testProfile-1],PositionsPerProfile,result_volume,SideText(candidate.side),g_basketId,i+1);
        }
      g_legs[i].entry_route=execution_route;
      if(V494ThirtyOrderTest)
        {
         g_v494Accepted++;
         if(execution_route=="FAST") g_v494FastAccepted++; else g_v494NormalAccepted++;
         if(candidate.side==SIDE_BUY) g_v494BuyAccepted++; else g_v494SellAccepted++;
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|V514_TEST_OPEN|accepted=%d/%d|route=%s|side=%s|Dir=%d|basket=%I64u|leg=%d|lot=%.2f",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_v494Accepted,V494AcceptedOrderLimit,
                        execution_route,SideText(candidate.side),candidate.direction_score,g_basketId,i+1,result_volume);
        }
      accepted_requests++;
      if(accepted_requests==1)
        {
         // A broker-accepted first leg is a basket attempt for the hourly
         // and daily guards even if a later leg fails and is safety-closed.
         g_basketsThisHour++;
         g_basketsToday++;
         PersistState(true);
        }
      accepted_risk+=live_risk;
      g_legs[i].filled_volume=result_volume;
      if(retcode==TRADE_RETCODE_DONE_PARTIAL || result_volume<volume-SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP)*0.5)
        {
         g_legs[i].status=LEG_RECONCILE_REQUIRED;
         g_incompleteBasket=true;
         break;
        }
      ReconcilePositions(false);
     }

   g_allEntryRequestsSent=true;
   ReconcilePositions(false);
   if(g_incompleteBasket || accepted_requests!=request_legs)
     {
      g_statusText="INCOMPLETE BASKET - RECONCILE";
      if(IncompleteBasketMode==INCOMPLETE_CLOSE_ACCEPTED)
         RequestCloseAllVerified("INCOMPLETE_BASKET");
      else if(ActivePositionCount()>0)
        {
         g_entryPending=false;
         g_basketActive=true;
         g_statusText=StringFormat("PARTIAL BASKET ACTIVE %d/%d",ActivePositionCount(),request_legs);
         AuditLifecycle("PARTIAL_BASKET_KEEP",candidate,0,0.0,0.0,"-",0.0,volume,0.0,accepted_risk,
                        StringFormat("accepted=%d requested=%d; failed legs skipped",ActivePositionCount(),request_legs));
         PersistState(true);
        }
      return;
     }
   if(ActivePositionCount()==g_expectedLegs) ConfirmBasketEntry();
  }


bool SpikeEntryTimingAllows(const ENUM_SIDE side,string &reason)
  {
   reason="";
   if(!SpikeWaitEnabled) return true;

   int elapsed=(int)MathMax(0,(long)TimeCurrent()-(long)g_currentBarTime);
   if(elapsed<SpikeWaitSeconds)
     {
      reason=StringFormat("spike-wait %ds/%ds",elapsed,SpikeWaitSeconds);
      return false;
     }

   // During the early M1 window, require the flow to confirm the intended direction.
   if(elapsed<=SpikeConfirmMaxSeconds && SpikeRequireFlowConfirm)
     {
      bool flow_ok=(side==SIDE_BUY ? (g_flow==FLOW_BUY || g_flow==FLOW_STRONG_BUY)
                                  : (g_flow==FLOW_SELL || g_flow==FLOW_STRONG_SELL));
      int pred_score=(side==SIDE_BUY ? g_nextBuyScore : g_nextSellScore);
      string wanted=(side==SIDE_BUY ? "BUY" : "SELL");
      bool pred_ok=(!NextCandlePredictorEnabled || (g_nextCandleBias==wanted && pred_score>=50));
      if(!flow_ok || !pred_ok)
        {
         reason=StringFormat("spike-confirm wait elapsed=%d flow=%s pred=%s score=%d",
                             elapsed,FlowText(g_flow),g_nextCandleBias,pred_score);
         return false;
        }
     }
   return true;
  }

bool UpdateReverseSeek()
  {
   if(!ReverseSeekEnabled || !g_reverseSeekActive || HasOwnPositions() || g_entryPending) return false;
   if(g_reverseSeekStart<=0 || TimeCurrent()-g_reverseSeekStart>ReverseSeekFreshSeconds)
     {
      if(DebugAuditLog)
         PrintFormat("AUDIT|%s|REVERSE_SEEK_EXPIRED|side=%s",
                     TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),SideText(g_reverseSeekSide));
      g_reverseSeekActive=false;
      return false;
     }

   MqlTick tick;
   if(!SymbolInfoTick(_Symbol,tick)) return false;
   double px=(g_reverseSeekSide==SIDE_BUY ? tick.ask : tick.bid);

   if(g_reverseSeekExtreme<=0.0) g_reverseSeekExtreme=px;
   if(g_reverseSeekSide==SIDE_BUY)
      g_reverseSeekExtreme=MathMin(g_reverseSeekExtreme,px); // track the falling low after SELL
   else
      g_reverseSeekExtreme=MathMax(g_reverseSeekExtreme,px); // track the rising high after BUY

   double rebound_points=(g_reverseSeekSide==SIDE_BUY
                          ? (px-g_reverseSeekExtreme)/PointSize()
                          : (g_reverseSeekExtreme-px)/PointSize());

   int pred_score=(g_reverseSeekSide==SIDE_BUY ? g_nextBuyScore : g_nextSellScore);
   string wanted=(g_reverseSeekSide==SIDE_BUY ? "BUY" : "SELL");
   bool pred_ok=(g_nextCandleBias==wanted && pred_score>=ReverseSeekMinPredictor);
   bool flow_ok=(g_reverseSeekSide==SIDE_BUY ? (g_flow==FLOW_BUY || g_flow==FLOW_STRONG_BUY)
                                             : (g_flow==FLOW_SELL || g_flow==FLOW_STRONG_SELL));
   if(!ReverseSeekRequireFlow) flow_ok=true;

   if(rebound_points<ReverseSeekReboundPoints || !pred_ok || !flow_ok)
     {
      g_statusText=StringFormat("REVERSE SEEK %s reb=%.0f/%0.f pred=%d flow=%s",
                                wanted,rebound_points,ReverseSeekReboundPoints,pred_score,FlowText(g_flow));
      return false;
     }

   Candidate c=BuildCandidate(g_reverseSeekSide);
   if(!c.eligible)
     {
      if(DebugAuditLog)
         PrintFormat("AUDIT|%s|REVERSE_SEEK_READY_BUT_REJECTED|side=%s|rebound=%.1f|pred=%d|reason=%s",
                     TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),wanted,rebound_points,pred_score,c.reject_reason);
      return false;
     }

   if(DebugAuditLog)
      PrintFormat("AUDIT|%s|REVERSE_SEEK_TRIGGER|side=%s|extreme=%.5f|price=%.5f|rebound=%.1f|pred=%d|flow=%s",
                  TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),wanted,g_reverseSeekExtreme,px,
                  rebound_points,pred_score,FlowText(g_flow));
   g_reverseSeekActive=false;
   ExecuteCandidate(c);
   return true;
  }


bool V494FastRouteEligible(const Candidate &candidate,string &reason)
  {
   reason="";
   if(!V494RouterEnabled) return false;
   if(candidate.direction_score<V494FastMinDirection)
     {
      reason=StringFormat("Dir %d < %d",candidate.direction_score,V494FastMinDirection);
      return false;
     }

   double ratio=(candidate.side==SIDE_BUY ? g_buyTickRatio : g_sellTickRatio);
   double velocity=(candidate.side==SIDE_BUY ? g_bidVelocityPoints : -g_bidVelocityPoints);
   bool flow_ok=(candidate.side==SIDE_BUY ? (g_flow==FLOW_BUY || g_flow==FLOW_STRONG_BUY)
                                           : (g_flow==FLOW_SELL || g_flow==FLOW_STRONG_SELL));
   double disp_atr=CurrentLiveDisplacementATR();
   bool opp_pred=(candidate.side==SIDE_BUY ? (g_nextCandleBias=="SELL" && g_nextSellScore>=V494FastOppPredBlock)
                                          : (g_nextCandleBias=="BUY"  && g_nextBuyScore>=V494FastOppPredBlock));

   // FAST is intentionally allowed only BEFORE the move is stretched.  A high
   // Direction score after a large M1 impulse is routed back to NORMAL so the
   // exhaustion/retrace logic can wait for a pullback instead of chasing.
   if(!flow_ok) { reason="flow not aligned"; return false; }
   if(ratio<V494FastMinTickRatio) { reason=StringFormat("ratio %.2f < %.2f",ratio,V494FastMinTickRatio); return false; }
   if(velocity<V494FastMinVelocityPoints) { reason=StringFormat("velocity %.1f < %.1f",velocity,V494FastMinVelocityPoints); return false; }
   if(disp_atr>V494FastMaxDispATR) { reason=StringFormat("extended DispATR %.2f > %.2f",disp_atr,V494FastMaxDispATR); return false; }
   if(opp_pred) { reason="strong opposite predictor"; return false; }

   reason=StringFormat("Dir=%d ratio=%.2f vel=%.1f DispATR=%.2f flow=%s",
                       candidate.direction_score,ratio,velocity,disp_atr,FlowText(g_flow));
   return true;
  }

string V494RouteFromPositionHistory(const ulong position_id)
  {
   if(position_id==0) return "UNKNOWN";
   if(!HistorySelect(0,TimeCurrent())) return "UNKNOWN";
   int total=HistoryDealsTotal();
   for(int i=total-1;i>=0;i--)
     {
      ulong deal=HistoryDealGetTicket(i);
      if(deal==0) continue;
      if((ulong)HistoryDealGetInteger(deal,DEAL_POSITION_ID)!=position_id) continue;
      ENUM_DEAL_ENTRY de=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal,DEAL_ENTRY);
      if(de!=DEAL_ENTRY_IN && de!=DEAL_ENTRY_INOUT) continue;
      string c=HistoryDealGetString(deal,DEAL_COMMENT);
      if(StringFind(c,"R=F")>=0) return "FAST";
      if(StringFind(c,"R=N")>=0) return "NORMAL";
     }
   return "UNKNOWN";
  }

void V494PrintSummary(const bool force=false)
  {
   if(!V494ThirtyOrderTest) return;
   if(!force && g_v494Closed<V494AcceptedOrderLimit) return;
   if(g_v494SummaryPrinted && !force) return;
   g_v494SummaryPrinted=true;
   double total_net=g_v494FastNet+g_v494NormalNet;
   int wins=g_v494FastWins+g_v494NormalWins;
   int losses=g_v494FastLosses+g_v494NormalLosses;
   double avg=(g_v494Closed>0 ? total_net/g_v494Closed : 0.0);
   double fast_avg=(g_v494FastClosed>0 ? g_v494FastNet/g_v494FastClosed : 0.0);
   double normal_avg=(g_v494NormalClosed>0 ? g_v494NormalNet/g_v494NormalClosed : 0.0);
   PrintFormat("AUDIT|%s|V514_TEST_SUMMARY|accepted=%d|closed=%d|wins=%d|losses=%d|net=%.2f|avg=%.2f|BUY=%d|SELL=%d|FAST_acc=%d|FAST_closed=%d|FAST_W=%d|FAST_L=%d|FAST_net=%.2f|FAST_avg=%.2f|FAST_MFEavg=%.2f|FAST_MAEavg=%.2f|NORMAL_acc=%d|NORMAL_closed=%d|NORMAL_W=%d|NORMAL_L=%d|NORMAL_net=%.2f|NORMAL_avg=%.2f|NORMAL_MFEavg=%.2f|NORMAL_MAEavg=%.2f|TP=%d|SL=%d|PROFIT_LOCK=%d|WRONG_WAY=%d",
               TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_v494Accepted,g_v494Closed,wins,losses,total_net,avg,
               g_v494BuyAccepted,g_v494SellAccepted,
               g_v494FastAccepted,g_v494FastClosed,g_v494FastWins,g_v494FastLosses,g_v494FastNet,fast_avg,
               g_v494FastClosed>0 ? g_v494FastMFE/g_v494FastClosed : 0.0,
               g_v494FastClosed>0 ? g_v494FastMAE/g_v494FastClosed : 0.0,
               g_v494NormalAccepted,g_v494NormalClosed,g_v494NormalWins,g_v494NormalLosses,g_v494NormalNet,normal_avg,
               g_v494NormalClosed>0 ? g_v494NormalMFE/g_v494NormalClosed : 0.0,
               g_v494NormalClosed>0 ? g_v494NormalMAE/g_v494NormalClosed : 0.0,
               g_v494ExitTP,g_v494ExitSL,g_v494ExitProfitLock,g_v494ExitWrongWay);
  }

bool StrongOppositeReversal(const ENUM_SIDE proposed_side,string &reason)
  {
   reason="";
   if(!ReversalOverrideEnabled) return false;

   ENUM_SIDE opp=(proposed_side==SIDE_BUY ? SIDE_SELL : SIDE_BUY);

   int opp_pred=(opp==SIDE_BUY ? g_nextBuyScore : g_nextSellScore);
   double opp_ratio=(opp==SIDE_BUY ? g_buyTickRatio : g_sellTickRatio);
   double opp_vel=(opp==SIDE_BUY ? g_bidVelocityPoints : -g_bidVelocityPoints);

   bool pred_ok=(g_nextCandleBias==(opp==SIDE_BUY ? "BUY" : "SELL") &&
                 opp_pred>=ReversalOppPredMin);

   bool tick_ok=(opp_ratio>=ReversalOppTickRatioMin);

   bool vel_ok=(opp_vel>=ReversalVelocityPointsMin);

   bool flow_ok=(opp==SIDE_BUY ? (g_flow==FLOW_BUY || g_flow==FLOW_STRONG_BUY)
                               : (g_flow==FLOW_SELL || g_flow==FLOW_STRONG_SELL));
   if(!ReversalRequireFlow) flow_ok=true;

   bool candle_reversal=false;
   // For BUY reversal: bullish closed body and/or lower-wick rejection.
   // For SELL reversal: bearish closed body and/or upper-wick rejection.
   double prev_open=iOpen(_Symbol,PERIOD_M1,1);
   double prev_close=iClose(_Symbol,PERIOD_M1,1);
   bool prev_bull=(prev_close>prev_open);
   bool prev_bear=(prev_close<prev_open);

   if(opp==SIDE_BUY)
      candle_reversal=((prev_bull && g_prevBodyRatio>=ReversalBodyRatioMin) ||
                       g_prevLowerWickRatio>=ReversalWickRatioMin);
   else
      candle_reversal=((prev_bear && g_prevBodyRatio>=ReversalBodyRatioMin) ||
                       g_prevUpperWickRatio>=ReversalWickRatioMin);

   // Strong opposite reversal requires predictor + candle rejection,
   // plus either live tick ratio or velocity, and optional flow.
   bool confirmed=(pred_ok && candle_reversal && (tick_ok || vel_ok) && flow_ok);

   if(confirmed)
     {
      reason=StringFormat("opposite reversal=%s pred=%d ratio=%.2f vel=%.1f body=%.2f uw=%.2f lw=%.2f flow=%s",
                          SideText(opp),opp_pred,opp_ratio,opp_vel,g_prevBodyRatio,
                          g_prevUpperWickRatio,g_prevLowerWickRatio,FlowText(g_flow));
      return true;
     }

   return false;
  }


// v4.97: copied observer logic from v4.95, but SHADOW ONLY.
// Its score is recorded for later comparison and cannot alter FAST/NORMAL, timing, or execution.
int V497AIShadowScore(const Candidate &candidate,string &state,string &detail)
  {
   state="NEUTRAL";
   detail="";
   if(!V497AIShadowEnabled) return 50;

   int score=50;
   double ratio=(candidate.side==SIDE_BUY ? g_buyTickRatio : g_sellTickRatio);
   double opp_ratio=(candidate.side==SIDE_BUY ? g_sellTickRatio : g_buyTickRatio);
   double velocity=(candidate.side==SIDE_BUY ? g_bidVelocityPoints : -g_bidVelocityPoints);
   double signed_accel=(candidate.side==SIDE_BUY ? g_accelerationPoints : -g_accelerationPoints);
   double disp_atr=CurrentLiveDisplacementATR();
   bool flow_ok=(candidate.side==SIDE_BUY ? (g_flow==FLOW_BUY || g_flow==FLOW_STRONG_BUY)
                                           : (g_flow==FLOW_SELL || g_flow==FLOW_STRONG_SELL));
   bool strong_flow=(candidate.side==SIDE_BUY ? g_flow==FLOW_STRONG_BUY : g_flow==FLOW_STRONG_SELL);
   int pred=(candidate.side==SIDE_BUY ? g_nextBuyScore : g_nextSellScore);
   int opp_pred=(candidate.side==SIDE_BUY ? g_nextSellScore : g_nextBuyScore);
   string wanted=(candidate.side==SIDE_BUY ? "BUY" : "SELL");
   string opposite=(candidate.side==SIDE_BUY ? "SELL" : "BUY");

   if(candidate.direction_score>=93) score+=12;
   else if(candidate.direction_score>=85) score+=8;
   else if(candidate.direction_score>=80) score+=3;

   if(ratio>=0.80) score+=10;
   else if(ratio>=0.68) score+=6;
   else if(ratio>=0.60) score+=2;
   if(opp_ratio>=0.65) score-=8;

   if(velocity>=120.0) score+=8;
   else if(velocity>=50.0) score+=5;
   else if(velocity>=20.0) score+=2;
   else if(velocity<0.0) score-=12;

   if(signed_accel>=80.0) score+=4;
   else if(signed_accel<=-100.0) score-=7;

   if(strong_flow) score+=9;
   else if(flow_ok) score+=5;
   else score-=10;

   if(g_nextCandleBias==wanted && pred>=65) score+=8;
   if(g_nextCandleBias==opposite && opp_pred>=55) score-=12;
   if(g_nextCandleBias==opposite && opp_pred>=70) score-=8;

   bool structure_same=(candidate.side==SIDE_BUY ? g_structure==STRUCTURE_BULLISH : g_structure==STRUCTURE_BEARISH);
   bool structure_opp=(candidate.side==SIDE_BUY ? g_structure==STRUCTURE_BEARISH : g_structure==STRUCTURE_BULLISH);
   if(structure_same) score+=6;
   if(structure_opp) score-=8;
   if(g_structure==STRUCTURE_RANGE) score-=2;

   bool dmi_same=(candidate.side==SIDE_BUY ? g_plusDI10>g_minusDI10 : g_minusDI10>g_plusDI10);
   bool roc_same=(candidate.side==SIDE_BUY ? g_roc9>0.0 : g_roc9<0.0);
   if(dmi_same) score+=4; else score-=5;
   if(roc_same) score+=4; else score-=5;

   // Exhaustion penalties: a very clean one-sided reading can be the END of an impulse.
   if(disp_atr>0.18) score-=12;
   else if(disp_atr>0.12) score-=5;
   if(ratio>=0.95 && velocity<90.0) score-=10; // saturation without enough fresh speed
   if(candidate.side==SIDE_BUY && g_prevUpperWickRatio>=0.45) score-=8;
   if(candidate.side==SIDE_SELL && g_prevLowerWickRatio>=0.45) score-=8;
   if(g_safeRegime=="REVERSAL") score-=6;
   if(g_safeRegime=="SPIKE") score-=8;

   score=MathMax(0,MathMin(100,score));
   if(score>=80) state="CONTINUE_STRONG";
   else if(score>=65) state="CONTINUE";
   else if(score>=45) state="WAIT";
   else state="REVERSAL_RISK";

   detail=StringFormat("score=%d state=%s Dir=%d ratio=%.2f vel=%.1f accel=%.1f DispATR=%.2f flow=%s pred=%s/%d oppPred=%d struct=%s regime=%s",
                       score,state,candidate.direction_score,ratio,velocity,signed_accel,disp_atr,FlowText(g_flow),
                       g_nextCandleBias,pred,opp_pred,StructureText(g_structure),g_safeRegime);
   g_v497LastAIScore=score;
   g_v497LastAIState=state;
   if(state=="CONTINUE_STRONG") g_v497LastAIWould="FAST_CANDIDATE";
   else if(state=="CONTINUE") g_v497LastAIWould="ALLOW";
   else if(state=="WAIT") g_v497LastAIWould="WAIT";
   else g_v497LastAIWould="BLOCK";
   return score;
  }


void EvaluateNewEntry()
  {
   Candidate buy=BuildCandidate(SIDE_BUY);
   Candidate sell=BuildCandidate(SIDE_SELL);
   g_buyDirectionScore=buy.direction_score;
   g_sellDirectionScore=sell.direction_score;
   g_buyStructureScore=buy.structure_score;
   g_sellStructureScore=sell.structure_score;
   g_buyEntryQuality=buy.entry_quality;
   g_sellEntryQuality=sell.entry_quality;
   g_buyCombinedScore=buy.combined_score;
   g_sellCombinedScore=sell.combined_score;

   if(V494ThirtyOrderTest && V494StopNewEntriesAtLimit && g_v494Accepted>=V494AcceptedOrderLimit)
     {
      g_statusText=StringFormat("V5.14 LOG-FIX TEST LIMIT %d/%d - WAIT CLOSE",g_v494Accepted,V494AcceptedOrderLimit);
      V494PrintSummary(false);
      return;
     }

   Candidate selected;
   bool have_selected=false;
   if(buy.eligible && sell.eligible)
     {
      selected=(buy.combined_score>sell.combined_score ||
                (buy.combined_score==sell.combined_score && buy.entry_quality>=sell.entry_quality)) ? buy : sell;
      have_selected=true;
     }
   else if(buy.eligible) { selected=buy; have_selected=true; }
   else if(sell.eligible) { selected=sell; have_selected=true; }

   if(have_selected)
     {
      // SMART FIRST-MOVE gate: use the 5 requested evidence groups before
      // deciding FAST/NORMAL route. It re-evaluates every tick, so WAIT is
      // temporary and does not consume/duplicate the signal.
      string sfm_reason="",sfm_tag="";
      if(!SmartFirstMoveAllows(selected.side,sfm_reason,sfm_tag))
        {
         g_statusText=StringFormat("%s SMART WAIT: %s",SideText(selected.side),sfm_reason);
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|V514_ENTRY_TIMING_WAIT|side=%s|signal=%s|Dir=%d|%s|reason=%s",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),SideText(selected.side),
                        selected.signal_id,selected.direction_score,sfm_tag,sfm_reason);
         return;
        }
      selected.setup_type+="["+sfm_tag+"]";
      if(DebugAuditLog)
         PrintFormat("AUDIT|%s|V514_ENTRY_TIMING_ALLOW|side=%s|signal=%s|Dir=%d|%s",
                     TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),SideText(selected.side),
                     selected.signal_id,selected.direction_score,sfm_tag);

      string reversal_reason="";
      if(ReversalBlockP1Direct && StrongOppositeReversal(selected.side,reversal_reason))
        {
         g_statusText=StringFormat("%s BLOCKED: %s",SideText(selected.side),reversal_reason);
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|REVERSAL_OVERRIDE_BLOCK|side=%s|signal=%s|Dir=%d|setup=%s|reason=%s",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),SideText(selected.side),
                        selected.signal_id,selected.direction_score,selected.setup_type,reversal_reason);
         return;
        }

      // v4.97 SHADOW ONLY: observe the exact candidate that v4.94 would trade.
      // IMPORTANT: no return/block/wait/route decision is based on this score.
      string ai_shadow_state="NEUTRAL";
      string ai_shadow_detail="";
      int ai_shadow_score=V497AIShadowScore(selected,ai_shadow_state,ai_shadow_detail);
      if(V497AIShadowEnabled && V497AIShadowLogEveryDecision && DebugAuditLog)
         PrintFormat("AUDIT|%s|V497_AI_SHADOW|side=%s|signal=%s|would=%s|%s",
                     TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),SideText(selected.side),
                     selected.signal_id,g_v497LastAIWould,ai_shadow_detail);
      if(V497AIShadowEnabled && V497AIShadowTagSetup)
         selected.setup_type+=StringFormat("[AI_SHADOW=%d,%s,WOULD_%s]",ai_shadow_score,ai_shadow_state,g_v497LastAIWould);

      string fast_reason="";
      bool fast=V494FastRouteEligible(selected,fast_reason);
      if(fast)
        {
         g_v494SelectedRoute="FAST";
         selected.setup_type+="[ROUTE=FAST]";
         g_statusText=StringFormat("%s FAST READY",SideText(selected.side));
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|V494_ROUTE|route=FAST|side=%s|Dir=%d|reason=%s",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),SideText(selected.side),selected.direction_score,fast_reason);
         ExecuteCandidate(selected);
         return;
        }

      // NORMAL route retains v4.93 safety/timing chain.  This is also the
      // route used when a strong signal is already extended; it must cool or
      // retrace and then resume before entry.
      g_v494SelectedRoute="NORMAL";
      selected.setup_type+="[ROUTE=NORMAL]";
      if(DebugAuditLog && V494RouterEnabled)
         PrintFormat("AUDIT|%s|V494_ROUTE|route=NORMAL|side=%s|Dir=%d|fastReject=%s",
                     TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),SideText(selected.side),selected.direction_score,fast_reason);

      string timing_reason="";
      if(!SpikeEntryTimingAllows(selected.side,timing_reason))
        {
         g_statusText=StringFormat("%s NORMAL WAIT: %s",SideText(selected.side),timing_reason);
         return;
        }
      string safe_reason="";
      if(!SafeTimingAllows(selected.side,safe_reason))
        {
         g_statusText=StringFormat("%s SAFE WAIT: %s",SideText(selected.side),safe_reason);
         return;
        }
      string exhaustion_reason="";
      if(!ExhaustionRetraceAllows(selected.side,exhaustion_reason))
        {
         g_statusText=StringFormat("%s PULLBACK WAIT: %s",SideText(selected.side),exhaustion_reason);
         return;
        }

      g_statusText=SideText(selected.side)+" NORMAL READY";
      ExecuteCandidate(selected);
      return;
     }

   Candidate better=buy.combined_score>sell.combined_score ? buy : sell;
   g_statusText=g_riskHalt ? "RISK HALT: "+HaltReasonText() : "WAIT";
   if(better.combined_score>=MathMax(20,MinSetupScore-15)) LogRejectThrottled(better);
  }

void HandleEntryPending()
  {
   ReconcilePositions(false);
   int count=ActivePositionCount();
   if(g_incompleteBasket)
     {
      if(IncompleteBasketMode==INCOMPLETE_CLOSE_ACCEPTED)
        {
         if(count>0) RequestCloseAllVerified("INCOMPLETE_BASKET");
         else
           {
            g_entryPending=false;
            bool had_accepted_position=g_basketActive;
            if(!had_accepted_position)
              {
               for(int i=0;i<MAX_LEGS;i++)
                  if(g_legs[i].position_id>0) { had_accepted_position=true; break; }
              }
            if(had_accepted_position)
              {
               g_basketActive=true;
               FinalizeBasket();
              }
            else
              {
               g_basketActive=false;
               g_lastTradeCloseTime=TimeCurrent();
              }
           }
        }
      else if(count>0)
        {
         g_entryPending=false;
         g_basketActive=true;
        }
      return;
     }
   if(g_allEntryRequestsSent && count==g_expectedLegs)
     {
      ConfirmBasketEntry();
      return;
     }
   if(TimeCurrent()-g_entryRequestTime>=EntryReconcileSeconds)
     {
      g_incompleteBasket=true;
      g_statusText="ENTRY RECONCILE TIMEOUT";
      if(IncompleteBasketMode==INCOMPLETE_CLOSE_ACCEPTED && count>0)
         RequestCloseAllVerified("ENTRY_RECONCILE_TIMEOUT");
      else if(count==0)
        {
         g_entryPending=false;
         g_lastTradeCloseTime=TimeCurrent();
        }
     }
  }

//====================================================================
// VERIFIED MODIFY/CLOSE, PROFIT LOCK AND TP1 BREAK-EVEN
//====================================================================
bool ModifyPositionVerified(const ulong ticket,const double new_sl,const double new_tp,const string reason)
  {
   if(ticket==0 || new_sl<=0.0 || !PositionSelectByTicket(ticket)) return false;
   ResetLastError();
   bool request_ok=trade.PositionModify(ticket,new_sl,new_tp);
   uint retcode=trade.ResultRetcode();
   if(!request_ok || !IsRequestSuccessRetcode(retcode))
     {
      if(DebugAuditLog)
         PrintFormat("AUDIT|%s|MODIFY_REJECT|ticket=%I64u|reason=%s|requestOk=%d|retcode=%u|comment=%s",
                     TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),ticket,reason,(int)request_ok,retcode,trade.ResultComment());
      return false;
     }
   if(!PositionSelectByTicket(ticket)) return false;
   double actual_sl=PositionGetDouble(POSITION_SL);
   double actual_tp=PositionGetDouble(POSITION_TP);
   bool confirmed=MathAbs(actual_sl-new_sl)<=TickSize()*0.51 && MathAbs(actual_tp-new_tp)<=TickSize()*0.51;
   if(DebugAuditLog)
      PrintFormat("AUDIT|%s|MODIFY_%s|ticket=%I64u|reason=%s|SL=%.5f|TP=%.5f|retcode=%u",
                  TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),confirmed ? "CONFIRMED" : "UNCONFIRMED",
                  ticket,reason,actual_sl,actual_tp,retcode);
   return confirmed;
  }

bool ClosePositionVerified(const ulong ticket,const string reason)
  {
   if(ticket==0 || !PositionSelectByTicket(ticket)) return false;
   ResetLastError();
   bool request_ok=trade.PositionClose(ticket,MaxSlippagePoints);
   uint retcode=trade.ResultRetcode();
   bool accepted=request_ok && IsRequestSuccessRetcode(retcode);
   int index=FindLegByTicket(ticket);
   if(accepted && index>=0)
     {
      g_legs[index].status=LEG_CLOSE_PENDING;
      g_legs[index].pending_exit_reason=reason;
     }
   if(DebugAuditLog)
      PrintFormat("AUDIT|%s|CLOSE_REQUEST|ticket=%I64u|reason=%s|requestOk=%d|retcode=%u|deal=%I64u|comment=%s",
                  TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),ticket,reason,(int)request_ok,
                  retcode,trade.ResultDeal(),trade.ResultComment());
   return accepted;
  }

void RequestCloseAllVerified(const string reason)
  {
   ulong tickets[];
   ArrayResize(tickets,0);
   for(int i=PositionsTotal()-1;i>=0;i--)
     {
      ulong ticket=PositionGetTicket(i);
      if(ticket==0) continue;
      if(PositionGetString(POSITION_SYMBOL)!=_Symbol || (ulong)PositionGetInteger(POSITION_MAGIC)!=MagicNumber) continue;
      int size=ArraySize(tickets);
      ArrayResize(tickets,size+1);
      tickets[size]=ticket;
     }
   for(int i=0;i<ArraySize(tickets);i++) ClosePositionVerified(tickets[i],reason);
  }

double BreakEvenCostDistance(const int index)
  {
   if(!BEIncludeCosts || index<0 || index>=MAX_LEGS) return 0.0;
   double volume=g_legs[index].filled_volume;
   double tick_value=SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE_PROFIT);
   double tick_size=TickSize();
   double money=MathAbs(MathMin(0.0,g_legs[index].entry_costs))+BECommissionBufferMoneyLot*volume;
   double distance=0.0;
   if(volume>0.0 && tick_value>0.0) distance=money/(tick_value*volume)*tick_size;
   distance+=BESpreadBufferPoints*PointSize();
   return MathMax(0.0,distance);
  }

void ApplyBreakEvenToRemaining()
  {
   if(!MoveRemainingToBEAfterTP1 || !g_tp1Reached) return;
   MqlTick tick;
   if(!SymbolInfoTick(_Symbol,tick)) return;
   double minimum=BrokerMinimumDistance()+TickSize();
   for(int i=1;i<MAX_LEGS;i++)
     {
      if(g_legs[i].status!=LEG_OPEN || g_legs[i].ticket==0 || g_legs[i].break_even_applied) continue;
      if(!PositionSelectByTicket(g_legs[i].ticket)) continue;
      ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      double entry=PositionGetDouble(POSITION_PRICE_OPEN);
      double current_sl=PositionGetDouble(POSITION_SL);
      double current_tp=PositionGetDouble(POSITION_TP);
      double costs=BreakEvenCostDistance(i);
      double new_sl=0.0;
      if(type==POSITION_TYPE_BUY)
        {
         new_sl=NormalizePriceUp(entry+costs);
         if(new_sl<=current_sl+TickSize()*0.5 || new_sl>tick.bid-minimum) continue;
        }
      else
        {
         new_sl=NormalizePriceDown(entry-costs);
         if((current_sl>0.0 && new_sl>=current_sl-TickSize()*0.5) || new_sl<tick.ask+minimum) continue;
        }
      if(ModifyPositionVerified(g_legs[i].ticket,new_sl,current_tp,"TP1_BREAK_EVEN"))
        {
         g_legs[i].sl=new_sl;
         g_legs[i].break_even_applied=true;
        }
     }
  }

void EnsurePartialCashTarget(const int index)
  {
   if(TargetMode!=TP_MODE_CASH || index<0 || index>=MAX_LEGS || g_legs[index].status!=LEG_OPEN) return;
   if(!PositionSelectByTicket(g_legs[index].ticket)) return;
   double volume=PositionGetDouble(POSITION_VOLUME);
   double entry=PositionGetDouble(POSITION_PRICE_OPEN);
   double sl=PositionGetDouble(POSITION_SL);
   ENUM_SIDE side=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY ? SIDE_BUY : SIDE_SELL;
   double desired_tp=0.0;
   string reason="";
   if(!BuildTargetPrice(side,index,entry,sl,volume,desired_tp,reason)) return;
   double current_tp=PositionGetDouble(POSITION_TP);
   if(MathAbs(current_tp-desired_tp)<=TickSize()*0.51) return;
   if(ModifyPositionVerified(g_legs[index].ticket,sl,desired_tp,"PARTIAL_FILL_CASH_TP"))
      g_legs[index].tp=desired_tp;
  }

void ManageOpenPositions()
  {
   if(CloseBasketAfterTwoM1Bars && g_basketStartTime>0)
     {
      int bars_since_entry=iBarShift(_Symbol,PERIOD_M1,g_basketStartTime,false);
      if(bars_since_entry>=MaxBasketM1Bars)
        {
         for(int k=0;k<MAX_LEGS;k++)
           {
            if(g_legs[k].status==LEG_OPEN && g_legs[k].ticket>0 && PositionSelectByTicket(g_legs[k].ticket))
               ClosePositionVerified(g_legs[k].ticket,"TWO_BAR_TIMEOUT");
           }
         g_statusText=StringFormat("TWO-BAR EXIT BASKET %I64u",g_basketId);
         return;
        }
     }
   if(g_tp1Reached && !UseVirtualCashSLTP) ApplyBreakEvenToRemaining();
   for(int i=0;i<MAX_LEGS;i++)
     {
      if(g_legs[i].status!=LEG_OPEN || g_legs[i].ticket==0) continue;
      if(!PositionSelectByTicket(g_legs[i].ticket)) continue;
      double server_sl=PositionGetDouble(POSITION_SL);
      if(!UseVirtualCashSLTP && server_sl<=0.0)
        {
         ClosePositionVerified(g_legs[i].ticket,"SERVER_SL_MISSING");
         continue;
        }
      double current=PositionNetProfitBySelection(i);

      // LOG_FIX: update peak/MFE/MAE before any exit check so the closing tick is not lost.
      if(g_legs[i].peak_profit<=-DBL_MAX/2.0 || current>g_legs[i].peak_profit) g_legs[i].peak_profit=current;
      if(g_legs[i].mfe<=-DBL_MAX/2.0 || current>g_legs[i].mfe) g_legs[i].mfe=current;
      if(g_legs[i].mae>=DBL_MAX/2.0 || current<g_legs[i].mae) g_legs[i].mae=current;

      // LOG_FIX: restore broker-side hard cash SL if it is ever missing.
      if(ServerCashSLBackstopEnabled && g_legs[i].sl>0.0 && server_sl<=0.0)
        {
         double current_tp=PositionGetDouble(POSITION_TP);
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|SERVER_SL_BACKSTOP_RESTORE|basket=%I64u|leg=%d|ticket=%I64u|SL=%.5f",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_basketId,i+1,g_legs[i].ticket,g_legs[i].sl);
         ModifyPositionVerified(g_legs[i].ticket,g_legs[i].sl,current_tp,"SERVER_SL_BACKSTOP_RESTORE");
        }

      datetime position_time=(datetime)PositionGetInteger(POSITION_TIME);
      int held_seconds=(int)MathMax(0,(long)(TimeCurrent()-position_time));
      bool minimum_hold_active=(UseMinimumHoldTime && MinimumHoldSeconds>0 && held_seconds<MinimumHoldSeconds);

      // v4.93 emergency wrong-way exit:
      // If a fresh trade is already losing materially and live flow/velocity turns hard
      // against the position, cut it before the full virtual cash SL is reached.
      if(EmergencyWrongWayEnabled &&
         held_seconds<=EmergencyWrongWaySeconds &&
         current<=-MathAbs(EmergencyWrongWayLossMoney) &&
         EmergencyWrongWayConfirmed(g_basketSide))
        {
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|EMERGENCY_WRONG_WAY_EXIT|basket=%I64u|leg=%d|ticket=%I64u|side=%s|held=%ds|Current=%.2f|BuyR=%.2f|SellR=%.2f|BidVel=%.1f|Flow=%s",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_basketId,i+1,g_legs[i].ticket,
                        SideText(g_basketSide),held_seconds,current,g_buyTickRatio,g_sellTickRatio,
                        g_bidVelocityPoints,FlowText(g_flow));
         ClosePositionVerified(g_legs[i].ticket,"EMERGENCY_WRONG_WAY_EXIT");
         continue;
        }

      // CASH TP always stays active, even during the minimum 1-minute hold.
      // Loss exits and profit-giveback exits are deferred until the hold expires.
      if(UseVirtualCashSLTP)
        {
         if(g_legs[i].target_money>0.0 && current>=g_legs[i].target_money)
           {
            ClosePositionVerified(g_legs[i].ticket,"VIRTUAL_CASH_TP");
            if(i==0) g_tp1Reached=true;
            continue;
           }
         if(!minimum_hold_active && g_legConfig[i].stop_loss_money>0.0 && current<=-g_legConfig[i].stop_loss_money)
           {
            ClosePositionVerified(g_legs[i].ticket,"VIRTUAL_CASH_SL");
            continue;
           }
        }
      // v4.86 EARLY WRONG-WAY EXIT:
      // If the just-opened trade immediately goes against us AND live micro-flow/predictor
      // confirms the opposite direction, cut it before the full virtual cash SL.
      if(EarlyWrongWayExitEnabled &&
         held_seconds<=EarlyWrongWaySeconds &&
         current<=-MathAbs(EarlyWrongWayMinLossMoney) &&
         OppositeMicroReversalConfirmed(g_basketSide,EarlyWrongWayOppPredScore,EarlyWrongWayOppTickRatio,EarlyWrongWayRequireFlow))
        {
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|EARLY_WRONG_WAY_EXIT|basket=%I64u|leg=%d|ticket=%I64u|side=%s|held=%ds|Current=%.2f|MFE=%.2f|MAE=%.2f|Pred=%s B=%d S=%d|Flow=%s|BuyR=%.2f|SellR=%.2f",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_basketId,i+1,g_legs[i].ticket,
                        SideText(g_basketSide),held_seconds,current,g_legs[i].mfe,g_legs[i].mae,
                        g_nextCandleBias,g_nextBuyScore,g_nextSellScore,FlowText(g_flow),g_buyTickRatio,g_sellTickRatio);
         ClosePositionVerified(g_legs[i].ticket,"EARLY_WRONG_WAY_EXIT");
         continue;
        }

      // v4.86 MICRO PROFIT LOCK:
      // After a small positive excursion, only protect the small profit when live flow
      // confirms an opposite reversal. This avoids closing merely because profit fluctuates.
      if(MicroProfitLockEnabled &&
         g_legs[i].mfe>=MicroLockArmMoney &&
         current>0.0 &&
         current<=MicroLockFloorMoney &&
         OppositeMicroReversalConfirmed(g_basketSide,MicroLockOppPredScore,0.55,MicroLockRequireOppFlow))
        {
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|MICRO_PROFIT_LOCK|basket=%I64u|leg=%d|ticket=%I64u|side=%s|Current=%.2f|Peak=%.2f|MFE=%.2f|MAE=%.2f|Floor=%.2f|Pred=%s B=%d S=%d|Flow=%s",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_basketId,i+1,g_legs[i].ticket,
                        SideText(g_basketSide),current,g_legs[i].peak_profit,g_legs[i].mfe,g_legs[i].mae,
                        MicroLockFloorMoney,g_nextCandleBias,g_nextBuyScore,g_nextSellScore,FlowText(g_flow));
         ClosePositionVerified(g_legs[i].ticket,"MICRO_PROFIT_LOCK");
         continue;
        }

      // v4.83: staged virtual cash profit protection.
      // This changes EXIT management only; P1 Direction >=75 entry logic remains untouched.
      if(!minimum_hold_active && AdaptiveProfitLockEnabled)
        {
         double apl_floor=AdaptiveProfitLockFloor(g_legs[i].peak_profit);
         if(apl_floor>-DBL_MAX/2.0 && current>0.0 && current<=apl_floor)
           {
            if(DebugAuditLog)
               PrintFormat("AUDIT|%s|ADAPTIVE_PROFIT_LOCK|basket=%I64u|leg=%d|ticket=%I64u|PeakProfit=%.2f|Current=%.2f|LockFloor=%.2f|MFE=%.2f|MAE=%.2f",
                           TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_basketId,i+1,g_legs[i].ticket,
                           g_legs[i].peak_profit,current,apl_floor,g_legs[i].mfe,g_legs[i].mae);
            ClosePositionVerified(g_legs[i].ticket,"ADAPTIVE_PROFIT_LOCK");
            continue;
           }
        }
      if(!g_legs[i].lock_armed && g_legs[i].peak_profit>=g_legConfig[i].activate_money)
        {
         g_legs[i].lock_armed=true;
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|PROFIT_LOCK_ARMED|basket=%I64u|leg=%d|ticket=%I64u|PeakProfit=%.2f|activate=%.2f|giveback=%.2f",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_basketId,i+1,g_legs[i].ticket,
                        g_legs[i].peak_profit,g_legConfig[i].activate_money,g_legConfig[i].giveback_money);
        }
      if(!minimum_hold_active && g_legs[i].lock_armed && current>0.0 && current<=g_legs[i].peak_profit-g_legConfig[i].giveback_money)
        {
         ClosePositionVerified(g_legs[i].ticket,"PEAK_PROFIT_GIVEBACK");
         continue;
        }
      if(!minimum_hold_active && MaxLossMoneyPerLeg>0.0 && current<=-MaxLossMoneyPerLeg)
        {
         ClosePositionVerified(g_legs[i].ticket,"MAX_LOSS_PER_LEG");
         continue;
        }
      if(g_legs[i].status==LEG_RECONCILE_REQUIRED ||
         MathAbs(g_legs[i].requested_volume-g_legs[i].filled_volume)>SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP)*0.5)
         EnsurePartialCashTarget(i);
     }
   g_statusText=StringFormat("MANAGE %s BASKET %I64u",SideText(g_basketSide),g_basketId);
  }

//====================================================================
// OPPOSITE WATCHER: WATCH -> ARM -> CLOSE -> REVALIDATE
//====================================================================
Candidate BuildWatchCandidate(const ENUM_SIDE side)
  {
   Candidate candidate;
   candidate.side=side;
   candidate.direction_score=BuildDirectionScore(side);
   candidate.structure_score=BuildStructureScore(side);
   candidate.combined_score=(int)MathRound(candidate.direction_score*0.65+candidate.structure_score*0.35);
   candidate.entry_quality=BuildEntryQuality(side);
   candidate.setup_type=(RunThreeProfileTest ? TestProfileName()+"_"+SetupType(side) : SetupType(side));
   candidate.signal_id=RunThreeProfileTest
                       ? StringFormat("%s|%I64u|P%d|%s|%I64d",_Symbol,MagicNumber,g_testProfile,SideText(side),(long)g_currentBarTime)
                       : StringFormat("%s|%I64u|%s|%I64d",_Symbol,MagicNumber,SideText(side),(long)g_currentBarTime);
   candidate.eligible=candidate.combined_score>=OppositeArmScore && candidate.entry_quality>=MinEntryQuality;
   candidate.reject_reason="";
   return candidate;
  }

void UpdateOppositeWatcher()
  {
   bool basket_open=HasOwnPositions();
   if(basket_open)
     {
      ENUM_SIDE watched=g_basketSide==SIDE_BUY ? SIDE_SELL : SIDE_BUY;
      Candidate candidate=BuildWatchCandidate(watched);
      bool side_flow=watched==SIDE_BUY ? (g_flow==FLOW_BUY || g_flow==FLOW_STRONG_BUY)
                                      : (g_flow==FLOW_SELL || g_flow==FLOW_STRONG_SELL);
      bool strong_flow=watched==SIDE_BUY ? g_flow==FLOW_STRONG_BUY : g_flow==FLOW_STRONG_SELL;
      if(g_oppositeState==OPP_IDLE && candidate.combined_score>=MinSetupScore && side_flow)
        {
         g_oppositeState=OPP_WATCHING;
         g_oppositeSide=watched;
        }
      if((g_oppositeState==OPP_WATCHING || g_oppositeState==OPP_ARMED) &&
         candidate.eligible && strong_flow)
        {
         if(g_oppositeState!=OPP_ARMED && DebugAuditLog)
            PrintFormat("AUDIT|%s|OPPOSITE_ARMED|basket=%I64u|side=%s|Dir=%d|Struct=%d|Combined=%d|EQ=%d",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_basketId,SideText(watched),
                        candidate.direction_score,candidate.structure_score,candidate.combined_score,candidate.entry_quality);
         g_oppositeState=OPP_ARMED;
         g_oppositeSide=watched;
         g_oppositeArmedTime=TimeCurrent();
        }
      if(g_oppositeState==OPP_WATCHING && !side_flow && candidate.combined_score<MinSetupScore-10)
         g_oppositeState=OPP_IDLE;
      return;
     }

   if(g_oppositeState!=OPP_REVALIDATE) return;
   if(g_oppositeArmedTime<=0 || TimeCurrent()-g_oppositeArmedTime>OppositeFreshSeconds)
     {
      g_oppositeState=OPP_IDLE;
      g_statusText="OPPOSITE EXPIRED";
      return;
     }
   Candidate current=BuildCandidate(g_oppositeSide);
   if(current.eligible)
     {
      if(DebugAuditLog)
         PrintFormat("AUDIT|%s|OPPOSITE_REVALIDATED|side=%s|signal=%s",
                     TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),SideText(current.side),current.signal_id);
      g_oppositeState=OPP_IDLE;
      ExecuteCandidate(current);
      return;
     }
   if(current.reject_reason=="cooldown")
     {
      g_statusText="OPPOSITE REVALIDATE COOLDOWN";
      return;
     }
   if(DebugAuditLog)
      PrintFormat("AUDIT|%s|OPPOSITE_REVALIDATE_FAILED|side=%s|reason=%s",
                  TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),SideText(current.side),current.reject_reason);
   g_oppositeState=OPP_IDLE;
  }

//====================================================================
// AUTHORITATIVE TRANSACTION RECONCILIATION AND BASKET FINALIZATION
//====================================================================
bool PositionIdentifierOpen(const ulong position_id)
  {
   for(int i=PositionsTotal()-1;i>=0;i--)
     {
      ulong ticket=PositionGetTicket(i);
      if(ticket==0) continue;
      if((ulong)PositionGetInteger(POSITION_IDENTIFIER)==position_id &&
         PositionGetString(POSITION_SYMBOL)==_Symbol &&
         (ulong)PositionGetInteger(POSITION_MAGIC)==MagicNumber) return true;
     }
   return false;
  }

double ClosedPositionNet(const ulong position_id)
  {
   if(position_id==0 || !HistorySelectByPosition(position_id)) return 0.0;
   double total=0.0;
   int deals=HistoryDealsTotal();
   for(int i=0;i<deals;i++)
     {
      ulong deal=HistoryDealGetTicket(i);
      if(deal==0) continue;
      total+=HistoryDealGetDouble(deal,DEAL_PROFIT);
      total+=HistoryDealGetDouble(deal,DEAL_SWAP);
      total+=HistoryDealGetDouble(deal,DEAL_COMMISSION);
      total+=HistoryDealGetDouble(deal,DEAL_FEE);
     }
   return total;
  }

string DealReasonText(const ENUM_DEAL_REASON reason)
  {
   if(reason==DEAL_REASON_TP) return "SERVER_TP";
   if(reason==DEAL_REASON_SL) return "SERVER_SL";
   if(reason==DEAL_REASON_SO) return "STOP_OUT";
   if(reason==DEAL_REASON_EXPERT) return "EA_CLOSE";
   return EnumToString(reason);
  }

void OnTradeTransaction(const MqlTradeTransaction &trans,const MqlTradeRequest &request,const MqlTradeResult &result)
  {
   if(trans.type!=TRADE_TRANSACTION_DEAL_ADD || trans.deal==0) return;
   if(!HistoryDealSelect(trans.deal)) return;
   if(HistoryDealGetString(trans.deal,DEAL_SYMBOL)!=_Symbol) return;
   if((ulong)HistoryDealGetInteger(trans.deal,DEAL_MAGIC)!=MagicNumber) return;
   ENUM_DEAL_ENTRY entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal,DEAL_ENTRY);
   ulong position_id=(ulong)HistoryDealGetInteger(trans.deal,DEAL_POSITION_ID);
   if(entry==DEAL_ENTRY_IN || entry==DEAL_ENTRY_INOUT)
     {
      ReconcilePositions(false);
      RecalculateRealizedHistory();
      UpdateRiskState(false);
      if(g_entryPending && g_allEntryRequestsSent && ActivePositionCount()==g_expectedLegs) ConfirmBasketEntry();
      return;
     }
   if(entry!=DEAL_ENTRY_OUT && entry!=DEAL_ENTRY_OUT_BY) return;

   int index=FindLegByPositionId(position_id);
   if(index<0)
     {
      ReconcilePositions(false);
      index=FindLegByPositionId(position_id);
     }
   if(PositionIdentifierOpen(position_id))
     {
      ReconcilePositions(false);
      return;
     }

   ENUM_DEAL_REASON deal_reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(trans.deal,DEAL_REASON);
   string exit_reason=DealReasonText(deal_reason);
   if(index>=0 && g_legs[index].pending_exit_reason!="") exit_reason=g_legs[index].pending_exit_reason;
   double realized=ClosedPositionNet(position_id);
   if(V494ThirtyOrderTest)
     {
      string route="UNKNOWN";
      if(index>=0 && g_legs[index].entry_route!="") route=g_legs[index].entry_route;
      if(route=="UNKNOWN") route=V494RouteFromPositionHistory(position_id);
      double leg_mfe=(index>=0 ? g_legs[index].mfe : 0.0);
      double leg_mae=(index>=0 ? g_legs[index].mae : 0.0);
      g_v494Closed++;
      if(route=="FAST")
        {
         g_v494FastClosed++; g_v494FastNet+=realized; g_v494FastMFE+=leg_mfe; g_v494FastMAE+=leg_mae;
         if(realized>0.0) g_v494FastWins++; else if(realized<0.0) g_v494FastLosses++;
        }
      else
        {
         g_v494NormalClosed++; g_v494NormalNet+=realized; g_v494NormalMFE+=leg_mfe; g_v494NormalMAE+=leg_mae;
         if(realized>0.0) g_v494NormalWins++; else if(realized<0.0) g_v494NormalLosses++;
        }
      if(StringFind(exit_reason,"TP")>=0) g_v494ExitTP++;
      else if(StringFind(exit_reason,"SL")>=0) g_v494ExitSL++;
      else if(StringFind(exit_reason,"PROFIT_LOCK")>=0 || StringFind(exit_reason,"GIVEBACK")>=0) g_v494ExitProfitLock++;
      else if(StringFind(exit_reason,"WRONG_WAY")>=0) g_v494ExitWrongWay++;
      if(DebugAuditLog)
         PrintFormat("AUDIT|%s|V514_TEST_CLOSE|closed=%d/%d|route=%s|position=%I64u|net=%.2f|MFE=%.2f|MAE=%.2f|exit=%s",
                     TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_v494Closed,V494AcceptedOrderLimit,route,
                     position_id,realized,leg_mfe,leg_mae,exit_reason);
      V494PrintSummary(false);
     }
   if(RunThreeProfileTest && g_testProfile>=1 && g_testProfile<=3)
     {
      int pi=g_testProfile-1;
      g_profileClosed[pi]++;
      g_profileNet[pi]+=realized;
      if(DebugAuditLog)
         PrintFormat("AUDIT|%s|TEST_POSITION_CLOSED|profile=%s|closed=%d/%d|position=%I64u|net=%.2f|profileNet=%.2f",
                     TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),TestProfileName(),
                     g_profileClosed[pi],PositionsPerProfile,position_id,realized,g_profileNet[pi]);
     }
   if(index>=0)
     {
      g_legs[index].status=LEG_CLOSED;
      if(index==0 && deal_reason==DEAL_REASON_TP) g_tp1Reached=true;
      if(DebugAuditLog)
         PrintFormat("AUDIT|%s|LEG_EXIT|basket=%I64u|leg=%d|ticket=%I64u|position=%I64u|exitReason=%s|realizedNet=%.2f|PeakProfit=%.2f|LockArmed=%d|MFE=%.2f|MAE=%.2f",
                     TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_basketId,index+1,g_legs[index].ticket,
                     position_id,exit_reason,realized,g_legs[index].peak_profit,(int)g_legs[index].lock_armed,
                     g_legs[index].mfe,g_legs[index].mae);
     }
   ReconcilePositions(false);
   if(g_tp1Reached && !UseVirtualCashSLTP) ApplyBreakEvenToRemaining();
   RecalculateRealizedHistory();
   UpdateRiskState(false);
   if(!HasOwnPositions() && g_basketActive && !g_entryPending) FinalizeBasket();
   PersistState(true);
  }

void AdvanceThreeProfileTestIfReady()
  {
   if(!RunThreeProfileTest || g_threeProfileFinished || HasOwnPositions() || g_entryPending) return;
   if(g_testProfile<1 || g_testProfile>3) return;
   int pi=g_testProfile-1;
   if(g_profileOpened[pi]<PositionsPerProfile || g_profileClosed[pi]<g_profileOpened[pi]) return;

   if(DebugAuditLog)
      PrintFormat("AUDIT|%s|PROFILE_RESULT|profile=%s|positions=%d|net=%.2f|avg=%.2f",
                  TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),TestProfileName(),
                  g_profileClosed[pi],g_profileNet[pi],
                  g_profileClosed[pi]>0 ? g_profileNet[pi]/g_profileClosed[pi] : 0.0);

   if(g_testProfile<3)
     {
      g_testProfile++;
      g_lastSignalBarTime=0;
      g_lastSignalSide=-1;
      g_lastRejectKey="";
      g_statusText="TEST SWITCH -> "+TestProfileName();
      if(DebugAuditLog)
         PrintFormat("AUDIT|%s|PROFILE_SWITCH|next=%s|targetPositions=%d",
                     TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),TestProfileName(),PositionsPerProfile);
     }
   else
     {
      g_threeProfileFinished=true;
      g_statusText="THREE PROFILE TEST COMPLETE";
      if(DebugAuditLog)
         PrintFormat("AUDIT|%s|THREE_PROFILE_SUMMARY|P1_net=%.2f|P2_net=%.2f|P3_net=%.2f|TOTAL=%.2f",
                     TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_profileNet[0],g_profileNet[1],g_profileNet[2],
                     g_profileNet[0]+g_profileNet[1]+g_profileNet[2]);
     }
   PersistState(true);
  }

void FinalizeBasket()
  {
   if(!g_basketActive) return;
   g_basketRealized=0.0;
   for(int i=0;i<MAX_LEGS;i++)
      if(g_legs[i].position_id>0) g_basketRealized+=ClosedPositionNet(g_legs[i].position_id);
   if(g_basketRealized<0.0) g_consecutiveLosses++;
   else if(g_basketRealized>0.0) g_consecutiveLosses=0;
   g_lastTradeCloseTime=TimeCurrent();

   // v4.85 virtual reverse-limit seeker:
   // after SELL closes, track the low and look for a confirmed BUY rebound;
   // after BUY closes, track the high and look for a confirmed SELL reversal.
   if(ReverseSeekEnabled)
     {
      MqlTick seek_tick;
      if(SymbolInfoTick(_Symbol,seek_tick))
        {
         g_reverseSeekSide=(g_basketSide==SIDE_SELL ? SIDE_BUY : SIDE_SELL);
         g_reverseSeekStart=TimeCurrent();
         g_reverseSeekExtreme=(g_reverseSeekSide==SIDE_BUY ? seek_tick.ask : seek_tick.bid);
         g_reverseSeekActive=true;
         if(DebugAuditLog)
            PrintFormat("AUDIT|%s|REVERSE_SEEK_ARMED|closedSide=%s|seekSide=%s|start=%.5f",
                        TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),SideText(g_basketSide),
                        SideText(g_reverseSeekSide),g_reverseSeekExtreme);
        }
     }

   g_basketActive=false;
   g_entryPending=false;
   g_allEntryRequestsSent=false;
   g_incompleteBasket=false;
   if(g_oppositeState==OPP_ARMED) g_oppositeState=OPP_REVALIDATE;
   else if(g_oppositeState!=OPP_REVALIDATE) g_oppositeState=OPP_IDLE;
   RecalculateRealizedHistory();
   UpdateRiskState(false);
   g_statusText=StringFormat("BASKET %I64u CLOSED %.2f",g_basketId,g_basketRealized);
   if(DebugAuditLog)
      PrintFormat("AUDIT|%s|BASKET_EXIT|basket=%I64u|side=%s|realizedNet=%.2f|consecutiveLosses=%d|next=%s",
                  TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),g_basketId,SideText(g_basketSide),
                  g_basketRealized,g_consecutiveLosses,OppositeStateText());
   AdvanceThreeProfileTestIfReady();
   PersistState(true);
  }

//====================================================================
// COMPACT PANEL
//====================================================================
void SetPanelLabel(const string name,const string text,const int x,const int y,const color clr,const int size=9)
  {
   if(ObjectFind(0,name)<0)
     {
      ObjectCreate(0,name,OBJ_LABEL,0,0,0);
      ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER);
      ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,name,OBJPROP_HIDDEN,true);
      ObjectSetInteger(0,name,OBJPROP_BACK,false);
     }
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(0,name,OBJPROP_COLOR,clr);
   ObjectSetInteger(0,name,OBJPROP_FONTSIZE,size);
   ObjectSetString(0,name,OBJPROP_FONT,"Consolas");
   ObjectSetString(0,name,OBJPROP_TEXT,text);
   ObjectSetInteger(0,name,OBJPROP_ZORDER,10000+y);
  }

void UpdatePanel()
  {
   const string bg="EIMS_STATUS_BG";
   if(ObjectFind(0,bg)<0)
     {
      ObjectCreate(0,bg,OBJ_RECTANGLE_LABEL,0,0,0);
      ObjectSetInteger(0,bg,OBJPROP_CORNER,CORNER_LEFT_UPPER);
      ObjectSetInteger(0,bg,OBJPROP_XDISTANCE,8);
      ObjectSetInteger(0,bg,OBJPROP_YDISTANCE,18);
      ObjectSetInteger(0,bg,OBJPROP_XSIZE,520);
      ObjectSetInteger(0,bg,OBJPROP_YSIZE,545);
      ObjectSetInteger(0,bg,OBJPROP_BGCOLOR,clrBlack);
      ObjectSetInteger(0,bg,OBJPROP_COLOR,clrDimGray);
      ObjectSetInteger(0,bg,OBJPROP_BORDER_TYPE,BORDER_FLAT);
      ObjectSetInteger(0,bg,OBJPROP_SELECTABLE,false);
      ObjectSetInteger(0,bg,OBJPROP_HIDDEN,true);
     }
   // Keep the black rectangle behind every status label.
   ObjectSetInteger(0,bg,OBJPROP_BACK,true);
   ObjectSetInteger(0,bg,OBJPROP_ZORDER,0);
   ObjectSetInteger(0,bg,OBJPROP_BGCOLOR,clrBlack);
   ObjectSetInteger(0,bg,OBJPROP_COLOR,clrDimGray);
   int x=18,y=27,h=17;
   SetPanelLabel("EIMS_P00","ENTRYINFINITY MICRO SCALPER v4.93",x,y,clrWhite,10); y+=h;
   SetPanelLabel("EIMS_P01","FASTFLOW STATUS",x,y,clrWhite,13); y+=h+4;
   SetPanelLabel("EIMS_P02","-----------------------------------------------",x,y,clrDimGray,9); y+=h;
   string dir="NEUTRAL"; color dirc=clrSilver;
   if(g_buyDirectionScore>g_sellDirectionScore){dir="BUY";dirc=clrLime;}
   else if(g_sellDirectionScore>g_buyDirectionScore){dir="SELL";dirc=clrTomato;}
   SetPanelLabel("EIMS_P03",StringFormat("DIRECTION: %s",dir),x,y,dirc,10); y+=h;
   SetPanelLabel("EIMS_P04",StringFormat("BUY D:%d S:%d C:%d EQ:%d   |   SELL D:%d S:%d C:%d EQ:%d",g_buyDirectionScore,g_buyStructureScore,g_buyCombinedScore,g_buyEntryQuality,g_sellDirectionScore,g_sellStructureScore,g_sellCombinedScore,g_sellEntryQuality),x,y,clrWhite,9); y+=h;
   SetPanelLabel("EIMS_P05",StringFormat("FASTFLOW: %s   ticks/s %.1f   BUY %.2f  SELL %.2f",FlowText(g_flow),g_ticksPerSecond,g_buyTickRatio,g_sellTickRatio),x,y,(g_flow==FLOW_STRONG_BUY||g_flow==FLOW_BUY)?clrLime:(g_flow==FLOW_STRONG_SELL||g_flow==FLOW_SELL)?clrTomato:clrSilver,9); y+=h;
   SetPanelLabel("EIMS_P06",StringFormat("STRUCTURE: %s   H %.3f   L %.3f",StructureText(g_structure),g_swingHigh.price,g_swingLow.price),x,y,clrWhite,9); y+=h;
   SetPanelLabel("EIMS_P07",StringFormat("STATUS: %s   |   %s",g_statusText,OppositeStateText()),x,y,clrYellow,9); y+=h;
   SetPanelLabel("EIMS_P08",StringFormat("SPREAD %.1f  shock:%s  tickAge:%.2f  volatility:%s",g_currentSpreadPoints,g_spreadShock?"YES":"NO",g_tickAgeSeconds,g_volatilitySpike?"YES":"NO"),x,y,clrWhite,9); y+=h;
   SetPanelLabel("EIMS_P09","-----------------------------------------------",x,y,clrDimGray,9); y+=h;
   SetPanelLabel("EIMS_P10",StringFormat("MODE: CASH  LEGS:%d  MIN DIR:%d%%  MIN HOLD:%ds",LegCount,MinDirectionPercent,MinimumHoldSeconds),x,y,clrAqua,9); y+=h;
   SetPanelLabel("EIMS_HYBRID",StringFormat("HYBRID75:%s  P2/P3 TELEMETRY:%s  NEXT TP: TP%d $%.0f",HybridDirect75Entry?"ON":"OFF",HybridTelemetry?"ON":"OFF",GlobalTargetSlotForAcceptedOffset(0),GlobalTargetMoneyForAcceptedOffset(0)),x,y,clrAqua,9); y+=h;
   SetPanelLabel("EIMS_SPIKE",StringFormat("PRED:%s B:%d S:%d | SPIKE WAIT:%s | REV SEEK:%s %s",
                  g_nextCandleBias,g_nextBuyScore,g_nextSellScore,SpikeWaitEnabled?"ON":"OFF",
                  g_reverseSeekActive?"ON":"OFF",g_reverseSeekActive?SideText(g_reverseSeekSide):"-"),x,y,clrWhite,9); y+=h;
   SetPanelLabel("EIMS_SAFE",StringFormat("SAFE REG:%s | FBU:%s FBD:%s | DispATR:%.2f | Chase B:%s S:%s",
                  g_safeRegime,g_safeFalseBreakUp?"Y":"N",g_safeFalseBreakDown?"Y":"N",
                  CurrentLiveDisplacementATR(),g_safeChaseBlockedBuy?"WAIT":"OK",g_safeChaseBlockedSell?"WAIT":"OK"),
                  x,y,clrWhite,9); y+=h;
   SetPanelLabel("EIMS_EXH",StringFormat("EXHAUST | B:%s/%s S:%s/%s | DispATR:%.2f",
                  g_exhaustBuyArmed?"ARM":"OK",g_exhaustBuyRetraceSeen?"RET":"-",
                  g_exhaustSellArmed?"ARM":"OK",g_exhaustSellRetraceSeen?"RET":"-",
                  CurrentLiveDisplacementATR()),x,y,clrWhite,9); y+=h;
   string rev_buy_reason="",rev_sell_reason="";
   SetPanelLabel("EIMS_V494",StringFormat("V4.94 TEST %d/%d closed:%d  ROUTE:%s  FAST:%d/%d NORM:%d/%d",
                 g_v494Accepted,V494AcceptedOrderLimit,g_v494Closed,g_v494SelectedRoute,
                 g_v494FastClosed,g_v494FastAccepted,g_v494NormalClosed,g_v494NormalAccepted),x,y,clrWhite,9); y+=h;
   bool rev_block_buy=StrongOppositeReversal(SIDE_BUY,rev_buy_reason);
   bool rev_block_sell=StrongOppositeReversal(SIDE_SELL,rev_sell_reason);
   SetPanelLabel("EIMS_REV",StringFormat("REV OVERRIDE | BUY:%s SELL:%s | Pred B:%d S:%d | Flow:%s",
                  rev_block_buy?"BLOCK":"OK",rev_block_sell?"BLOCK":"OK",
                  g_nextBuyScore,g_nextSellScore,FlowText(g_flow)),x,y,clrWhite,9); y+=h;
   if(RunThreeProfileTest)
     {
      int pi=MathMax(0,MathMin(2,g_testProfile-1));
      SetPanelLabel("EIMS_TEST1",StringFormat("TEST: %s  OPENED:%d/%d CLOSED:%d NET:%.2f",TestProfileName(),g_profileOpened[pi],PositionsPerProfile,g_profileClosed[pi],g_profileNet[pi]),x,y,clrAqua,9); y+=h;
      SetPanelLabel("EIMS_TEST2",StringFormat("P1 %.2f | P2 %.2f | P3 %.2f | ROC9 %.4f",g_profileNet[0],g_profileNet[1],g_profileNet[2],g_roc9),x,y,clrWhite,9); y+=h;
     }
   SetPanelLabel("EIMS_P11","SL/TP MODE: VIRTUAL CASH ($)   ATR SL/TP: OFF",x,y,clrAqua,9); y+=h;
   SetPanelLabel("EIMS_P12",StringFormat("BASKET: %I64u   OPEN: %d/%d   RISK HALT: %s",g_basketId,ActivePositionCount(),LegCount,g_riskHalt?HaltReasonText():"NO"),x,y,clrWhite,9); y+=h;
   SetPanelLabel("EIMS_P13",StringFormat("BASKET P/L: %.2f   Day: %.2f   Week: %.2f   DD: %.2f%%",BasketFloatingRaw(),g_dailyRealized+BasketFloatingRaw(),g_weeklyRealized+BasketFloatingRaw(),g_equityDrawdownPercent),x,y,clrWhite,9); y+=h;
   SetPanelLabel("EIMS_P14","-----------------------------------------------",x,y,clrDimGray,9); y+=h;
   SetPanelLabel("EIMS_P15","LEG   LOT     TP($)   SL($)   PEAK    LOCK",x,y,clrYellow,9); y+=h;
   for(int i=0;i<LegCount;i++)
     {
      double floating=0.0;
      if(g_legs[i].ticket>0 && PositionSelectByTicket(g_legs[i].ticket)) floating=PositionNetProfitBySelection(i);
      double peak=g_legs[i].peak_profit<=-DBL_MAX/2.0 ? 0.0 : g_legs[i].peak_profit;
      double lot=g_legs[i].filled_volume>0.0?g_legs[i].filled_volume:(UseFixedLot?FixedLot:0.0);
      string txt=StringFormat("%2d   %5.2f   %6.0f   %5.0f   %6.2f   %s",i+1,lot,g_legConfig[i].target_money,g_legConfig[i].stop_loss_money,peak,g_legs[i].lock_armed?"ARMED":"-");
      SetPanelLabel(StringFormat("EIMS_LEG_%02d",i),txt,x,y,clrWhite,9); y+=h;
     }
   y+=2;
   SetPanelLabel("EIMS_P30",StringFormat("Profit lock: L1 %.0f/-%.0f  L2 %.0f/-%.0f  L3 %.0f/-%.0f",g_legConfig[0].activate_money,g_legConfig[0].giveback_money,g_legConfig[1].activate_money,g_legConfig[1].giveback_money,g_legConfig[2].activate_money,g_legConfig[2].giveback_money),x,y,clrSilver,8); y+=h;
   SetPanelLabel("EIMS_P31",StringFormat("Magic: %I64u   Symbol: %s",MagicNumber,_Symbol),x,y,clrSilver,8);

   // Remove old v4.42 generic line labels if present.
   for(int i=0;i<40;i++)
     {
      string old=StringFormat("EIMS_STATUS_LINE_%02d",i);
      if(ObjectFind(0,old)>=0) ObjectDelete(0,old);
     }
   if(ObjectFind(0,"EIMS_STATUS_TEXT")>=0) ObjectDelete(0,"EIMS_STATUS_TEXT");
   ChartRedraw(0);
  }
