//+------------------------------------------------------------------+
//| XAU_V59.mq5                                                      |
//| XAUUSD BASSO - V56 shell: SELL-only + two structural gates       |
//| Build: OEM_V59_M5_001                                            |
//| BUY operational disabled. SELL direct (no V57 breakout).         |
//| Gates: gap_std3>=0.26199 AND delta30_mean5<=0.100396883.          |
//| BUY blocked with NO BUY->SELL remap. No automatic re-entry.      |
//| BUY never locks edge. Vault/HL/EdgeDying = V56 exit authorities. |
//+------------------------------------------------------------------+
#property strict
#property version   "1.59"

// V59: V56 lifecycle + V55 scores; SELL-only with two pre-entry structural gates.
// CSV segmented by month / ~8 MB parts in FILE_COMMON.

#include <Trade/Trade.mqh>

// -------------------------------------------------------------------
// V52 MULTI-MONTH EDGE FINGERPRINT CONTRACT (approved scope only):
// 1) V50 execution/management logic is preserved: BUY/SELL EntryScore, session, fixed 0.01 lot,
//    Risk Manager, hard loss, structural EdgeHealth/DYING, Dynamic TP, main Profit Vault and SELL micro Vault are unchanged.
// 2) V52 replaces ONLY the V50 February-specific hard-rule bank with a PRE-ENTRY multi-month probabilistic classifier.
// 3) BUY and SELL classifiers are fully separate. They were fitted on the combined February+March candidate dataset,
//    with ProfitPeak >=1.60 used only as the historical GOOD label; no calendar/date or future P/L is a runtime input.
// 4) Runtime features are available before broker order only: current structural snapshot, previous 4 OPS (M5) snapshots,
//    spread, EntryScore and its already-computed component scores.
// V54: OPS timeframe is PERIOD_M5. M1-fitted score/FP/edge coefficients are DIAGNOSTIC BASELINE only.
// 5) Operational hard reject is deliberately conservative:
//       BUY  reject only when P(GOOD) <= 0.15
//       SELL reject only when P(GOOD) <= 0.18
//    A wider WATCH band is diagnostic only and has zero entry authority.
// 6) GOOD probability is diagnostic only; it never forces entry or bypasses EntryScore/session/risk/exposure checks.
// 7) Every tradable candidate logs P(GOOD), P(BAD), model band and ACCEPT/REJECT decision.
//    Fingerprint rejects continue through the existing mirror lifecycle so false rejects are measurable out of sample.
// 8) April 2026 is intentionally NOT used in calibration and is reserved as the first untouched out-of-sample test.
// 9) Profit Vault / Micro Vault / EdgeState / hard loss behavior is intentionally unchanged from V50.
// -------------------------------------------------------------------

input long   InpMagic                    = 26080859; // V59 default (distinct from V58 26080858)
// V55 operational entry / lifecycle thresholds (exact approved defaults).
input double InpV55EntryScore            = 60.0;   // ENTRY_VALID / ACTIVE minimum
input double InpV55StrongScore           = 70.0;   // STRONG + Profit-Vault reentry arm
input double InpV55DeadScore             = 40.0;   // EDGE_DYING / DEAD below this
input double InpRiskPct                  = 0.25;   // legacy diagnostic only; BASSO lot is fixed 0.01
input double InpMinimumOpenLot           = 0.01;   // legacy compatibility; execution is fixed at 0.01
input bool   InpEnableTrading            = true;
input int    InpMaxSpreadPoints          = 80;
input int    InpSlippagePoints           = 30;
input double InpBassoHardMaxLossEUR      = 3.50;   // absolute BASSO loss boundary; operational exit authority

// V54: BUY/SELL score thresholds retained ONLY as M1-era diagnostic baseline/reference (NOT M5-calibrated).
input double InpBuyEntryScoreMin          = 55.00; // baseline reference only on M5
input double InpSellEntryScoreMin         = 80.00; // baseline reference only on M5

// Legacy evidence values are retained for CSV/backward comparison only.
// They are NOT hard entry gates in V46.
input double InpBuyQualityGapMinATR       = 0.368555;
input double InpSellRangeMinATR           = 0.8499708121861677;

// Approved BASSO entry session. Management/exit remains active 24h.
input int    InpTradeStartHour            = 8;
input int    InpTradeEndHour              = 20;


// Separate BASSO BUY / SELL entry brains, re-calibrated from the V17 MT5 CSV.
// V17 replay evidence (same executed trades, before V18 lifecycle changes):
// BUY gap 0.35..0.60 + bullish body 0.20..0.80 ATR retained 49 trades, +158.73 EUR.
// SELL gap 0.10..0.18 + prior30 <=1 + closeExtreme <=0.40 + range <=1.60 ATR
// retained 40 trades, +114.06 EUR. Combined: 89 trades, +272.79 EUR, all 4 test days positive.
input double InpBuyEntryGapATR           = 0.35;
input double InpBuyEntryGapMaxATR        = 0.60;
input double InpBuyBodyMinATR            = 0.20;
input double InpBuyBodyMaxATR            = 0.80;
input double InpBuyPrior30MaxATR         = 1.00;

input double InpSellEntryGapATR          = 0.10;
input double InpSellEntryGapMaxATR       = 0.18;
input double InpSellPrior30MinATR        = -3.00;
input double InpSellPrior30MaxATR        = 1.00;
input double InpSellCloseExtremeMax      = 0.40;
input double InpSellRangeMaxATR          = 1.60;

// Legacy ATR sizing input retained for compatibility. V43 hard-loss protection is converted
// into a REAL broker-side emergency SL plus a bot-side close backup.
input double InpBassoSizingATR           = 0.25;

// EdgeHealth lifecycle bands already approved for the project.
input double InpEdgeDeathHealth          = 40.0;   // legacy compatibility; structural exit uses approved strong boundary
input double InpEdgeMediumHealth         = 70.0;   // legacy compatibility
input double InpEdgeStrongHealth         = 90.0;   // structural EdgeAlive boundary
input double InpEdgeRearmHealth          = 60.0;   // diagnostic only; never vetoes EntryScore
input int    InpEdgeDeathConfirmBars     = 1;      // legacy compatibility only; V19 LIVE EdgeAlive does not use it as a veto
input double InpBuyEdgeAliveHealthV2     = 10.0;   // hard structural death fallback
input double InpSellEdgeAliveHealthV2    = 12.0;   // hard structural death fallback
input double InpBuyEdgeDyingHealthV46    = 45.0;   // V45 retained DYING boundary
input double InpBuyEdgeDyingDropPctV46   = 10.0;   // structural only
input double InpSellEdgeDyingHealthV46   = 20.0;   // V45 retained DYING boundary
input double InpSellEdgeDyingDropPctV46  = 10.0;   // structural only
input double InpBuyEdgeWeakHealthV46     = 70.0;   // unchanged V46 WEAK structural boundary; Profit Vault is separate
input double InpBuyEdgeWeakDropPctV46    = 5.0;    // first meaningful BUY structural deterioration
input double InpSellEdgeWeakHealthV46    = 50.0;   // unchanged V46 WEAK structural boundary; Profit Vault is separate
input double InpSellEdgeWeakDropPctV46   = 5.0;    // first meaningful SELL structural deterioration

#define BUILD_ID "OEM_V59_M5_001"
#define BASSO_OPS_TF PERIOD_M5                 // single operational/diagnostic structural TF
#define BASSO_DELTA_HORIZON_BARS 6             // ~30 minutes on M5 (NOT 30 M5 bars)
#define BASSO_FIXED_LOT 0.01
#define BASSO_PEAK_ARM_EUR 2.00         // legacy STEP diagnostic reference begins here
#define BASSO_MIN_PROTECTED_EUR 1.50    // legacy V42 STEP reference only; no V50 exit authority
#define BASSO_VAULT_MICRO_ARM_EUR 1.60  // inherited main positive protection trigger
#define BASSO_VAULT_MICRO_FLOOR_EUR 0.70 // inherited main emergency positive net floor
#define BASSO_SELL_MICRO_EMERGENCY_ARM_EUR 1.20   // V50 approved SELL-only pre-main trigger
#define BASSO_SELL_MICRO_EMERGENCY_FLOOR_EUR 0.30 // V50 approved SELL-only protected net floor
#define BASSO_SELL_MICRO_EMERGENCY_HEALTH_MAX 50.0 // V50 structural pre-alert: SELL EdgeHealth <= 50
#define BASSO_SELL_MICRO_EMERGENCY_DROP_MIN_PCT 2.0 // V50 structural pre-alert: live-gap deterioration >= 2%
#define BASSO_ROUNDTRIP_COST_RESERVE_EUR 0.04 // V34 broker truth at 0.01: 0.02 entry + 0.02 exit
#define BASSO_HARD_EXECUTION_RESERVE_EUR 1.97 // V39 broker truth repeated a 1.97 EUR cap deviation; restore empirical reserve
#define BASSO_FLOOR_ADAPTIVE_BUFFER_EUR 1.13 // legacy adaptive helper; V50 exact Vault path does not widen beyond approved floor
#define BASSO_BUY_ENTRY_SCORE_MAX 75.00 // OLD/SHADOW M1-era BUY overextension baseline (no V55 entry authority)
#define V59_SELL_SIGNED_GAP_STD3_MIN 0.26199 // mandatory SELL pre-entry gate (same FP feature as CSV)
#define V59_SELL_DELTA30_MEAN5_MAX 0.100396883 // second SELL pre-entry structural gate (same FP feature as CSV)
#define DIAG2025_FILE_LIMIT_BYTES 8000000ULL
#define DIAG2025_BUILD "V59_M5_001"

enum EngineClass { ENG_SCARTO=0, ENG_BASSO=1, ENG_MEDIO=2, ENG_SUPER=3 };
enum TradeSide   { SIDE_NONE=0, SIDE_BUY=1, SIDE_SELL=-1 };
enum EdgeStateV46 { EDGE_STATE_STRONG=0, EDGE_STATE_WEAK=1, EDGE_STATE_DYING=2 };
enum RiskDecision{ RISK_BLOCK=0, RISK_APPROVE=1 };
enum V55EdgeState { V55_EDGE_OFF=0, V55_EDGE_ACTIVE=1, V55_EDGE_STRONG=2, V55_EDGE_DYING=3 };

// Forward declarations for single-authority lifecycle helpers.
double RequiredEntryScore(TradeSide side);
double RequiredProtectedFloor(int exit_mode,double peak);
double ProfitVaultFloorFromPeak(TradeSide side,double peak);
bool SellMicroEmergencyVaultEligible(TradeSide side,double peak,double health,double live_gap_drop_pct);
double DynamicTPFromPeak(double peak);
double HardLossServerTargetNet();
EdgeStateV46 StructuralEdgeStateV46(TradeSide side,double health,double live_gap_drop_pct);
string EdgeStateNameV46(EdgeStateV46 state);
string CsvOperationalEdgeState(ulong identifier);
string V55EdgeStateName(V55EdgeState st);
bool DiagKeepEvent(string stage,string status);
void DiagWriteEvent(datetime t,string row);
void DiagDailyTouch(datetime t);
void DiagFlushDaily();
void DiagUpdateRejectTracks();
void DiagUpdateRealTrade(ulong identifier,double current_net);
void DiagFlushOpenRejects(string status);
double MoneyPerPointPerLot();
bool StructuralEdgeDyingV46(TradeSide side,double health,double live_gap_drop_pct);
double HypotheticalNetFromPrice(TradeSide side,double entry,double now);

struct MarketSnapshot
{
   datetime time;                              // alias of bar_time (completed OPS bar) for legacy call sites
   datetime bar_time;                          // completed M5 bar open time (rates[1].time) — features
   datetime decision_time;                     // TimeCurrent() when snapshot/decision is taken — session gate
   long tick_time_msc;                         // diagnostic exact tick timestamp in milliseconds
   double bid,ask,spread_pts;
   double bar_open,bar_high,bar_low,bar_close;
   double atr_m1,atr_m5,atr_m15;               // V54: atr_m1 = ATR(OPS,shift1) M5 completed alias; atr_m5=same; atr_m15=context only
   double body_m1,range_m1,body_atr,range_atr,close_extreme; // body_m1/range_m1 = OPS-bar body/range (M5); legacy names
   double ema3,ema8,gap_atr;                  // completed OPS (M5) EMA3/8 — NOT time-equivalent to M1 EMA3/8
   double buy_health,sell_health;                  // completed-OPS ENTRY health only (baseline formula)
   double delta30,delta30_atr;                     // ~30 REAL minutes via BASSO_DELTA_HORIZON_BARS on OPS TF
   double live_ema3,live_ema8,live_gap_atr;        // forming OPS bar, refreshed every tick
};

double PositionEdgeHealthV2(TradeSide side,const MarketSnapshot &s,double peak_signed_live);
double StructuralLiveGapDropPctV46(TradeSide side,const MarketSnapshot &s,double peak_signed_live);

struct EdgeContext
{
   ulong edge_id;
   ulong sequence_id;
   EngineClass engine;
   TradeSide side;
   double score;
   double health;
   double score_gap;
   double score_shape;
   double score_context;
   double score_extra;
   double score_threshold;
   bool continuation; // BUY protected-floor continuation candidate
   bool recovery;     // legacy/diagnostic flag only in V46; no operational hard-loss recovery path
   string reason;
   // V59 final-authority pre-entry signature (must be set before Execute; never rebuilt post-entry).
   bool   v59_signed_gap_std3_valid;
   double v59_signed_gap_std3;
   bool   v59_delta30_mean5_valid;
   double v59_delta30_mean5;
};

struct RiskContext
{
   double equity;
   double risk_money;
   double stop_points;
   double raw_lot;
   double final_lot;
   double actual_risk;
   RiskDecision decision;
   string reason;
};

// V52 pre-entry multi-month structural fingerprint. All fields are computable before an order exists.
struct FingerprintFeatures
{
   double atr,range,body,dir_close,signed_gap,signed_live,signed_delta30,health,close_ema3;
   double atr_d1,atr_d2,atr_std5;
   double range_d1,range_d2,range_mean3,range_std3;
   double body_d1,body_mean3;
   double dir_close_d1,dir_close_mean3,dir_close_std3;
   double signed_gap_d1,signed_gap_mean3,signed_gap_std3;
   double signed_live_d1,signed_live_mean3,signed_live_std3;
   double delta30_d1,delta30_mean5,delta30_std5;
   double health_d1,health_mean3,health_std3;
};

struct FingerprintDecision
{
   bool ready;
   bool reject;
   int bad_code;
   int good_code;
   double bad_match;
   double good_match;
   string bad_id;
   string good_id;
   string decision;
};

// Prototypes that reference structs — must follow struct declarations above.
datetime DiagDayClockFromSnapshot(const MarketSnapshot &s);
void DiagWriteM5Candidate(const MarketSnapshot &s,const EdgeContext &e,string decision,string reject_reason,
                          const FingerprintDecision &fp,bool fp_eval);

CTrade trade;
string g_symbol="";
string g_symbol_resolution="UNRESOLVED";
bool g_runtime_ok=false;
ulong g_edge_counter=0;
ulong g_seq_counter=0;
string FOne="XAU_V59.csv";

bool g_buy_rearmed=true;
bool g_sell_rearmed=true;
bool g_snapshot_ok=false;
datetime g_snapshot_bar=0;
MarketSnapshot g_snapshot;
// Five-new-OPS-bar structural history, [0]=current snapshot, [1..4]=previous M5 snapshots.
// OLD fingerprint history only (shadow). NOT time-equivalent to M1 five-bar history.
MarketSnapshot g_fp_hist[5];
int g_fp_hist_count=0;

bool g_close_inflight=false; // blocks any new entry until broker DEAL_OUT truth clears the previous close
bool g_cont_reentry_armed=false; // LEGACY V50 continuation — no V55 operational authority
TradeSide g_cont_reentry_side=SIDE_NONE;
double g_cont_reentry_peak_live=0.0;

// V55 operational edge runtime (independent from old EdgeHealth).
struct V55EdgeRuntime
{
   V55EdgeState state;
   TradeSide side;
   ulong edge_id;
   double entry_score;
   double current_score;
   double peak_score;
   bool reentry_armed;
   double last_positive_exit_price;
   long last_positive_exit_tick_msc;
};
V55EdgeRuntime g_v55;

// Last computed V55 diagnostics mirrored into EVENTS CSV (same values as live authority).
double g_diag_v55_buy_score=0.0;
double g_diag_v55_sell_score=0.0;
double g_diag_v55_selected_score=0.0;
TradeSide g_diag_v55_selected_side=SIDE_NONE;
string g_diag_v55_entry_authority="NA";
string g_diag_v55_old_score_decision="NA";
string g_diag_v55_old_fp_decision="NA";

struct RejectTrack
{
   bool active;
   ulong edge_id;
   TradeSide side;
   datetime start_time;
   double entry_price;
   double entry_gap;
   double health_peak_live;
   double max_net;
   double min_net;
   double entry_score;
   double threshold;
   double score_gap;
   double score_shape;
   double score_context;
   double score_extra;
   string origin;
   string origin_reason;
};
RejectTrack g_reject_tracks[];

struct PostCloseTrack
{
   bool active;
   ulong sequence_id;
   TradeSide side;
   datetime start_time;
   double exit_price;
   double entry_gap;
   double health_peak_live;
   double exit_net;
   double preclose_peak;
   double max_extra_net;
   double step_floor;
   double entry_score;
   double threshold;
   double score_gap;
   double score_shape;
   double score_context;
   double score_extra;
   double recovery_score;
   int exit_code;
   int exit_mode;
};
PostCloseTrack g_post_close_tracks[];

//+------------------------------------------------------------------+
//| DIAG2025 segmented research state (diagnostics only)              |
//+------------------------------------------------------------------+
struct DiagFileState
{
   int year;
   int month;
   int part;
};

struct PendingEntryDiag
{
   bool active;
   ulong sequence_id;
   ulong edge_id;
   TradeSide side;
   datetime decision_time;
   long decision_msc;
   EdgeContext edge;
   MarketSnapshot snap;
   FingerprintFeatures fp;
   FingerprintDecision fp_decision;
   bool fp_ready;
   int hist_count;
   string hist_csv;
};
PendingEntryDiag g_diag_pending[];

struct RealTradeDiag
{
   bool active;
   ulong identifier;
   ulong sequence_id;
   ulong edge_id;
   TradeSide side;
   datetime decision_time;
   long decision_msc;
   datetime entry_time;
   long entry_msc;
   double entry_price;
   double volume;
   double min_net;
   double max_net;
   EdgeContext edge;
   MarketSnapshot snap;
   FingerprintFeatures fp;
   FingerprintDecision fp_decision;
   bool fp_ready;
   int hist_count;
   string hist_csv;
};
RealTradeDiag g_diag_real[];

struct DiagRejectTrack
{
   bool active;
   ulong edge_id;
   TradeSide side;
   datetime start_time;
   long start_msc;
   double entry_price;
   double health_peak_live;
   double max_net;
   double min_net;
   double max_net_before_hard;
   bool hard_loss_seen;
   datetime hard_loss_time;
   string origin;
   string origin_reason;
   EdgeContext edge;
   MarketSnapshot snap;
   FingerprintFeatures fp;
   FingerprintDecision fp_decision;
   bool fp_ready;
   int hist_count;
   string hist_csv;
};
DiagRejectTrack g_diag_rejects[];

string g_diag_run_tag="R00000000";

DiagFileState g_diag_events_file;
DiagFileState g_diag_trades_file;
DiagFileState g_diag_rejects_file;
DiagFileState g_diag_postclose_file;
DiagFileState g_diag_daily_file;
DiagFileState g_diag_candidates_file;

int g_diag_day_key=0;
datetime g_diag_day_anchor=0;
double g_diag_day_start_balance=0.0;
double g_diag_day_start_equity=0.0;
int g_diag_day_entry_requests=0;
int g_diag_day_entries=0;
int g_diag_day_closed=0;
int g_diag_day_closed_buy=0;
int g_diag_day_closed_sell=0;
int g_diag_day_loss=0;
int g_diag_day_0_3=0;
int g_diag_day_3_5=0;
int g_diag_day_5p=0;
double g_diag_day_net=0.0;
double g_diag_day_gross=0.0;
double g_diag_day_costs=0.0;
int g_diag_day_reject_started=0;
int g_diag_day_reject_completed=0;
int g_diag_day_reject_loss=0;
int g_diag_day_reject_0_3=0;
int g_diag_day_reject_3_5=0;
int g_diag_day_reject_5p=0;

//+------------------------------------------------------------------+
//| Universal XAU Symbol Resolver                                    |
//+------------------------------------------------------------------+
string UpperCopy(string v)
{
   StringToUpper(v);
   return v;
}

bool ContainsCI(string text,string needle)
{
   text=UpperCopy(text);
   needle=UpperCopy(needle);
   return (StringFind(text,needle)>=0);
}

bool IsSymbolSelected(string sym)
{
   int selected=SymbolsTotal(true);
   for(int i=0;i<selected;i++)
      if(SymbolName(i,true)==sym) return true;
   return false;
}

bool SymbolRuntimeUsable(string sym)
{
   if(sym=="") return false;
   return (SymbolInfoDouble(sym,SYMBOL_POINT)>0 &&
           SymbolInfoDouble(sym,SYMBOL_TRADE_TICK_SIZE)>0 &&
           SymbolInfoDouble(sym,SYMBOL_VOLUME_STEP)>0);
}

int XauUsdCandidateScore(string sym,bool prefer_chart,string &why)
{
   why="NO_MATCH";
   if(sym=="") return -1;

   string base=UpperCopy(SymbolInfoString(sym,SYMBOL_CURRENCY_BASE));
   string profit=UpperCopy(SymbolInfoString(sym,SYMBOL_CURRENCY_PROFIT));
   string margin=UpperCopy(SymbolInfoString(sym,SYMBOL_CURRENCY_MARGIN));
   string desc=UpperCopy(SymbolInfoString(sym,SYMBOL_DESCRIPTION));
   string name=UpperCopy(sym);

   bool exact_props=(base=="XAU" && profit=="USD");
   bool xauusd_name=ContainsCI(name,"XAUUSD");
   bool gold_name=ContainsCI(name,"GOLD");
   bool gold_desc=ContainsCI(desc,"GOLD");
   if(!exact_props && !xauusd_name && !gold_name) return -1;

   int score=0;
   if(exact_props) score+=1000;
   if(base=="XAU") score+=200;
   if(profit=="USD") score+=200;
   if(margin=="USD") score+=20;
   if(xauusd_name) score+=120;
   if(gold_name) score+=70;
   if(gold_desc) score+=10;
   if(prefer_chart) score+=5000;

   if(exact_props) why="XAU_USD_PROPERTIES";
   else if(xauusd_name) why="XAUUSD_NAME_FALLBACK";
   else why="GOLD_NAME_FALLBACK";
   if(prefer_chart) why="CHART_"+why;
   return score;
}

bool ResolveXauUsdSymbol()
{
   g_symbol="";
   g_symbol_resolution="UNRESOLVED";

   string why="";
   int chart_score=XauUsdCandidateScore(_Symbol,true,why);
   if(chart_score>=0 && SymbolRuntimeUsable(_Symbol))
   {
      g_symbol=_Symbol;
      g_symbol_resolution=why;
      return true;
   }

   int total=SymbolsTotal(false);
   int best_score=-1;
   string best_symbol="";
   string best_why="";
   bool ambiguous=false;

   for(int i=0;i<total;i++)
   {
      string sym=SymbolName(i,false);
      if(sym=="") continue;
      bool was_selected=IsSymbolSelected(sym);
      if(!was_selected && !SymbolSelect(sym,true)) continue;

      string candidate_why="";
      int score=XauUsdCandidateScore(sym,false,candidate_why);
      bool usable=SymbolRuntimeUsable(sym);
      if(!was_selected) SymbolSelect(sym,false);
      if(score<0 || !usable) continue;

      if(score>best_score)
      {
         best_score=score;
         best_symbol=sym;
         best_why=candidate_why;
         ambiguous=false;
      }
      else if(score==best_score && sym!=best_symbol)
         ambiguous=true;
   }

   if(best_score<0 || best_symbol=="")
   {
      g_symbol_resolution="NO_XAUUSD_SYMBOL_FOUND";
      return false;
   }
   if(ambiguous)
   {
      g_symbol_resolution="AMBIGUOUS_XAUUSD_SYMBOLS";
      return false;
   }
   if(!SymbolSelect(best_symbol,true))
   {
      g_symbol_resolution="SYMBOL_SELECT_FAILED";
      return false;
   }

   g_symbol=best_symbol;
   g_symbol_resolution=best_why;
   return true;
}

//+------------------------------------------------------------------+
//| Names / CSV                                                       |
//+------------------------------------------------------------------+
string EngineName(EngineClass e)
{
   if(e==ENG_BASSO) return "BASSO";
   if(e==ENG_MEDIO) return "MEDIO";
   if(e==ENG_SUPER) return "SUPER";
   return "SCARTO";
}

string SideName(TradeSide s)
{
   if(s==SIDE_BUY) return "BUY";
   if(s==SIDE_SELL) return "SELL";
   return "NONE";
}

string CsvSafe(string v)
{
   StringReplace(v,",",";");
   StringReplace(v,"\r"," ");
   StringReplace(v,"\n"," ");
   return v;
}

int CsvOpenAppend()
{
   int h=FileOpen(FOne,FILE_READ|FILE_WRITE|FILE_TXT|FILE_ANSI|FILE_COMMON|FILE_SHARE_READ|FILE_SHARE_WRITE);
   if(h==INVALID_HANDLE) return INVALID_HANDLE;
   FileSeek(h,0,SEEK_END);
   return h;
}

void Csv(string row)
{
   int h=CsvOpenAppend();
   if(h==INVALID_HANDLE) return;
   FileWriteString(h,row+"\r\n");
   FileFlush(h);
   FileClose(h);
}

int CsvExitMode(ulong identifier,ulong sequence_id)
{
   if(identifier>0)
   {
      string k=StringFormat("XV52_%I64d_%I64u_MODE",InpMagic,identifier);
      if(GlobalVariableCheck(k)) return (int)GlobalVariableGet(k);
   }
   if(sequence_id>0)
   {
      string k=StringFormat("XV52_SEQ_%I64d_%I64u_MODE",InpMagic,sequence_id);
      if(GlobalVariableCheck(k)) return (int)GlobalVariableGet(k);
   }
   return -1;
}

string CsvExitModeName(int mode)
{
   if(mode==1) return "EDGE_DYNAMIC_TP_PROFIT_VAULT";
   return "NA";
}

string CsvOperationalEdgeState(ulong identifier)
{
   if(identifier==0) return "NA";
   string k=StringFormat("XV52_%I64d_%I64u_ESTATE",InpMagic,identifier);
   if(!GlobalVariableCheck(k)) return "NA";
   int state=(int)GlobalVariableGet(k);
   if(state<0 || state>2) return "NA";
   return EdgeStateNameV46((EdgeStateV46)state);
}

void CsvCreate()
{
   // DIAG2025 writes lazily to segmented monthly files.
   // The legacy monolithic XAU_V52.csv is intentionally not created.
}

int DiagCsvFieldCount(const string csv)
{
   if(StringLen(csv)<=0) return 0;
   int n=1;
   int len=StringLen(csv);
   for(int i=0;i<len;i++)
      if(StringGetCharacter(csv,i)==',') n++;
   return n;
}

// Static schema contract: DiagEventHeader() columns MUST equal LogEvent() value count.
#define DIAG_EVENTS_COLUMNS 87

void LogEvent(const MarketSnapshot &s,const EdgeContext &e,string stage,string status,string reason,
              const RiskContext &r,double sl,uint retcode,string ret_desc,
              ulong order,ulong deal,ulong position,ulong identifier,double volume,double price,
              double current_net,double profit_peak,double protected_floor,double edge_health,
              int death_count,double entry_atr,double diag_edge_entry_raw=0.0,double diag_edge_current_raw=0.0,
              double diag_edge_peak_raw=0.0,double diag_edge_relative_pct=0.0,double diag_edge_drop_pct=0.0,
              int fp_ready=0,double fp_bad_match=0.0,double fp_good_match=0.0,
              string fp_bad_id="NA",string fp_good_id="NA",string fp_decision="NA")
{
   if(!DiagKeepEvent(stage,status)) return;
   datetime bar_t=(s.bar_time>0 ? s.bar_time : s.time);
   datetime dec_t=(s.decision_time>0 ? s.decision_time : TimeCurrent());
   string edge_state_diag=((e.side==SIDE_BUY || e.side==SIDE_SELL)
                           ? EdgeStateNameV46(StructuralEdgeStateV46(e.side,edge_health,diag_edge_drop_pct)) : "NA");
   int exit_mode_csv=CsvExitMode(identifier,e.sequence_id);
   string exit_mode_name=CsvExitModeName(exit_mode_csv);
   string op_edge_state=CsvOperationalEdgeState(identifier);
   string fp_bad_s=CsvSafe(fp_bad_id);
   string fp_good_s=CsvSafe(fp_good_id);
   string fp_dec_s=CsvSafe(fp_decision);
   // Block A: 14 — build_id .. reason
   string row=StringFormat("%s,%s,%s,%s,%s,%I64d,%s,%I64u,%I64u,%s,%s,%s,%s,%s,",
      BUILD_ID,EnumToString((ENUM_TIMEFRAMES)BASSO_OPS_TF),EnumToString((ENUM_TIMEFRAMES)_Period),
      TimeToString(bar_t,TIME_DATE|TIME_SECONDS),TimeToString(dec_t,TIME_DATE|TIME_SECONDS),
      s.tick_time_msc,CsvSafe(g_symbol),e.sequence_id,e.edge_id,
      EngineName(e.engine),SideName(e.side),stage,status,CsvSafe(reason));
   // Block B: 15 — bar_open .. spread_pts
   row+=StringFormat("%.5f,%.5f,%.5f,%.5f,%.8f,%.8f,%.8f,%.8f,%.8f,%.6f,%.6f,%d,%.2f,%.2f,%.1f,",
      s.bar_open,s.bar_high,s.bar_low,s.bar_close,s.atr_m1,s.atr_m5,s.atr_m15,s.ema3,s.ema8,s.gap_atr,s.delta30_atr,
      BASSO_DELTA_HORIZON_BARS,s.buy_health,s.sell_health,s.spread_pts);
   // Block C: 9 — equity .. retcode_desc
   row+=StringFormat("%.2f,%.2f,%.2f,%.6f,%.6f,%.2f,%.5f,%u,%s,",
      r.equity,r.risk_money,r.stop_points,r.raw_lot,r.final_lot,r.actual_risk,sl,retcode,CsvSafe(ret_desc));
   // Block D: 15 — order .. live_gap_atr
   row+=StringFormat("%I64u,%I64u,%I64u,%I64u,%.6f,%.5f,%.2f,%.2f,%.2f,%.2f,%d,%.8f,%d,%d,%.6f,",
      order,deal,position,identifier,volume,price,current_net,profit_peak,protected_floor,edge_health,
      death_count,entry_atr,g_buy_rearmed?1:0,g_sell_rearmed?1:0,s.live_gap_atr);
   // Block E: 5 — diag_edge_*
   row+=StringFormat("%.4f,%.4f,%.4f,%.2f,%.2f,",
      diag_edge_entry_raw,diag_edge_current_raw,diag_edge_peak_raw,diag_edge_relative_pct,diag_edge_drop_pct);
   // Block F: 6 — entry_score .. score_threshold
   row+=StringFormat("%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,",
      e.score,e.score_gap,e.score_shape,e.score_context,e.score_extra,e.score_threshold);
   // Block G: 7 — exit_mode .. operational_edge_state
   row+=StringFormat("%d,%s,%.2f,%.2f,%d,%s,%s,",
      exit_mode_csv,exit_mode_name,
      RequiredProtectedFloor(exit_mode_csv,profit_peak),DynamicTPFromPeak(profit_peak),
      (protected_floor>0.0?1:0),edge_state_diag,op_edge_state);
   // Block H: 6 — fp_ready .. fp_decision
   row+=StringFormat("%d,%.2f,%.2f,%s,%s,%s,",
      fp_ready,fp_bad_match,fp_good_match,fp_bad_s,fp_good_s,fp_dec_s);
   // Block I: 10 — V55 authority mirror (same values used for live decisions)
   row+=StringFormat("%.2f,%.2f,%.2f,%s,%s,%I64u,%s,%d,%s,%s",
      g_diag_v55_buy_score,g_diag_v55_sell_score,g_diag_v55_selected_score,
      SideName(g_diag_v55_selected_side),V55EdgeStateName(g_v55.state),g_v55.edge_id,
      CsvSafe(g_diag_v55_entry_authority),(g_v55.reentry_armed?1:0),
      CsvSafe(g_diag_v55_old_score_decision),CsvSafe(g_diag_v55_old_fp_decision));
   // Static contract: 14+15+9+15+5+6+7+6+10 = 87
   int hdr_n=DiagCsvFieldCount(DiagEventHeader());
   int row_n=DiagCsvFieldCount(row);
   if(hdr_n!=DIAG_EVENTS_COLUMNS || row_n!=DIAG_EVENTS_COLUMNS || hdr_n!=row_n)
      PrintFormat("EVENTS schema mismatch: header=%d row=%d expected=%d",hdr_n,row_n,DIAG_EVENTS_COLUMNS);
   DiagWriteEvent(dec_t,row);
}

void LogSystem(string stage,string status,string reason)
{
   MarketSnapshot s; ZeroMemory(s);
   datetime now=TimeCurrent();
   s.decision_time=now; // DAILY / session clock — never use bar_time here
   s.time=now;
   EdgeContext e; ZeroMemory(e); e.engine=ENG_SCARTO; e.side=SIDE_NONE;
   RiskContext r; ZeroMemory(r); r.equity=AccountInfoDouble(ACCOUNT_EQUITY);
   LogEvent(s,e,stage,status,reason,r,0,0,"",0,0,0,0,0,0,0,0,0,0,0,0);
}

//+------------------------------------------------------------------+
//| IDs / indicators                                                  |
//+------------------------------------------------------------------+
ulong NewEdgeID(){ g_edge_counter++; return ((ulong)TimeCurrent()*1000ULL)+g_edge_counter; }
ulong NewSequenceID(){ g_seq_counter++; return ((ulong)TimeCurrent()*1000ULL)+500ULL+g_seq_counter; }

double ATRValue(ENUM_TIMEFRAMES tf,int shift=0,int period=14)
{
   int h=iATR(g_symbol,tf,period);
   if(h==INVALID_HANDLE) return 0.0;
   double b[];
   ArraySetAsSeries(b,true);
   double v=0.0;
   if(CopyBuffer(h,0,shift,1,b)==1) v=b[0];
   IndicatorRelease(h);
   return v;
}

double EMACompleted(const MqlRates &rates[],int count,int period)
{
   if(count<period+3) return 0.0;
   double alpha=2.0/((double)period+1.0);
   double ema=rates[count-1].close;
   for(int k=count-2;k>=1;k--)
      ema=alpha*rates[k].close+(1.0-alpha)*ema;
   return ema;
}

// Same EMA convention on OPS rates, but includes the forming OPS close so lifecycle health
// can change on every tick without changing completed-bar entry logic.
// V54 WARNING: EMA(3)/EMA(8) on M5 bars span ~15/~40 minutes — NOT equivalent to M1 EMA3/8.
double EMALive(const MqlRates &rates[],int count,int period)
{
   if(count<period+3) return 0.0;
   double alpha=2.0/((double)period+1.0);
   double ema=rates[count-1].close;
   for(int k=count-2;k>=0;k--)
      ema=alpha*rates[k].close+(1.0-alpha)*ema;
   return ema;
}

double Clamp100(double v)
{
   if(v<0.0) return 0.0;
   if(v>100.0) return 100.0;
   return v;
}

double BuyEntryHealthFromGap(double gap_atr)
{
   if(InpBuyEntryGapATR<=0) return 0.0;
   return Clamp100(100.0*gap_atr/InpBuyEntryGapATR);
}

double SellEntryHealthFromGap(double gap_atr)
{
   if(InpSellEntryGapATR<=0) return 0.0;
   return Clamp100(100.0*(-gap_atr)/InpSellEntryGapATR);
}

// V39 DIAGNOSTIC ONLY: one uncapped LIVE structural metric used consistently
// at diagnostic entry, on every current tick and for the monotonic diagnostic peak.
// It has ZERO authority over entry, hold or exit decisions.
double PositionRawEdgeScore(TradeSide side,double live_gap_atr)
{
   double base=(side==SIDE_BUY ? InpBuyEntryGapATR : InpSellEntryGapATR);
   if(base<0.000001) return 0.0;
   double signed_gap=(side==SIDE_BUY ? live_gap_atr : -live_gap_atr);
   return 100.0*(signed_gap/base);
}

// V46 structural lifecycle authority.
// EdgeHealth V2 is side-specific and structural only. Profit/loss/ProfitPeak are not inputs.
bool BuildSnapshot(MarketSnapshot &s)
{
   MqlTick tick;
   if(!SymbolInfoTick(g_symbol,tick)) return false;

   MqlRates rates[];
   ArraySetAsSeries(rates,true);
   // V54: structural brain reads BASSO_OPS_TF (M5). Need >= 1+BASSO_DELTA_HORIZON_BARS completed bars.
   int copied=CopyRates(g_symbol,BASSO_OPS_TF,0,70,rates);
   int min_bars=1+BASSO_DELTA_HORIZON_BARS+8;
   if(min_bars<32) min_bars=32;
   if(copied<min_bars) return false;

   double pt=SymbolInfoDouble(g_symbol,SYMBOL_POINT);
   if(pt<=0) return false;

   s.bar_time=rates[1].time;                 // completed M5 bar (features)
   s.decision_time=TimeCurrent();             // real decision clock (session gate)
   s.time=s.bar_time;                         // legacy alias = bar_time
   s.tick_time_msc=tick.time_msc;
   s.bid=tick.bid;
   s.ask=tick.ask;
   s.spread_pts=(tick.ask-tick.bid)/pt;
   // Completed-bar structural ATR: shift 1 matches rates[1] (NOT forming-bar shift 0).
   double atr_ops=ATRValue(BASSO_OPS_TF,1);
   s.atr_m1=atr_ops;
   s.atr_m5=atr_ops;
   s.atr_m15=ATRValue(PERIOD_M15,1); // context-only; zero entry authority
   if(s.atr_m1<=0) return false;

   s.bar_open=rates[1].open;
   s.bar_high=rates[1].high;
   s.bar_low=rates[1].low;
   s.bar_close=rates[1].close;
   s.body_m1=MathAbs(s.bar_close-s.bar_open);
   s.range_m1=s.bar_high-s.bar_low;
   s.body_atr=s.body_m1/s.atr_m1;
   s.range_atr=s.range_m1/s.atr_m1;
   s.close_extreme=0.5;
   if(s.range_m1>0) s.close_extreme=(s.bar_close-s.bar_low)/s.range_m1;

   // Completed-OPS (M5) state. EMA periods stay 3/8 in BARS (baseline formulas; NOT M1 time-equivalent).
   s.ema3=EMACompleted(rates,copied,3);
   s.ema8=EMACompleted(rates,copied,8);
   if(s.ema3<=0 || s.ema8<=0) return false;
   s.gap_atr=(s.ema3-s.ema8)/s.atr_m1;
   s.buy_health=BuyEntryHealthFromGap(s.gap_atr);
   s.sell_health=SellEntryHealthFromGap(s.gap_atr);
   // ~30 REAL minutes: BASSO_DELTA_HORIZON_BARS (=6) M5 bars back from completed bar rates[1].
   int delta_shift=1+BASSO_DELTA_HORIZON_BARS;
   s.delta30=rates[1].close-rates[delta_shift].close;
   s.delta30_atr=s.delta30/s.atr_m1;

   // LIVE lifecycle state on forming OPS (M5) bar.
   rates[0].close=tick.bid;
   if(rates[0].high<tick.bid) rates[0].high=tick.bid;
   if(rates[0].low>tick.bid || rates[0].low<=0.0) rates[0].low=tick.bid;
   s.live_ema3=EMALive(rates,copied,3);
   s.live_ema8=EMALive(rates,copied,8);
   if(s.live_ema3<=0 || s.live_ema8<=0) return false;
   s.live_gap_atr=(s.live_ema3-s.live_ema8)/s.atr_m1;
   return true;
}

bool RefreshSnapshot(bool &new_bar)
{
   datetime bar=iTime(g_symbol,BASSO_OPS_TF,0);
   if(bar==0) return false;

   new_bar=(!g_snapshot_ok || bar!=g_snapshot_bar);

   // Rebuild every tick. Completed-OPS fields stable inside the bar; live_* follow tick.
   MarketSnapshot s;
   if(!BuildSnapshot(s)) return false;
   g_snapshot=s;
   g_snapshot_bar=bar;
   g_snapshot_ok=true;

   // Rearm only from completed-OPS health (diagnostic flag; not an entry veto).
   if(new_bar)
   {
      if(s.buy_health<InpEdgeRearmHealth) g_buy_rearmed=true;
      if(s.sell_health<InpEdgeRearmHealth) g_sell_rearmed=true;
   }
   return true;
}

//+------------------------------------------------------------------+
//| BASSO entry brains / candidate filter                            |
//+------------------------------------------------------------------+
bool HistoricalSuperBuy(const MarketSnapshot &s)
{
   return (s.body_atr>=2.50 && s.range_atr>=2.50 && s.close_extreme>=0.98);
}

bool HistoricalSuperSell(const MarketSnapshot &s)
{
   return (s.body_atr>=2.50 && s.range_atr>=2.50 && s.close_extreme<=0.02);
}

bool BrainBassoBuy(const MarketSnapshot &s)
{
   // V40: BUY directional eligibility unchanged; BUY EntryScore remains the entry authority.
   // Legacy rearm and old V17/V26 min/max windows are diagnostic history, not vetoes.
   return (s.atr_m1>0.0 && s.gap_atr>0.0);
}

bool BrainBassoSell(const MarketSnapshot &s)
{
   // V40 SELL is independent; negative EMA structure selects SELL and its own score includes the V38 directional-close guard,
   // then SELL score alone decides ACCEPT/REJECT.
   return (s.atr_m1>0.0 && s.gap_atr<0.0);
}

double SigmoidScore100(double raw)
{
   if(raw>=40.0) return 100.0;
   if(raw<=-40.0) return 0.0;
   return 100.0/(1.0+MathExp(-raw));
}

//+------------------------------------------------------------------+
//| V55 M5 ENTRY BRAIN — exact approved formulas (no re-fit)         |
//+------------------------------------------------------------------+
string V55EdgeStateName(V55EdgeState st)
{
   if(st==V55_EDGE_ACTIVE) return "ACTIVE";
   if(st==V55_EDGE_STRONG) return "STRONG";
   if(st==V55_EDGE_DYING) return "DYING";
   return "OFF";
}

void V55ResetEdge(string reason)
{
   bool had_reentry=g_v55.reentry_armed;
   g_v55.state=V55_EDGE_OFF;
   g_v55.side=SIDE_NONE;
   g_v55.edge_id=0;
   g_v55.entry_score=0.0;
   g_v55.current_score=0.0;
   g_v55.peak_score=0.0;
   g_v55.reentry_armed=false;
   g_v55.last_positive_exit_price=0.0;
   g_v55.last_positive_exit_tick_msc=0;
   if(had_reentry)
   {
      EdgeContext ce; ZeroMemory(ce); ce.engine=ENG_BASSO;
      RiskContext cr; ZeroMemory(cr); cr.equity=AccountInfoDouble(ACCOUNT_EQUITY);
      LogEvent(g_snapshot,ce,"V55_REENTRY_CANCEL","CANCEL",reason,cr,0,0,"",0,0,0,0,0,0,0,0,0,0,0,0);
   }
}

bool V55FeaturesValid(const MarketSnapshot &s)
{
   return (s.atr_m1>0.0);
}

double V55BuyScore(const MarketSnapshot &s,bool &ok)
{
   ok=false;
   if(!V55FeaturesValid(s)) return 0.0;
   double atr_ops=s.atr_m1;
   double BUY_f5_delta30=s.delta30_atr;
   double BUY_f6_dir_close=s.close_extreme;
   double BUY_f8_atr=atr_ops;
   double BUY_f10_close_ema3=(s.bar_close-s.ema3)/atr_ops;
   double z_buy=
      -2.9171069046045623
      +0.4063157210557491*BUY_f5_delta30
      +1.1680554774856506*BUY_f6_dir_close
      +0.5225727526157998*BUY_f8_atr
      +1.1722963348539370*BUY_f10_close_ema3;
   ok=true;
   return SigmoidScore100(z_buy);
}

double V55SellScore(const MarketSnapshot &s,bool &ok)
{
   ok=false;
   if(!V55FeaturesValid(s)) return 0.0;
   double atr_ops=s.atr_m1;
   double SELL_f0_signed_gap=-s.gap_atr;
   double SELL_f1_signed_live=-s.live_gap_atr;
   double SELL_f6_dir_close=1.0-s.close_extreme;
   double SELL_f8_atr=atr_ops;
   double z_sell=
      -6.6791461042831150
      +1.5799977449310554*SELL_f0_signed_gap
      +0.3245312758255056*SELL_f1_signed_live
      +6.8195453582744750*SELL_f6_dir_close
      +0.3580913612036087*SELL_f8_atr;
   ok=true;
   return SigmoidScore100(z_sell);
}

double V55ScoreForSide(const MarketSnapshot &s,TradeSide side,bool &ok)
{
   if(side==SIDE_BUY) return V55BuyScore(s,ok);
   if(side==SIDE_SELL) return V55SellScore(s,ok);
   ok=false;
   return 0.0;
}

V55EdgeState V55StateFromScore(double score)
{
   if(score>=InpV55StrongScore) return V55_EDGE_STRONG;
   if(score>=InpV55EntryScore) return V55_EDGE_ACTIVE;
   if(score>=InpV55DeadScore) return V55_EDGE_ACTIVE; // alive but not entry-authorizing
   return V55_EDGE_DYING;
}

void V55PublishDiag(double buy_sc,double sell_sc,TradeSide sel,double sel_sc,string authority)
{
   g_diag_v55_buy_score=buy_sc;
   g_diag_v55_sell_score=sell_sc;
   g_diag_v55_selected_side=sel;
   g_diag_v55_selected_score=sel_sc;
   g_diag_v55_entry_authority=authority;
}

void BuildEntryBrainFeatures(const MarketSnapshot &s,TradeSide side,double &f[])
{
   ArrayResize(f,12);
   double sign=(side==SIDE_BUY ? 1.0 : -1.0);
   double range=(s.range_atr>0.0 ? s.range_atr : 0.0);
   double directional_body=(s.atr_m1>0.0 ? sign*(s.bar_close-s.bar_open)/s.atr_m1 : 0.0);
   double directional_close=(side==SIDE_BUY ? s.close_extreme : 1.0-s.close_extreme);
   double signed_gap=sign*s.gap_atr;
   double signed_live=sign*s.live_gap_atr;

   f[0]=signed_gap;
   f[1]=signed_live;
   f[2]=signed_live-signed_gap;                 // live acceleration vs completed gap
   f[3]=directional_body;
   f[4]=range;
   f[5]=sign*s.delta30_atr;
   f[6]=directional_close;
   f[7]=(range>0.0000001 ? directional_body/range : 0.0);
   f[8]=s.atr_m1;
   f[9]=s.spread_pts;
   f[10]=(s.atr_m1>0.0 ? sign*(s.bar_close-s.ema3)/s.atr_m1 : 0.0);
   f[11]=(s.atr_m1>0.0 ? sign*(s.bar_close-s.ema8)/s.atr_m1 : 0.0);
}

//+------------------------------------------------------------------+
//| V52/V54 PRE-ENTRY MULTI-MONTH EDGE FINGERPRINT (OPS = M5)        |
//| Features/history are M5. Decision thresholds are M1-era BASELINE |
//| retained for diagnostic reference only — NOT M5-calibrated.      |
//+------------------------------------------------------------------+
void PushFingerprintSnapshot(const MarketSnapshot &s)
{
   for(int i=4;i>=1;i--) g_fp_hist[i]=g_fp_hist[i-1];
   g_fp_hist[0]=s;
   if(g_fp_hist_count<5) g_fp_hist_count++;
}

double FPMean3(double a,double b,double c){ return (a+b+c)/3.0; }
double FPMean5(double a,double b,double c,double d,double e){ return (a+b+c+d+e)/5.0; }
double FPStd3(double a,double b,double c)
{
   double m=FPMean3(a,b,c);
   return MathSqrt((MathPow(a-m,2.0)+MathPow(b-m,2.0)+MathPow(c-m,2.0))/3.0);
}
double FPStd5(double a,double b,double c,double d,double e)
{
   double m=FPMean5(a,b,c,d,e);
   return MathSqrt((MathPow(a-m,2.0)+MathPow(b-m,2.0)+MathPow(c-m,2.0)+MathPow(d-m,2.0)+MathPow(e-m,2.0))/5.0);
}

void FPPoint(const MarketSnapshot &s,TradeSide side,
             double &atr,double &range,double &body,double &dir_close,
             double &signed_gap,double &signed_live,double &signed_delta30,double &health,double &close_ema3)
{
   double sign=(side==SIDE_BUY ? 1.0 : -1.0);
   atr=s.atr_m1;
   range=s.range_atr;
   body=s.body_atr;
   dir_close=(side==SIDE_BUY ? s.close_extreme : 1.0-s.close_extreme);
   signed_gap=sign*s.gap_atr;
   signed_live=sign*s.live_gap_atr;
   signed_delta30=sign*s.delta30_atr;
   health=(side==SIDE_BUY ? s.buy_health : s.sell_health);
   close_ema3=(s.atr_m1>0.0 ? sign*(s.bar_close-s.ema3)/s.atr_m1 : 0.0);
}

bool BuildFingerprintFeatures(TradeSide side,FingerprintFeatures &f)
{
   ZeroMemory(f);
   if(side!=SIDE_BUY && side!=SIDE_SELL) return false;
   if(g_fp_hist_count<5) return false;

   double atr[5],range[5],body[5],dc[5],sg[5],sl[5],d30[5],health[5],ce3[5];
   for(int i=0;i<5;i++)
      FPPoint(g_fp_hist[i],side,atr[i],range[i],body[i],dc[i],sg[i],sl[i],d30[i],health[i],ce3[i]);

   f.atr=atr[0]; f.range=range[0]; f.body=body[0]; f.dir_close=dc[0];
   f.signed_gap=sg[0]; f.signed_live=sl[0]; f.signed_delta30=d30[0]; f.health=health[0]; f.close_ema3=ce3[0];
   f.atr_d1=atr[0]-atr[1]; f.atr_d2=atr[0]-atr[2]; f.atr_std5=FPStd5(atr[0],atr[1],atr[2],atr[3],atr[4]);
   f.range_d1=range[0]-range[1]; f.range_d2=range[0]-range[2];
   f.range_mean3=FPMean3(range[0],range[1],range[2]); f.range_std3=FPStd3(range[0],range[1],range[2]);
   f.body_d1=body[0]-body[1]; f.body_mean3=FPMean3(body[0],body[1],body[2]);
   f.dir_close_d1=dc[0]-dc[1]; f.dir_close_mean3=FPMean3(dc[0],dc[1],dc[2]); f.dir_close_std3=FPStd3(dc[0],dc[1],dc[2]);
   f.signed_gap_d1=sg[0]-sg[1]; f.signed_gap_mean3=FPMean3(sg[0],sg[1],sg[2]); f.signed_gap_std3=FPStd3(sg[0],sg[1],sg[2]);
   f.signed_live_d1=sl[0]-sl[1]; f.signed_live_mean3=FPMean3(sl[0],sl[1],sl[2]); f.signed_live_std3=FPStd3(sl[0],sl[1],sl[2]);
   f.delta30_d1=d30[0]-d30[1]; f.delta30_mean5=FPMean5(d30[0],d30[1],d30[2],d30[3],d30[4]);
   f.delta30_std5=FPStd5(d30[0],d30[1],d30[2],d30[3],d30[4]);
   f.health_d1=health[0]-health[1]; f.health_mean3=FPMean3(health[0],health[1],health[2]); f.health_std3=FPStd3(health[0],health[1],health[2]);
   return true;
}

double FPSigmoid(double z)
{
   if(z>35.0) return 1.0;
   if(z<-35.0) return 0.0;
   return 1.0/(1.0+MathExp(-z));
}

// L2-regularized logistic classifiers: coefficients fitted on Feb+Mar 2026 M1-era candidate bank.
// V54 feeds M5 features into the SAME coefficients for DIAGNOSTIC REFERENCE ONLY.
// Do NOT treat ACCEPT/REJECT as M5-calibrated authority until refit on M5 labels.
double MultiMonthGoodProbability(TradeSide side,const FingerprintFeatures &f,const EdgeContext &e,double spread_pts)
{
   double z=0.0;
   if(side==SIDE_BUY)
   {
      z=-3.34105153996161;
      z += 0.0357282586465052*f.atr;
      z += -0.325777353272672*f.range;
      z += -0.0696295093409726*f.body;
      z += 1.84340847702233*f.dir_close;
      z += 0.147395208046702*f.signed_gap;
      z += 0.122440722738631*f.signed_live;
      z += -0.000999012665539023*f.signed_delta30;
      z += -5.21126313123654e-05*f.health;
      z += 0.0551488767134287*f.close_ema3;
      z += 0.0360512955113046*f.atr_d1;
      z += 0.101715974704161*f.atr_d2;
      z += 0.0491549767888973*f.atr_std5;
      z += -0.134501303312925*f.range_d1;
      z += -0.390006948887921*f.range_d2;
      z += -0.139323889682604*f.range_mean3;
      z += -0.0130897704363649*f.range_std3;
      z += -0.0413120322089735*f.body_d1;
      z += 0.371411333654522*f.body_mean3;
      z += 0.0101472379558315*f.dir_close_d1;
      z += 0.429201248976245*f.dir_close_mean3;
      z += 0.739842361025778*f.dir_close_std3;
      z += -0.376832550938097*f.signed_gap_d1;
      z += 0.161164829673255*f.signed_gap_mean3;
      z += -0.0809867403439857*f.signed_gap_std3;
      z += -0.392972290548324*f.signed_live_d1;
      z += 0.129980681287607*f.signed_live_mean3;
      z += 0.209841871635961*f.signed_live_std3;
      z += -0.0423091910889596*f.delta30_d1;
      z += 0.02271706487354*f.delta30_mean5;
      z += -0.25296177107503*f.delta30_std5;
      z += 0.00161876425479769*f.health_d1;
      z += -0.00387429216887339*f.health_mean3;
      z += 0.00594940945917267*f.health_std3;
      z += 0.0489284768203997*spread_pts;
      z += 0.0145628320882349*e.score;
      z += -0.0946963586541827*e.score_gap;
      z += 0.313591285680737*e.score_shape;
      z += 0.349445208684898*e.score_context;
      z += -0.463141405971893*e.score_extra;
   }
   else if(side==SIDE_SELL)
   {
      z=-5.56350491533794;
      z += 0.151053155514932*f.atr;
      z += 0.134772796223653*f.range;
      z += -0.0268541925704304*f.body;
      z += 4.36254858482871*f.dir_close;
      z += 0.135701320967719*f.signed_gap;
      z += 0.173039983961275*f.signed_live;
      z += -0.0320937753085437*f.signed_delta30;
      z += 0.00185511073600208*f.health;
      z += 0.0533431875537574*f.close_ema3;
      z += 0.383160973765884*f.atr_d1;
      z += -0.492375088615139*f.atr_d2;
      z += 0.0463939563646448*f.atr_std5;
      z += -0.112726051231741*f.range_d1;
      z += 0.111158212288486*f.range_d2;
      z += 0.54709757402938*f.range_mean3;
      z += -0.0962472284239976*f.range_std3;
      z += -0.0955628380571213*f.body_d1;
      z += -0.501072534525499*f.body_mean3;
      z += -0.218052570892733*f.dir_close_d1;
      z += -0.0268776508159407*f.dir_close_mean3;
      z += -0.236862779643578*f.dir_close_std3;
      z += -0.585966217418269*f.signed_gap_d1;
      z += 0.138440540839828*f.signed_gap_mean3;
      z += -1.1338549626413*f.signed_gap_std3;
      z += -0.381721753379922*f.signed_live_d1;
      z += 0.05828575131645*f.signed_live_mean3;
      z += 0.356296074558378*f.signed_live_std3;
      z += -0.129174599174676*f.delta30_d1;
      z += -0.0399370681493147*f.delta30_mean5;
      z += 0.066422488610218*f.delta30_std5;
      z += -0.000593066863091581*f.health_d1;
      z += 0.00213710888253908*f.health_mean3;
      z += -0.00528444581839538*f.health_std3;
      z += -0.0677952280540361*spread_pts;
      z += 0.0202604501123387*e.score;
      z += 0.10061245080277*e.score_gap;
      z += 0.0948845687537017*e.score_shape;
      z += 0.00989637216806369*e.score_context;
      z += 0*e.score_extra;
   }
   else return 0.0;
   return FPSigmoid(z);
}

void EvaluateFingerprint(TradeSide side,const EdgeContext &e,FingerprintDecision &d)
{
   ZeroMemory(d);
   d.bad_id="NONE";
   d.good_id="NONE";
   d.decision="ACCEPT_WARMUP";

   FingerprintFeatures f;
   if(!BuildFingerprintFeatures(side,f)) return;

   d.ready=true;
   double pgood=MultiMonthGoodProbability(side,f,e,g_snapshot.spread_pts);
   pgood=MathMax(0.0,MathMin(1.0,pgood));
   double pbad=1.0-pgood;

   double hard_threshold=(side==SIDE_BUY ? 0.15 : 0.18);
   double watch_threshold=(side==SIDE_BUY ? 0.20 : 0.22);

   d.good_match=100.0*pgood;
   d.bad_match=100.0*pbad;
   d.reject=(pgood<=hard_threshold);
   d.bad_code=(d.reject ? 1 : (pgood<=watch_threshold ? 2 : 0));
   d.good_code=0;

   string side_tag=(side==SIDE_BUY ? "BUY" : "SELL");
   if(d.reject)
   {
      d.bad_id=side_tag+"_MM_BAD_HIGH";
      d.decision="REJECT";
   }
   else if(pgood<=watch_threshold)
   {
      d.bad_id=side_tag+"_MM_BAD_WATCH";
      d.decision="ACCEPT_WATCH";
   }
   else
   {
      d.bad_id="NONE";
      d.decision="ACCEPT";
   }
   d.good_id=side_tag+"_MM_PGOOD";
}

//+------------------------------------------------------------------+
//| DIAG2025 segmented CSV implementation                            |
//+------------------------------------------------------------------+
string DiagProfitClass(double net)
{
   if(net<0.0) return "LOSS";
   if(net<3.0) return "0_3";
   if(net<5.0) return "3_5";
   return "5_PLUS";
}

void DiagResetFileState(DiagFileState &st,int y,int m)
{
   st.year=y;
   st.month=m;
   st.part=1;
}

string DiagFileName(string kind,int y,int m,int part)
{
   return StringFormat("V59D_%s_%04d_%02d_%s_P%02d.csv",kind,y,m,g_diag_run_tag,part);
}

bool DiagAppendSegmented(DiagFileState &st,string kind,datetime t,string header,string row)
{
   MqlDateTime dt; TimeToStruct(t,dt);
   if(dt.year<=0 || dt.mon<=0) return false;
   if(st.year!=dt.year || st.month!=dt.mon || st.part<=0)
      DiagResetFileState(st,dt.year,dt.mon);

   for(int guard=0;guard<100;guard++)
   {
      string fn=DiagFileName(kind,dt.year,dt.mon,st.part);
      int h=FileOpen(fn,FILE_READ|FILE_WRITE|FILE_TXT|FILE_ANSI|FILE_COMMON|FILE_SHARE_READ|FILE_SHARE_WRITE);
      if(h==INVALID_HANDLE) return false;
      ulong sz=FileSize(h);
      if(sz==0)
      {
         FileWriteString(h,header+"\r\n");
         FileFlush(h);
         sz=FileSize(h);
      }
      ulong need=(ulong)StringLen(row)+4ULL;
      if(sz>0 && sz+need>DIAG2025_FILE_LIMIT_BYTES)
      {
         FileClose(h);
         st.part++;
         continue;
      }
      FileSeek(h,0,SEEK_END);
      FileWriteString(h,row+"\r\n");
      FileFlush(h);
      FileClose(h);
      return true;
   }
   return false;
}

string DiagEventHeader()
{
   return "build_id,ops_tf,tester_tf,bar_time,decision_time,tick_time_msc,symbol,sequence_id,edge_id,engine,side,stage,status,reason,"
          "bar_open,bar_high,bar_low,bar_close,atr_ops,atr_m5,atr_m15,ema3,ema8,gap_atr,delta30_atr,delta_horizon_bars,buy_health,sell_health,spread_pts,"
          "equity,risk_money,stop_points,raw_lot,final_lot,actual_risk,sl,retcode,retcode_desc,"
          "order,deal,position,identifier,volume,price,current_net,profit_peak,protected_floor,edge_health,"
          "death_count,entry_atr,rearmed_buy,rearmed_sell,live_gap_atr,diag_edge_entry_raw,diag_edge_current_raw,diag_edge_peak_raw,diag_edge_relative_pct,diag_edge_drop_pct,"
          "entry_score,score_gap,score_shape,score_context,score_extra,score_threshold,exit_mode,exit_mode_name,theoretical_floor,dynamic_tp,profit_floor_armed,diag_edge_state,operational_edge_state,fp_ready,fp_bad_match,fp_good_match,fp_bad_id,fp_good_id,fp_decision,"
          "v55_buy_score,v55_sell_score,v55_selected_score,v55_selected_side,v55_edge_state,v55_edge_id,v55_entry_authority,v55_reentry_armed,v55_old_score_decision,v55_old_fp_decision";
}

bool DiagKeepEvent(string stage,string status)
{
   // Keep V55 authority events and OLD shadow. Suppress only ultra-noisy every-tick EDGE_DIAG.
   if(stage=="EDGE_DIAG") return false;
   if(stage=="RISK" && status=="APPROVE") return false;
   if(stage=="RULE_AUDIT" && status=="PASS") return false;
   if(stage=="LIFECYCLE" && status=="HOLD") return false;
   if(stage=="POST_REJECT" || stage=="POST_BLOCK" || stage=="POST_SESSION" ||
      stage=="POST_RISK" || stage=="POST_EXECUTION" || stage=="POST_BROKER_REJECT" ||
      stage=="POST_FINGERPRINT") return false;
   return true;
}

void DiagWriteEvent(datetime t,string row)
{
   DiagAppendSegmented(g_diag_events_file,"EVENTS",t,DiagEventHeader(),row);
}

string DiagHistoryHeader()
{
   string h="";
   for(int i=0;i<5;i++)
   {
      if(h!="") h+=",";
      h+=StringFormat("h%d_atr,h%d_range,h%d_body,h%d_dir_close,h%d_signed_gap,h%d_signed_live,h%d_signed_delta30,h%d_health,h%d_close_ema3",
                      i,i,i,i,i,i,i,i,i);
   }
   return h;
}

string DiagHistoryValues(TradeSide side)
{
   string v="";
   double sign=(side==SIDE_BUY ? 1.0 : -1.0);
   for(int i=0;i<5;i++)
   {
      double atr=0.0,range=0.0,body=0.0,dir_close=0.0,sgap=0.0,slive=0.0,sdelta=0.0,health=0.0,close_ema3=0.0;
      if(i<g_fp_hist_count)
      {
         MarketSnapshot x=g_fp_hist[i];
         atr=x.atr_m1;
         range=x.range_atr;
         body=x.body_atr;
         dir_close=(x.atr_m1>0.0 ? sign*(x.bar_close-x.bar_open)/x.atr_m1 : 0.0);
         sgap=sign*x.gap_atr;
         slive=sign*x.live_gap_atr;
         sdelta=sign*x.delta30_atr;
         health=(side==SIDE_BUY ? x.buy_health : x.sell_health);
         close_ema3=(x.atr_m1>0.0 ? sign*(x.bar_close-x.ema3)/x.atr_m1 : 0.0);
      }
      if(v!="") v+=",";
      v+=StringFormat("%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f",
                      atr,range,body,dir_close,sgap,slive,sdelta,health,close_ema3);
   }
   return v;
}

string DiagSignatureHeader()
{
   // atr_m1 column = OPS ATR alias (M5). atr_m5 = same M5. atr_m15 = context.
   return "ops_tf,tester_tf,delta_horizon_bars,bar_open,bar_high,bar_low,bar_close,atr_ops,atr_m5,atr_m15,body_m1,range_m1,body_atr,range_atr,close_extreme,"
          "ema3,ema8,gap_atr,delta30_atr,buy_health,sell_health,spread_pts,live_ema3,live_ema8,live_gap_atr,"
          "fp_ready,fp_bad_match,fp_good_match,fp_bad_id,fp_good_id,fp_decision,fp_m5_calibration,"
          "f_atr,f_range,f_body,f_dir_close,f_signed_gap,f_signed_live,f_signed_delta30,f_health,f_close_ema3,"
          "f_atr_d1,f_atr_d2,f_atr_std5,f_range_d1,f_range_d2,f_range_mean3,f_range_std3,f_body_d1,f_body_mean3,"
          "f_dir_close_d1,f_dir_close_mean3,f_dir_close_std3,f_signed_gap_d1,f_signed_gap_mean3,f_signed_gap_std3,"
          "f_signed_live_d1,f_signed_live_mean3,f_signed_live_std3,f_delta30_d1,f_delta30_mean5,f_delta30_std5,"
          "f_health_d1,f_health_mean3,f_health_std3,hist_count,"+DiagHistoryHeader();
}

string DiagSignatureValues(const MarketSnapshot &s,const FingerprintFeatures &f,const FingerprintDecision &d,bool ready,int hist_count,string hist_csv)
{
   string row=StringFormat("%s,%s,%d,%.5f,%.5f,%.5f,%.5f,%.8f,%.8f,%.8f,%.8f,%.8f,%.6f,%.6f,%.6f,",
                       EnumToString((ENUM_TIMEFRAMES)BASSO_OPS_TF),EnumToString((ENUM_TIMEFRAMES)_Period),BASSO_DELTA_HORIZON_BARS,
                       s.bar_open,s.bar_high,s.bar_low,s.bar_close,s.atr_m1,s.atr_m5,s.atr_m15,
                       s.body_m1,s.range_m1,s.body_atr,s.range_atr,s.close_extreme);
   row+=StringFormat("%.8f,%.8f,%.6f,%.6f,%.2f,%.2f,%.1f,%.8f,%.8f,%.6f,",
                       s.ema3,s.ema8,s.gap_atr,s.delta30_atr,s.buy_health,s.sell_health,s.spread_pts,
                       s.live_ema3,s.live_ema8,s.live_gap_atr);
   row+=StringFormat("%d,%.2f,%.2f,%s,%s,%s,%s,",
                       ready?1:0,d.bad_match,d.good_match,CsvSafe(d.bad_id),CsvSafe(d.good_id),CsvSafe(d.decision),
                       "M1_BASELINE_NEEDS_M5_CALIBRATION");
   row+=StringFormat("%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,",
                       f.atr,f.range,f.body,f.dir_close,f.signed_gap,f.signed_live,f.signed_delta30,f.health,f.close_ema3);
   row+=StringFormat("%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,",
                       f.atr_d1,f.atr_d2,f.atr_std5,f.range_d1,f.range_d2,f.range_mean3,f.range_std3,f.body_d1,f.body_mean3);
   row+=StringFormat("%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,",
                       f.dir_close_d1,f.dir_close_mean3,f.dir_close_std3,f.signed_gap_d1,f.signed_gap_mean3,f.signed_gap_std3,
                       f.signed_live_d1,f.signed_live_mean3,f.signed_live_std3);
   row+=StringFormat("%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,",
                       f.delta30_d1,f.delta30_mean5,f.delta30_std5,f.health_d1,f.health_mean3,f.health_std3);
   row+=IntegerToString(hist_count)+","+hist_csv;
   return row;
}

void DiagCaptureSignature(const EdgeContext &e,const MarketSnapshot &s,
                          FingerprintFeatures &f,FingerprintDecision &d,bool &ready,int &hist_count,string &hist_csv)
{
   ZeroMemory(f); ZeroMemory(d);
   d.bad_id="NONE"; d.good_id="NONE"; d.decision="WARMUP";
   ready=BuildFingerprintFeatures(e.side,f);
   EvaluateFingerprint(e.side,e,d);
   hist_count=g_fp_hist_count;
   hist_csv=DiagHistoryValues(e.side);
}

int DiagFindPending(ulong seq)
{
   for(int i=0;i<ArraySize(g_diag_pending);i++)
      if(g_diag_pending[i].active && g_diag_pending[i].sequence_id==seq) return i;
   return -1;
}

int DiagPendingSlot()
{
   for(int i=0;i<ArraySize(g_diag_pending);i++) if(!g_diag_pending[i].active) return i;
   int n=ArraySize(g_diag_pending); ArrayResize(g_diag_pending,n+1); return n;
}

void DiagStagePendingEntry(const EdgeContext &e,const MarketSnapshot &s)
{
   int idx=DiagFindPending(e.sequence_id);
   if(idx<0) idx=DiagPendingSlot();
   g_diag_pending[idx].active=true;
   g_diag_pending[idx].sequence_id=e.sequence_id;
   g_diag_pending[idx].edge_id=e.edge_id;
   g_diag_pending[idx].side=e.side;
   g_diag_pending[idx].decision_time=(s.decision_time>0 ? s.decision_time : TimeCurrent());
   g_diag_pending[idx].decision_msc=s.tick_time_msc;
   g_diag_pending[idx].edge=e;
   g_diag_pending[idx].snap=s;
   DiagCaptureSignature(e,s,g_diag_pending[idx].fp,g_diag_pending[idx].fp_decision,g_diag_pending[idx].fp_ready,g_diag_pending[idx].hist_count,g_diag_pending[idx].hist_csv);
   DiagDailyTouch(DiagDayClockFromSnapshot(s));
   g_diag_day_entry_requests++;
}

void DiagCancelPending(ulong seq)
{
   int idx=DiagFindPending(seq);
   if(idx>=0) g_diag_pending[idx].active=false;
}

int DiagRealSlot()
{
   for(int i=0;i<ArraySize(g_diag_real);i++) if(!g_diag_real[i].active) return i;
   int n=ArraySize(g_diag_real); ArrayResize(g_diag_real,n+1); return n;
}

int DiagFindReal(ulong identifier)
{
   for(int i=0;i<ArraySize(g_diag_real);i++)
      if(g_diag_real[i].active && g_diag_real[i].identifier==identifier) return i;
   return -1;
}

void DiagRegisterDealIn(ulong identifier,const EdgeContext &e,datetime deal_time,long deal_msc,double price,double volume,double entry_net)
{
   int idx=DiagFindReal(identifier);
   if(idx<0) idx=DiagRealSlot();
   ZeroMemory(g_diag_real[idx]);
   g_diag_real[idx].active=true;
   g_diag_real[idx].identifier=identifier;
   g_diag_real[idx].sequence_id=e.sequence_id;
   g_diag_real[idx].edge_id=e.edge_id;
   g_diag_real[idx].side=e.side;
   g_diag_real[idx].entry_time=deal_time;
   g_diag_real[idx].entry_msc=deal_msc;
   g_diag_real[idx].entry_price=price;
   g_diag_real[idx].volume=volume;
   g_diag_real[idx].min_net=entry_net-BASSO_ROUNDTRIP_COST_RESERVE_EUR*0.5;
   g_diag_real[idx].max_net=g_diag_real[idx].min_net;
   g_diag_real[idx].edge=e;

   int p=DiagFindPending(e.sequence_id);
   if(p>=0)
   {
      g_diag_real[idx].edge_id=g_diag_pending[p].edge_id;
      g_diag_real[idx].decision_time=g_diag_pending[p].decision_time;
      g_diag_real[idx].decision_msc=g_diag_pending[p].decision_msc;
      g_diag_real[idx].edge=g_diag_pending[p].edge;
      g_diag_real[idx].snap=g_diag_pending[p].snap;
      g_diag_real[idx].fp=g_diag_pending[p].fp;
      g_diag_real[idx].fp_decision=g_diag_pending[p].fp_decision;
      g_diag_real[idx].fp_ready=g_diag_pending[p].fp_ready;
      g_diag_real[idx].hist_count=g_diag_pending[p].hist_count;
      g_diag_real[idx].hist_csv=g_diag_pending[p].hist_csv;
      g_diag_pending[p].active=false;
   }
   else
   {
      g_diag_real[idx].decision_time=(g_snapshot.decision_time>0 ? g_snapshot.decision_time : TimeCurrent());
      g_diag_real[idx].decision_msc=g_snapshot.tick_time_msc;
      g_diag_real[idx].snap=g_snapshot;
      DiagCaptureSignature(e,g_snapshot,g_diag_real[idx].fp,g_diag_real[idx].fp_decision,g_diag_real[idx].fp_ready,g_diag_real[idx].hist_count,g_diag_real[idx].hist_csv);
   }
   DiagDailyTouch(deal_time);
   g_diag_day_entries++;
}

void DiagUpdateRealTrade(ulong identifier,double current_net)
{
   int idx=DiagFindReal(identifier);
   if(idx<0) return;
   if(current_net>g_diag_real[idx].max_net) g_diag_real[idx].max_net=current_net;
   if(current_net<g_diag_real[idx].min_net) g_diag_real[idx].min_net=current_net;
}

string DiagTradeHeader()
{
   return "diag_build,decision_time,decision_tick_msc,entry_time,entry_msc,exit_time,exit_msc,symbol,tester_tf,sequence_id,edge_id,identifier,side,volume,entry_price,exit_price,duration_sec,"
          "gross_price_profit,entry_costs,exit_commission,swap,exit_fee,total_costs_signed,final_net,profit_peak,diag_mfe,diag_mae,giveback,retention_pct,final_class,peak_class,exit_code,exit_source,exit_mode,"
          "entry_score,score_gap,score_shape,score_context,score_extra,score_threshold,entry_reason,"+DiagSignatureHeader();
}

string DiagExitSource(int code)
{
   if(code==1) return "V55_EDGE_DYING";
   if(code==2) return "PROFIT_VAULT";
   if(code==3) return "HARD_LOSS";
   if(code==5) return "RULE_INVARIANT_FAILSAFE";
   if(code==6) return "PROTECTION_FAILSAFE";
   return "UNCLASSIFIED";
}

void DiagFinalizeTrade(ulong identifier,const EdgeContext &fallback_e,datetime exit_time,long exit_msc,
                       double exit_price,double final_net,double peak,int exit_code,int exit_mode,
                       double gross_price_profit,double exit_commission,double swap,double exit_fee,double entry_costs)
{
   int idx=DiagFindReal(identifier);
   RealTradeDiag rt; ZeroMemory(rt);
   if(idx>=0) rt=g_diag_real[idx];
   else
   {
      rt.active=true; rt.identifier=identifier; rt.sequence_id=fallback_e.sequence_id; rt.edge_id=fallback_e.edge_id; rt.side=fallback_e.side;
      rt.decision_time=(g_snapshot.decision_time>0 ? g_snapshot.decision_time : TimeCurrent()); rt.decision_msc=g_snapshot.tick_time_msc; rt.entry_time=exit_time; rt.entry_msc=exit_msc;
      rt.entry_price=exit_price; rt.volume=BASSO_FIXED_LOT; rt.edge=fallback_e; rt.snap=g_snapshot;
      rt.min_net=MathMin(0.0,final_net); rt.max_net=MathMax(0.0,peak);
      DiagCaptureSignature(fallback_e,g_snapshot,rt.fp,rt.fp_decision,rt.fp_ready,rt.hist_count,rt.hist_csv);
   }
   double mfe=MathMax(peak,rt.max_net);
   double mae=MathMin(rt.min_net,MathMin(0.0,final_net));
   double giveback=MathMax(0.0,mfe-final_net);
   double retention=(mfe>0.0 ? 100.0*final_net/mfe : 0.0);
   double costs=entry_costs+exit_commission+swap+exit_fee;
   long duration_ms=exit_msc-rt.entry_msc;
   if(duration_ms<0) duration_ms=0;
   double duration=(double)duration_ms/1000.0;

   string row=StringFormat("%s,%s,%I64d,%s,%I64d,%s,%I64d,%s,%s,%I64u,%I64u,%I64u,%s,%.6f,%.5f,%.5f,%.3f,"
                           "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%s,%s,%d,%s,%d,"
                           "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%s,",
                           DIAG2025_BUILD,TimeToString(rt.decision_time,TIME_DATE|TIME_SECONDS),rt.decision_msc,
                           TimeToString(rt.entry_time,TIME_DATE|TIME_SECONDS),rt.entry_msc,
                           TimeToString(exit_time,TIME_DATE|TIME_SECONDS),exit_msc,CsvSafe(g_symbol),EnumToString((ENUM_TIMEFRAMES)_Period),rt.sequence_id,rt.edge_id,identifier,
                           SideName(rt.side),rt.volume,rt.entry_price,exit_price,duration,
                           gross_price_profit,entry_costs,exit_commission,swap,exit_fee,costs,final_net,peak,mfe,mae,giveback,retention,
                           DiagProfitClass(final_net),DiagProfitClass(mfe),exit_code,DiagExitSource(exit_code),exit_mode,
                           rt.edge.score,rt.edge.score_gap,rt.edge.score_shape,rt.edge.score_context,rt.edge.score_extra,rt.edge.score_threshold,CsvSafe(rt.edge.reason));
   row+=DiagSignatureValues(rt.snap,rt.fp,rt.fp_decision,rt.fp_ready,rt.hist_count,rt.hist_csv);
   DiagAppendSegmented(g_diag_trades_file,"TRADES",exit_time,DiagTradeHeader(),row);

   DiagDailyTouch(exit_time);
   g_diag_day_closed++;
   if(rt.side==SIDE_BUY) g_diag_day_closed_buy++; else if(rt.side==SIDE_SELL) g_diag_day_closed_sell++;
   string cls=DiagProfitClass(final_net);
   if(cls=="LOSS") g_diag_day_loss++; else if(cls=="0_3") g_diag_day_0_3++; else if(cls=="3_5") g_diag_day_3_5++; else g_diag_day_5p++;
   g_diag_day_net+=final_net;
   g_diag_day_gross+=gross_price_profit;
   g_diag_day_costs+=costs;
   if(idx>=0) g_diag_real[idx].active=false;
}

int DiagRejectSlot()
{
   for(int i=0;i<ArraySize(g_diag_rejects);i++) if(!g_diag_rejects[i].active) return i;
   int n=ArraySize(g_diag_rejects); ArrayResize(g_diag_rejects,n+1); return n;
}

void DiagStartReject(const EdgeContext &e,const MarketSnapshot &s,string origin)
{
   if(e.side==SIDE_NONE) return;
   int idx=DiagRejectSlot();
   ZeroMemory(g_diag_rejects[idx]);
   g_diag_rejects[idx].active=true;
   g_diag_rejects[idx].edge_id=e.edge_id;
   g_diag_rejects[idx].side=e.side;
   g_diag_rejects[idx].start_time=s.time;
   g_diag_rejects[idx].start_msc=s.tick_time_msc;
   g_diag_rejects[idx].entry_price=(e.side==SIDE_BUY ? s.ask : s.bid);
   g_diag_rejects[idx].health_peak_live=MathMax(0.000001,(e.side==SIDE_BUY ? s.live_gap_atr : -s.live_gap_atr));
   g_diag_rejects[idx].max_net=-BASSO_ROUNDTRIP_COST_RESERVE_EUR;
   g_diag_rejects[idx].min_net=-BASSO_ROUNDTRIP_COST_RESERVE_EUR;
   g_diag_rejects[idx].max_net_before_hard=-BASSO_ROUNDTRIP_COST_RESERVE_EUR;
   g_diag_rejects[idx].hard_loss_seen=false;
   g_diag_rejects[idx].origin=origin;
   g_diag_rejects[idx].origin_reason=e.reason;
   g_diag_rejects[idx].edge=e;
   g_diag_rejects[idx].snap=s;
   DiagCaptureSignature(e,s,g_diag_rejects[idx].fp,g_diag_rejects[idx].fp_decision,g_diag_rejects[idx].fp_ready,g_diag_rejects[idx].hist_count,g_diag_rejects[idx].hist_csv);
   DiagDailyTouch(DiagDayClockFromSnapshot(s));
   g_diag_day_reject_started++;
}

string DiagRejectHeader()
{
   return "diag_build,start_time,start_msc,end_time,end_msc,symbol,tester_tf,edge_id,side,origin,origin_reason,duration_sec,entry_price,end_price,"
          "end_net,structural_mfe,structural_mae,survivable_mfe_before_hard,hard_loss_seen,hard_loss_time,potential_class,survivable_class,end_health,end_drop_pct,end_status,"
          "entry_score,score_gap,score_shape,score_context,score_extra,score_threshold,"+DiagSignatureHeader();
}

void DiagWriteReject(const DiagRejectTrack &rt,datetime end_time,long end_msc,double end_price,double end_net,double health,double drop_pct,string end_status)
{
   long duration_ms=end_msc-rt.start_msc;
   if(duration_ms<0) duration_ms=0;
   double duration=(double)duration_ms/1000.0;
   double surv=(rt.hard_loss_seen ? rt.max_net_before_hard : rt.max_net);
   string row=StringFormat("%s,%s,%I64d,%s,%I64d,%s,%s,%I64u,%s,%s,%s,%.3f,%.5f,%.5f,"
                           "%.2f,%.2f,%.2f,%.2f,%d,%s,%s,%s,%.2f,%.2f,%s,"
                           "%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,",
                           DIAG2025_BUILD,TimeToString(rt.start_time,TIME_DATE|TIME_SECONDS),rt.start_msc,
                           TimeToString(end_time,TIME_DATE|TIME_SECONDS),end_msc,CsvSafe(g_symbol),EnumToString((ENUM_TIMEFRAMES)_Period),rt.edge_id,SideName(rt.side),
                           CsvSafe(rt.origin),CsvSafe(rt.origin_reason),duration,rt.entry_price,end_price,
                           end_net,rt.max_net,rt.min_net,surv,rt.hard_loss_seen?1:0,
                           rt.hard_loss_seen?TimeToString(rt.hard_loss_time,TIME_DATE|TIME_SECONDS):"NA",
                           DiagProfitClass(rt.max_net),DiagProfitClass(surv),health,drop_pct,CsvSafe(end_status),
                           rt.edge.score,rt.edge.score_gap,rt.edge.score_shape,rt.edge.score_context,rt.edge.score_extra,rt.edge.score_threshold);
   row+=DiagSignatureValues(rt.snap,rt.fp,rt.fp_decision,rt.fp_ready,rt.hist_count,rt.hist_csv);
   DiagAppendSegmented(g_diag_rejects_file,"REJECTS",rt.start_time,DiagRejectHeader(),row);

   DiagDailyTouch(end_time);
   g_diag_day_reject_completed++;
   string cls=DiagProfitClass(rt.max_net);
   if(cls=="LOSS") g_diag_day_reject_loss++; else if(cls=="0_3") g_diag_day_reject_0_3++; else if(cls=="3_5") g_diag_day_reject_3_5++; else g_diag_day_reject_5p++;
}

void DiagUpdateRejectTracks()
{
   double slip_reserve=(double)InpSlippagePoints*MoneyPerPointPerLot()*BASSO_FIXED_LOT;
   double hard_trigger=HardLossServerTargetNet()+slip_reserve;
   for(int i=0;i<ArraySize(g_diag_rejects);i++)
   {
      if(!g_diag_rejects[i].active) continue;
      TradeSide side=g_diag_rejects[i].side;
      double now=(side==SIDE_BUY ? g_snapshot.bid : g_snapshot.ask);
      double p=HypotheticalNetFromPrice(side,g_diag_rejects[i].entry_price,now)-BASSO_ROUNDTRIP_COST_RESERVE_EUR;
      if(p>g_diag_rejects[i].max_net) g_diag_rejects[i].max_net=p;
      if(p<g_diag_rejects[i].min_net) g_diag_rejects[i].min_net=p;
      if(!g_diag_rejects[i].hard_loss_seen)
      {
         if(p>g_diag_rejects[i].max_net_before_hard) g_diag_rejects[i].max_net_before_hard=p;
         if(p<=hard_trigger)
         {
            g_diag_rejects[i].hard_loss_seen=true;
            g_diag_rejects[i].hard_loss_time=TimeCurrent();
         }
      }

      double signed_live=(side==SIDE_BUY ? g_snapshot.live_gap_atr : -g_snapshot.live_gap_atr);
      if(signed_live>g_diag_rejects[i].health_peak_live) g_diag_rejects[i].health_peak_live=signed_live;
      double health=PositionEdgeHealthV2(side,g_snapshot,g_diag_rejects[i].health_peak_live);
      double drop=StructuralLiveGapDropPctV46(side,g_snapshot,g_diag_rejects[i].health_peak_live);
      if(!StructuralEdgeDyingV46(side,health,drop)) continue;

      DiagWriteReject(g_diag_rejects[i],TimeCurrent(),g_snapshot.tick_time_msc,now,p,health,drop,"STRUCTURAL_EDGE_DYING");
      g_diag_rejects[i].active=false;
   }
}

void DiagFlushOpenRejects(string status)
{
   for(int i=0;i<ArraySize(g_diag_rejects);i++)
   {
      if(!g_diag_rejects[i].active) continue;
      TradeSide side=g_diag_rejects[i].side;
      double now=(side==SIDE_BUY ? g_snapshot.bid : g_snapshot.ask);
      double p=HypotheticalNetFromPrice(side,g_diag_rejects[i].entry_price,now)-BASSO_ROUNDTRIP_COST_RESERVE_EUR;
      double signed_live=(side==SIDE_BUY ? g_snapshot.live_gap_atr : -g_snapshot.live_gap_atr);
      if(signed_live>g_diag_rejects[i].health_peak_live) g_diag_rejects[i].health_peak_live=signed_live;
      double health=PositionEdgeHealthV2(side,g_snapshot,g_diag_rejects[i].health_peak_live);
      double drop=StructuralLiveGapDropPctV46(side,g_snapshot,g_diag_rejects[i].health_peak_live);
      DiagWriteReject(g_diag_rejects[i],TimeCurrent(),g_snapshot.tick_time_msc,now,p,health,drop,status);
      g_diag_rejects[i].active=false;
   }
}

string DiagPostCloseHeader()
{
   return "diag_build,time,symbol,tester_tf,sequence_id,side,exit_source,exit_mode,entry_score,score_threshold,score_gap,score_shape,score_context,score_extra,"
          "exit_net,preclose_peak,missed_after_close,post_close_potential,giveback,end_health,verdict";
}

void DiagWritePostClose(const PostCloseTrack &p,double missed,double postclose_potential,double giveback,double health,string verdict)
{
   string row=StringFormat("%s,%s,%s,%s,%I64u,%s,%s,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%s",
                           DIAG2025_BUILD,TimeToString(TimeCurrent(),TIME_DATE|TIME_SECONDS),CsvSafe(g_symbol),EnumToString((ENUM_TIMEFRAMES)_Period),p.sequence_id,SideName(p.side),
                           DiagExitSource(p.exit_code),p.exit_mode,p.entry_score,p.threshold,p.score_gap,p.score_shape,p.score_context,p.score_extra,
                           p.exit_net,p.preclose_peak,missed,postclose_potential,giveback,health,CsvSafe(verdict));
   DiagAppendSegmented(g_diag_postclose_file,"POSTCLOSE",TimeCurrent(),DiagPostCloseHeader(),row);
}

string DiagDailyHeader()
{
   return "diag_build,date,symbol,tester_tf,start_balance,start_equity,end_balance,end_equity,entry_requests,entries,closed,closed_buy,closed_sell,"
          "loss_count,net_0_3_count,net_3_5_count,net_5_plus_count,day_gross,day_costs_signed,day_net,reject_started,reject_completed,"
          "reject_loss,reject_0_3,reject_3_5,reject_5_plus";
}

// DAILY accounting clock: decision_time / TimeCurrent only. NEVER bar_time (rates[1].time).
datetime DiagDayClockFromSnapshot(const MarketSnapshot &s)
{
   if(s.decision_time>0) return s.decision_time;
   return TimeCurrent();
}

void DiagResetDaily(datetime t)
{
   // t must be decision/current clock, not completed-bar time.
   datetime clock=(t>0 ? t : TimeCurrent());
   MqlDateTime dt; TimeToStruct(clock,dt);
   g_diag_day_key=dt.year*10000+dt.mon*100+dt.day;
   g_diag_day_anchor=clock;
   g_diag_day_start_balance=AccountInfoDouble(ACCOUNT_BALANCE);
   g_diag_day_start_equity=AccountInfoDouble(ACCOUNT_EQUITY);
   g_diag_day_entry_requests=0; g_diag_day_entries=0; g_diag_day_closed=0; g_diag_day_closed_buy=0; g_diag_day_closed_sell=0;
   g_diag_day_loss=0; g_diag_day_0_3=0; g_diag_day_3_5=0; g_diag_day_5p=0;
   g_diag_day_net=0.0; g_diag_day_gross=0.0; g_diag_day_costs=0.0;
   g_diag_day_reject_started=0; g_diag_day_reject_completed=0; g_diag_day_reject_loss=0; g_diag_day_reject_0_3=0; g_diag_day_reject_3_5=0; g_diag_day_reject_5p=0;
}

void DiagFlushDaily()
{
   if(g_diag_day_key==0 || g_diag_day_anchor==0) return;
   MqlDateTime dt; TimeToStruct(g_diag_day_anchor,dt);
   string date=StringFormat("%04d.%02d.%02d",dt.year,dt.mon,dt.day);
   string row=StringFormat("%s,%s,%s,%s,%.2f,%.2f,%.2f,%.2f,%d,%d,%d,%d,%d,%d,%d,%d,%d,%.2f,%.2f,%.2f,%d,%d,%d,%d,%d,%d",
                           DIAG2025_BUILD,date,CsvSafe(g_symbol),EnumToString((ENUM_TIMEFRAMES)_Period),g_diag_day_start_balance,g_diag_day_start_equity,
                           AccountInfoDouble(ACCOUNT_BALANCE),AccountInfoDouble(ACCOUNT_EQUITY),
                           g_diag_day_entry_requests,g_diag_day_entries,g_diag_day_closed,g_diag_day_closed_buy,g_diag_day_closed_sell,
                           g_diag_day_loss,g_diag_day_0_3,g_diag_day_3_5,g_diag_day_5p,g_diag_day_gross,g_diag_day_costs,g_diag_day_net,
                           g_diag_day_reject_started,g_diag_day_reject_completed,g_diag_day_reject_loss,g_diag_day_reject_0_3,g_diag_day_reject_3_5,g_diag_day_reject_5p);
   DiagAppendSegmented(g_diag_daily_file,"DAILY",g_diag_day_anchor,DiagDailyHeader(),row);
}

void DiagDailyTouch(datetime t)
{
   // t = decision clock / TimeCurrent / deal clock. bar_time must never be passed here.
   datetime clock=(t>0 ? t : TimeCurrent());
   MqlDateTime dt; TimeToStruct(clock,dt);
   int key=dt.year*10000+dt.mon*100+dt.day;
   if(g_diag_day_key==0) { DiagResetDaily(clock); return; }
   if(key!=g_diag_day_key)
   {
      DiagFlushDaily();
      DiagResetDaily(clock);
   }
}

string DiagCandidateHeader()
{
   return "diag_build,ops_tf,tester_tf,delta_horizon_bars,bar_time,decision_time,tick_msc,symbol,sequence_id,edge_id,side,engine,"
          "decision,reject_reason,score_threshold_ref,entry_score,score_gap,score_shape,score_context,score_extra,"
          "continuation,recovery,bar_open,bar_high,bar_low,bar_close,atr_ops,atr_m5,atr_m15,body,range,body_atr,range_atr,close_extreme,"
          "ema3,ema8,gap_atr,delta30_atr,buy_health,sell_health,spread_pts,live_ema3,live_ema8,live_gap_atr,"
          "f0_signed_gap,f1_signed_live,f2_accel,f3_dir_body,f4_range_atr,f5_delta30_atr,f6_dir_close,f7_body_over_range,"
          "f8_atr_ops,f9_spread_pts,f10_close_ema3,f11_close_ema8,"
          "fp_ready,fp_bad_match,fp_good_match,fp_bad_id,fp_good_id,fp_decision,fp_m5_calibration,hist_count,"+DiagHistoryHeader()+","
          "v55_buy_score,v55_sell_score,v55_selected_score,v55_selected_side,v55_edge_state,v55_edge_id,v55_entry_authority,v55_reentry_armed,v55_old_score_decision,v55_old_fp_decision";
}

void DiagWriteM5Candidate(const MarketSnapshot &s,const EdgeContext &e,string decision,string reject_reason,
                          const FingerprintDecision &fp,bool fp_eval)
{
   if(e.side!=SIDE_BUY && e.side!=SIDE_SELL) return;
   double f[]; BuildEntryBrainFeatures(s,e.side,f);
   FingerprintFeatures ff; FingerprintDecision fd=fp; bool ready=false; int hist_count=0; string hist_csv="";
   if(fp_eval)
   {
      ready=BuildFingerprintFeatures(e.side,ff);
      hist_count=g_fp_hist_count;
      hist_csv=DiagHistoryValues(e.side);
   }
   else
   {
      ZeroMemory(ff); ZeroMemory(fd);
      fd.bad_id="NA"; fd.good_id="NA"; fd.decision="NOT_EVALUATED";
      hist_count=g_fp_hist_count;
      hist_csv=DiagHistoryValues(e.side);
   }
   datetime bar_t=(s.bar_time>0 ? s.bar_time : s.time);
   datetime dec_t=(s.decision_time>0 ? s.decision_time : TimeCurrent());
   string row=StringFormat("%s,%s,%s,%d,%s,%s,%I64d,%s,%I64u,%I64u,%s,%s,",
                           DIAG2025_BUILD,EnumToString((ENUM_TIMEFRAMES)BASSO_OPS_TF),EnumToString((ENUM_TIMEFRAMES)_Period),
                           BASSO_DELTA_HORIZON_BARS,
                           TimeToString(bar_t,TIME_DATE|TIME_SECONDS),TimeToString(dec_t,TIME_DATE|TIME_SECONDS),
                           s.tick_time_msc,CsvSafe(g_symbol),
                           e.sequence_id,e.edge_id,SideName(e.side),EngineName(e.engine));
   row+=StringFormat("%s,%s,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%d,%d,",
                           CsvSafe(decision),CsvSafe(reject_reason),e.score_threshold,e.score,e.score_gap,e.score_shape,e.score_context,e.score_extra,
                           e.continuation?1:0,e.recovery?1:0);
   row+=StringFormat("%.5f,%.5f,%.5f,%.5f,%.8f,%.8f,%.8f,%.8f,%.8f,%.6f,%.6f,%.6f,",
                           s.bar_open,s.bar_high,s.bar_low,s.bar_close,s.atr_m1,s.atr_m5,s.atr_m15,s.body_m1,s.range_m1,s.body_atr,s.range_atr,s.close_extreme);
   row+=StringFormat("%.8f,%.8f,%.6f,%.6f,%.2f,%.2f,%.1f,%.8f,%.8f,%.6f,",
                           s.ema3,s.ema8,s.gap_atr,s.delta30_atr,s.buy_health,s.sell_health,s.spread_pts,s.live_ema3,s.live_ema8,s.live_gap_atr);
   row+=StringFormat("%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,",
                           f[0],f[1],f[2],f[3],f[4],f[5],f[6],f[7],f[8],f[9],f[10],f[11]);
   row+=StringFormat("%d,%.2f,%.2f,%s,%s,%s,%s,%d,",
                           ready?1:0,fd.bad_match,fd.good_match,CsvSafe(fd.bad_id),CsvSafe(fd.good_id),CsvSafe(fd.decision),
                           "M1_BASELINE_NEEDS_M5_CALIBRATION",hist_count);
   row+=hist_csv;
   row+=StringFormat(",%.2f,%.2f,%.2f,%s,%s,%I64u,%s,%d,%s,%s",
                           g_diag_v55_buy_score,g_diag_v55_sell_score,g_diag_v55_selected_score,
                           SideName(g_diag_v55_selected_side),V55EdgeStateName(g_v55.state),g_v55.edge_id,
                           CsvSafe(g_diag_v55_entry_authority),(g_v55.reentry_armed?1:0),
                           CsvSafe(g_diag_v55_old_score_decision),CsvSafe(g_diag_v55_old_fp_decision));
   DiagAppendSegmented(g_diag_candidates_file,"CANDIDATES",dec_t,DiagCandidateHeader(),row);
   DiagDailyTouch(dec_t);
}

// V54: BuyEntryScore coefficients are M1-era baseline formulas applied to M5 OPS features.
// Thresholds 55/75 remain diagnostic reference only — NOT claimed M5-calibrated.
double BuyEntryScore(const MarketSnapshot &s,double &gapPart,double &shapePart,double &contextPart,double &extraPart)
{
   if(s.atr_m1<=0.0 || s.gap_atr<=0.0)
   {
      gapPart=shapePart=contextPart=extraPart=0.0;
      return 0.0;
   }

   double f[]; BuildEntryBrainFeatures(s,SIDE_BUY,f);

   gapPart =
      14.437731726809506*f[2] +
       1.2914271587628525*f[1] -
       0.5614474628087706*f[0];

   shapePart =
      -0.7425180038151135*f[3] +
       0.8068207159600087*f[7] +
       0.22305850503488794*f[4];

   contextPart =
       0.10249211033551898*f[8] +
       0.043339450445100076*f[5];

   extraPart =
      -0.10290274089169496*f[9];

   double raw=-1.5307978165201834 + gapPart + shapePart + contextPart + extraPart;
   return SigmoidScore100(raw);
}

// V46 BUY ContinuationScore: retained from V45 and independent from the normal BUY EntryScore.
// Calibration sample: 41 V41 BUY STEP floor exits; target = >=2.00 EUR additional movement
// after protected exit before the same EdgeHealth-V2 death boundary (32 positive / 9 negative).
// Inputs are structural only. The +0.401341 shift maps the V41 fitted probability 0.45 to score 55, so the existing
// BUY numeric threshold remains usable without creating a second execution authority.
double BuyContinuationScore(const MarketSnapshot &s,double health,
                            double &gapPart,double &shapePart,double &contextPart,double &extraPart)
{
   if(s.atr_m1<=0.0 || s.gap_atr<=0.0)
   {
      gapPart=shapePart=contextPart=extraPart=0.0;
      return 0.0;
   }

   double f[]; BuildEntryBrainFeatures(s,SIDE_BUY,f);
   double signed_live=f[1];
   double accel=f[2];

   gapPart =
       0.36788806799677176*signed_live -
       1.63394420278258400*accel;

   shapePart =
       2.11176072023405050*f[6];

   contextPart =
      -0.04255416482776663*f[5] -
       0.39143101347217646*f[10] +
       0.11482453454005298*f[11];

   extraPart =
       0.011458198336760907*health;

   double raw=-2.4039521550967806 + gapPart + shapePart + contextPart + extraPart;
   return SigmoidScore100(raw+0.40134139092430250);
}

// V40 SELL EntryScore: M1-era continuous model coefficients applied to M5 OPS features in V54.
 // Fitted on V39 accepted + mirror-rejected candidates (M1). Threshold 80 = diagnostic baseline only on M5.
 // Runtime inputs are structural only; BUY/SELL remain independent brains.
double SellEntryScore(const MarketSnapshot &s,double &gapPart,double &shapePart,double &contextPart,double &extraPart)
{
   if(s.atr_m1<=0.0 || s.gap_atr>=0.0)
   {
      gapPart=shapePart=contextPart=extraPart=0.0;
      return 0.0;
   }

   double f[]; BuildEntryBrainFeatures(s,SIDE_SELL,f);

   gapPart =
       13.171129*f[2] +
        0.260132*f[1] -
        0.754227*f[0];

   shapePart =
       -0.070161*f[3] -
        0.509711*f[4] +
        1.015278*f[6];

   contextPart =
        0.029814*f[5] +
        1.419079*f[10] +
        0.312147*f[11];

   extraPart=0.0;

   double raw=-0.570237 + gapPart + shapePart + contextPart + extraPart;
   return SigmoidScore100(raw);
}

// V46 RECOVERY SCORE (diagnostic only).
// The coefficients remain the data-driven hard-loss recovery classifier. BUY becomes operational ONLY
// after a real hard-loss, after a fresh completed M1 bar, and only while the same structural BUY edge
// survives the V45 death detector. SELL remains diagnostic only.
double BuyRecoveryScoreV46(const MarketSnapshot &s)
{
   if(s.atr_m1<=0.0 || s.gap_atr<=0.0) return 0.0;
   double f[]; BuildEntryBrainFeatures(s,SIDE_BUY,f);
   double raw=1.7325018686967955
      +0.44507166939725495*f[0]
      +0.47740690323134444*f[1]
      -2.28132133010267200*f[2]
      -0.02832454991592364*f[3]
      -0.04279090854436482*f[4]
      +0.03179513559329627*f[5]
      -0.24982006236984250*f[6]
      -0.29157995003804515*f[7]
      +0.00137138888023207*f[8]
      -0.18230812460166076*f[9]
      +0.21237783927416365*f[10]
      +0.22987364035470279*f[11];
   return SigmoidScore100(raw);
}

double SellRecoveryScoreV46(const MarketSnapshot &s)
{
   if(s.atr_m1<=0.0 || s.gap_atr>=0.0) return 0.0;
   double f[]; BuildEntryBrainFeatures(s,SIDE_SELL,f);
   double raw=0.2535306958292231
      -0.20342724706379364*f[0]
      -0.23324515895603118*f[1]
      -1.58010225202678400*f[2]
      -0.22202354092155743*f[3]
      +0.05325688676255359*f[4]
      +0.04641940225691789*f[5]
      +2.36233025280327520*f[6]
      -1.23172796648784090*f[7]
      +0.10372760946322641*f[8]
      -0.13398581908774300*f[9]
      -0.22092601958398650*f[10]
      -0.11391035994301117*f[11];
   return SigmoidScore100(raw);
}

// V46 structural EdgeHealth V2 — coefficients are M1-era baseline.
// V54 feeds OPS(M5) structural features; thresholds remain diagnostic baseline (NOT M5-recalibrated).
// Trained from V39 tick-by-tick structural state against whether the trade later proved
// capable of reaching the approved +2 EUR quality milestone. Profit/loss is NOT an input.
// Includes live/completed gap, acceleration, candle structure, 30m context, EMA location,
// and deterioration from the strongest signed live gap seen since the edge started.
double PositionEdgeHealthV2(TradeSide side,const MarketSnapshot &s,double peak_signed_live)
{
   if(side!=SIDE_BUY && side!=SIDE_SELL) return 0.0;
   if(s.atr_m1<=0.0) return 0.0;

   double sign=(side==SIDE_BUY ? 1.0 : -1.0);
   double range=(s.range_atr>0.0 ? s.range_atr : 0.0);
   double signed_live=sign*s.live_gap_atr;
   double signed_gap=sign*s.gap_atr;
   double accel=signed_live-signed_gap;
   double dir_body=sign*(s.bar_close-s.bar_open)/s.atr_m1;
   double dir_close=(side==SIDE_BUY ? s.close_extreme : 1.0-s.close_extreme);
   double dir_delta30=sign*s.delta30_atr;
   double close_ema3=sign*(s.bar_close-s.ema3)/s.atr_m1;
   double close_ema8=sign*(s.bar_close-s.ema8)/s.atr_m1;
   double peak=MathMax(0.000001,peak_signed_live);
   double live_retention=signed_live/peak;
   if(live_retention<0.0) live_retention=0.0;
   if(live_retention>1.25) live_retention=1.25;

   double raw=0.0;
   if(side==SIDE_BUY)
   {
      raw=-0.377015
          +1.662188*signed_live
          +0.419526*signed_gap
          +11.898598*accel
          -0.779859*dir_body
          -2.269941*range
          +1.989007*dir_close
          +0.132753*dir_delta30
          -0.211871*close_ema3
          +0.085782*close_ema8
          +0.066071*live_retention;
   }
   else
   {
      raw=-10.916741
          +1.990544*signed_live
          +1.135588*signed_gap
          +8.392132*accel
          -1.453970*dir_body
          +0.724123*range
          +7.303835*dir_close
          -0.165627*dir_delta30
          -2.187840*close_ema3
          -0.094808*close_ema8
          +4.210200*live_retention;
   }
   return SigmoidScore100(raw);
}

// V46 structural deterioration is independent of money.
double StructuralLiveGapDropPctV46(TradeSide side,const MarketSnapshot &s,double peak_signed_live)
{
   if(side!=SIDE_BUY && side!=SIDE_SELL) return 100.0;
   double signed_live=(side==SIDE_BUY ? s.live_gap_atr : -s.live_gap_atr);
   double peak=MathMax(0.000001,peak_signed_live);
   double rel=100.0*(signed_live/peak);
   if(rel>125.0) rel=125.0;
   return 100.0-rel;
}

bool StructuralEdgeDyingV46(TradeSide side,double health,double live_gap_drop_pct)
{
   if(side==SIDE_BUY)
   {
      if(health<InpBuyEdgeAliveHealthV2) return true; // hard structural death
      return (health<InpBuyEdgeDyingHealthV46 &&
              live_gap_drop_pct>=InpBuyEdgeDyingDropPctV46);
   }
   if(side==SIDE_SELL)
   {
      if(health<InpSellEdgeAliveHealthV2) return true; // hard structural death
      return (health<InpSellEdgeDyingHealthV46 &&
              live_gap_drop_pct>=InpSellEdgeDyingDropPctV46);
   }
   return true;
}

bool StructuralEdgeWeakV46(TradeSide side,double health,double live_gap_drop_pct)
{
   if(StructuralEdgeDyingV46(side,health,live_gap_drop_pct)) return false;
   if(side==SIDE_BUY)
      return (health<InpBuyEdgeWeakHealthV46 && live_gap_drop_pct>=InpBuyEdgeWeakDropPctV46);
   if(side==SIDE_SELL)
      return (health<InpSellEdgeWeakHealthV46 && live_gap_drop_pct>=InpSellEdgeWeakDropPctV46);
   return false;
}

EdgeStateV46 StructuralEdgeStateV46(TradeSide side,double health,double live_gap_drop_pct)
{
   if(StructuralEdgeDyingV46(side,health,live_gap_drop_pct)) return EDGE_STATE_DYING;
   if(StructuralEdgeWeakV46(side,health,live_gap_drop_pct)) return EDGE_STATE_WEAK;
   return EDGE_STATE_STRONG;
}

bool StructuralEdgeAliveV46(TradeSide side,double health,double live_gap_drop_pct)
{
   return StructuralEdgeStateV46(side,health,live_gap_drop_pct)!=EDGE_STATE_DYING;
}

string EdgeStateNameV46(EdgeStateV46 state)
{
   if(state==EDGE_STATE_DYING) return "DYING";
   if(state==EDGE_STATE_WEAK) return "WEAK";
   return "STRONG";
}

// Recovery scores remain diagnostic only in V46; no hard-loss recovery candidate can enter the execution pipeline.

double StepLockFloorFromPeak(double peak)
{
   // V50 inherits the exact V42 STEP scale ONLY as a diagnostic comparison reference.
   if(peak<BASSO_PEAK_ARM_EUR) return 0.0;
   if(peak<3.00) return BASSO_MIN_PROTECTED_EUR;
   if(peak<4.00) return 1.90;
   if(peak<5.00) return 2.30;
   if(peak<6.50) return 3.10;
   if(peak<8.00) return 4.00;
   if(peak<10.00) return 4.90;
   if(peak<12.50) return 6.60;
   if(peak<15.00) return 8.50;
   if(peak<20.00) return 10.75;
   if(peak<25.00) return 13.00;
   if(peak<30.00) return 17.50;
   if(peak<40.00) return 22.00;
   if(peak<50.00) return 26.50;
   if(peak<75.00) return 35.50;
   if(peak<100.00) return 45.00;
   if(peak<150.00) return 68.00;
   if(peak<200.00) return 92.00;
   return MathMax(140.00,peak*0.80);
}

// V50 inherits the V48-approved side-specific monotonic Profit Vault. Profit is NOT an edge-state input.
// Above the final calibrated V47 band (20 EUR Peak), the last approved floor is held constant
// while the dynamic TP and structural edge continue without a profit ceiling.
double ProfitVaultFloorFromPeak(TradeSide side,double peak)
{
   if(side!=SIDE_BUY && side!=SIDE_SELL) return 0.0;
   if(peak<BASSO_VAULT_MICRO_ARM_EUR) return 0.0;

   if(side==SIDE_BUY)
   {
      if(peak<2.00)  return BASSO_VAULT_MICRO_FLOOR_EUR;
      if(peak<3.00)  return 1.40;
      if(peak<4.00)  return 1.40;
      if(peak<5.00)  return 2.50;
      if(peak<6.50)  return 2.50;
      if(peak<8.00)  return 4.00;
      if(peak<10.00) return 6.50;
      if(peak<12.50) return 7.00;
      if(peak<15.00) return 9.00;
      if(peak<20.00) return 9.00;
      return 10.00;
   }

   if(peak<2.00)  return BASSO_VAULT_MICRO_FLOOR_EUR;
   if(peak<3.00)  return 0.90;
   if(peak<4.00)  return 1.00;
   if(peak<5.00)  return 2.50;
   if(peak<6.50)  return 2.80;
   if(peak<8.00)  return 5.00;
   if(peak<10.00) return 6.50;
   if(peak<12.50) return 7.00;
   if(peak<15.00) return 10.00;
   if(peak<20.00) return 12.00;
   return 12.00;
}

// V50 approved SELL-only Micro Emergency Vault pre-alert.
// Money does NOT decide edge state: Peak only gates whether the +0.30 retention floor may exist;
// Health and live-gap deterioration are structural inputs already used by the edge model.
bool SellMicroEmergencyVaultEligible(TradeSide side,double peak,double health,double live_gap_drop_pct)
{
   if(side!=SIDE_SELL) return false;
   if(peak<BASSO_SELL_MICRO_EMERGENCY_ARM_EUR || peak>=BASSO_VAULT_MICRO_ARM_EUR) return false;
   if(health>BASSO_SELL_MICRO_EMERGENCY_HEALTH_MAX) return false;
   if(live_gap_drop_pct<BASSO_SELL_MICRO_EMERGENCY_DROP_MIN_PCT) return false;
   return true;
}

// Virtual runner objective only. It NEVER closes a live edge and is never sent to the broker.
// The approved V42 peak milestones become successive objectives; above 200 EUR the objective
// remains unbounded by staying 25% ahead of the latest achieved ProfitPeak.
double DynamicTPFromPeak(double peak)
{
   if(peak<2.00) return 2.00;
   if(peak<3.00) return 3.00;
   if(peak<4.00) return 4.00;
   if(peak<5.00) return 5.00;
   if(peak<6.50) return 6.50;
   if(peak<8.00) return 8.00;
   if(peak<10.00) return 10.00;
   if(peak<12.50) return 12.50;
   if(peak<15.00) return 15.00;
   if(peak<20.00) return 20.00;
   if(peak<25.00) return 25.00;
   if(peak<30.00) return 30.00;
   if(peak<40.00) return 40.00;
   if(peak<50.00) return 50.00;
   if(peak<75.00) return 75.00;
   if(peak<100.00) return 100.00;
   if(peak<150.00) return 150.00;
   if(peak<200.00) return 200.00;
   return peak*1.25;
}

void AddRejectTrack(const EdgeContext &e,const MarketSnapshot &s,string origin)
{
   if(e.side==SIDE_NONE) return;
   DiagStartReject(e,s,origin);
   int n=ArraySize(g_reject_tracks); ArrayResize(g_reject_tracks,n+1);
   g_reject_tracks[n].active=true;
   g_reject_tracks[n].edge_id=e.edge_id;
   g_reject_tracks[n].side=e.side;
   g_reject_tracks[n].start_time=s.time;
   g_reject_tracks[n].entry_price=(e.side==SIDE_BUY?s.ask:s.bid);
   g_reject_tracks[n].entry_gap=s.gap_atr;
   g_reject_tracks[n].health_peak_live=MathMax(0.000001,(e.side==SIDE_BUY ? s.live_gap_atr : -s.live_gap_atr));
   g_reject_tracks[n].max_net=-BASSO_ROUNDTRIP_COST_RESERVE_EUR;
   g_reject_tracks[n].min_net=-BASSO_ROUNDTRIP_COST_RESERVE_EUR;
   g_reject_tracks[n].entry_score=e.score;
   g_reject_tracks[n].threshold=e.score_threshold;
   g_reject_tracks[n].score_gap=e.score_gap;
   g_reject_tracks[n].score_shape=e.score_shape;
   g_reject_tracks[n].score_context=e.score_context;
   g_reject_tracks[n].score_extra=e.score_extra;
   g_reject_tracks[n].origin=origin;
   g_reject_tracks[n].origin_reason=e.reason;
}

double HypotheticalNetFromPrice(TradeSide side,double entry,double now)
{
   double mpp=MoneyPerPointPerLot();
   double pt=SymbolInfoDouble(g_symbol,SYMBOL_POINT);
   if(mpp<=0.0 || pt<=0.0) return 0.0;
   double pts=(side==SIDE_BUY ? (now-entry)/pt : (entry-now)/pt);
   return pts*mpp*BASSO_FIXED_LOT;
}

void UpdateRejectTracks()
{
   for(int i=0;i<ArraySize(g_reject_tracks);i++)
   {
      if(!g_reject_tracks[i].active) continue;

      TradeSide side=g_reject_tracks[i].side;
      double now=(side==SIDE_BUY ? g_snapshot.bid : g_snapshot.ask);

      // Mirror realizable net: entry at historical ask/bid, current exit at bid/ask
      // already includes spread; observed V34 0.01 round-trip deal costs are deducted too.
      double p=HypotheticalNetFromPrice(side,g_reject_tracks[i].entry_price,now)
               -BASSO_ROUNDTRIP_COST_RESERVE_EUR;
      if(p>g_reject_tracks[i].max_net) g_reject_tracks[i].max_net=p;
      if(p<g_reject_tracks[i].min_net) g_reject_tracks[i].min_net=p;

      double signed_live=(side==SIDE_BUY ? g_snapshot.live_gap_atr : -g_snapshot.live_gap_atr);
      if(signed_live>g_reject_tracks[i].health_peak_live)
         g_reject_tracks[i].health_peak_live=signed_live;
      double health=PositionEdgeHealthV2(side,g_snapshot,g_reject_tracks[i].health_peak_live);
      double live_gap_drop_pct=StructuralLiveGapDropPctV46(side,g_snapshot,g_reject_tracks[i].health_peak_live);
      bool edge_dying=StructuralEdgeDyingV46(side,health,live_gap_drop_pct);
      bool edge_alive=!edge_dying;

      double slip_reserve=(double)InpSlippagePoints*MoneyPerPointPerLot()*BASSO_FIXED_LOT;
      double hard_trigger=HardLossServerTargetNet()+slip_reserve;

      // ENTRY-SCORE target is exact: did the candidate reach +2 before the SAME
      // real lifecycle would have killed it? Once +2 is reached, entry quality is proven.
      bool reached2=(g_reject_tracks[i].max_net>=BASSO_PEAK_ARM_EUR);
      bool hard_fail=(!reached2 && p<=hard_trigger);
      bool edge_fail=(!reached2 && edge_dying);
      if(!reached2 && !hard_fail && !edge_fail) continue;

      EdgeContext e; ZeroMemory(e);
      e.engine=ENG_BASSO; e.side=side; e.edge_id=g_reject_tracks[i].edge_id; e.health=health;
      e.score=g_reject_tracks[i].entry_score; e.score_threshold=g_reject_tracks[i].threshold;
      e.score_gap=g_reject_tracks[i].score_gap; e.score_shape=g_reject_tracks[i].score_shape;
      e.score_context=g_reject_tracks[i].score_context; e.score_extra=g_reject_tracks[i].score_extra;
      RiskContext r; ZeroMemory(r); r.equity=AccountInfoDouble(ACCOUNT_EQUITY);

      string stage="POST_REJECT";
      string verdict="";
      string outcome=(reached2 ? "MFE_GE_2_BEFORE_REAL_EXIT" :
                      (hard_fail ? "HARD_LOSS_BEFORE_2" : "EDGE_DYING_BEFORE_2"));

      if(g_reject_tracks[i].origin=="POSITION_BLOCK")
      {
         stage="POST_BLOCK";
         verdict=(reached2 ? "BLOCK_MIRROR_MFE_GE_2" : "BLOCK_MIRROR_MFE_LT_2");
      }
      else if(g_reject_tracks[i].origin=="SESSION_BLOCK")
      {
         stage="POST_SESSION";
         verdict=(reached2 ? "SESSION_MIRROR_MFE_GE_2" : "SESSION_MIRROR_MFE_LT_2");
      }
      else if(g_reject_tracks[i].origin=="RISK_BLOCK")
      {
         stage="POST_RISK";
         verdict=(reached2 ? "RISK_BLOCK_MIRROR_MFE_GE_2" : "RISK_BLOCK_MIRROR_MFE_LT_2");
      }
      else if(g_reject_tracks[i].origin=="EXECUTION_BLOCK")
      {
         stage="POST_EXECUTION";
         verdict=(reached2 ? "EXEC_BLOCK_MIRROR_MFE_GE_2" : "EXEC_BLOCK_MIRROR_MFE_LT_2");
      }
      else if(g_reject_tracks[i].origin=="BROKER_REJECT")
      {
         stage="POST_BROKER_REJECT";
         verdict=(reached2 ? "BROKER_REJECT_MIRROR_MFE_GE_2" : "BROKER_REJECT_MIRROR_MFE_LT_2");
      }
      else if(g_reject_tracks[i].origin=="FINGERPRINT_REJECT")
      {
         stage="POST_FINGERPRINT";
         verdict=(reached2 ? "FP_FALSE_REJECT_MFE_GE_2" : "FP_REJECT_CONFIRMED_MFE_LT_2");
      }
      else
         verdict=(reached2 ? "REJECT_MIRROR_MFE_GE_2" : "REJECT_MIRROR_MFE_LT_2");

      string why=StringFormat("%s;MIRROR_REAL_LIFECYCLE;%s;MIN_NET=%.2f;COST_EST=%.2f",
                              g_reject_tracks[i].origin_reason,outcome,
                              g_reject_tracks[i].min_net,BASSO_ROUNDTRIP_COST_RESERVE_EUR);

      LogEvent(g_snapshot,e,stage,verdict,why,r,0,0,"",0,0,0,0,0,0,
               p,g_reject_tracks[i].max_net,0.0,health,edge_dying?1:0,0.0);
      g_reject_tracks[i].active=false;
   }
}

void AddPostCloseTrack(ulong seq,TradeSide side,double exit_price,double entry_gap,double exit_net,double peak,double floor,
                       double entry_score,double threshold,double sgap,double sshape,double scontext,double sextra,double recovery_score,
                       int exit_code,int exit_mode)
{
   if(side==SIDE_NONE || seq==0) return;
   int n=ArraySize(g_post_close_tracks); ArrayResize(g_post_close_tracks,n+1);
   g_post_close_tracks[n].active=true;
   g_post_close_tracks[n].sequence_id=seq;
   g_post_close_tracks[n].side=side;
   g_post_close_tracks[n].start_time=TimeCurrent();
   g_post_close_tracks[n].exit_price=exit_price;
   g_post_close_tracks[n].entry_gap=entry_gap;
   g_post_close_tracks[n].health_peak_live=MathMax(0.000001,(side==SIDE_BUY ? g_snapshot.live_gap_atr : -g_snapshot.live_gap_atr));
   g_post_close_tracks[n].exit_net=exit_net;
   g_post_close_tracks[n].preclose_peak=peak;
   g_post_close_tracks[n].max_extra_net=0.0;
   g_post_close_tracks[n].step_floor=floor;
   g_post_close_tracks[n].entry_score=entry_score;
   g_post_close_tracks[n].threshold=threshold;
   g_post_close_tracks[n].score_gap=sgap;
   g_post_close_tracks[n].score_shape=sshape;
   g_post_close_tracks[n].score_context=scontext;
   g_post_close_tracks[n].score_extra=sextra;
   g_post_close_tracks[n].recovery_score=recovery_score;
   g_post_close_tracks[n].exit_code=exit_code;
   g_post_close_tracks[n].exit_mode=exit_mode;
}

string ExitSourceName(int exit_code)
{
   if(exit_code==1) return "V55_EDGE_DYING";
   if(exit_code==2) return "PROFIT_VAULT";
   if(exit_code==3) return "HARD_LOSS";
   if(exit_code==5) return "RULE_INVARIANT_FAILSAFE";
   if(exit_code==6) return "PROTECTION_FAILSAFE";
   return "UNCLASSIFIED";
}

void UpdatePostCloseTracks()
{
   for(int i=0;i<ArraySize(g_post_close_tracks);i++)
   {
      if(!g_post_close_tracks[i].active) continue;

      TradeSide side=g_post_close_tracks[i].side;
      double now=(side==SIDE_BUY ? g_snapshot.bid : g_snapshot.ask);
      double extra=HypotheticalNetFromPrice(side,g_post_close_tracks[i].exit_price,now);
      if(extra>g_post_close_tracks[i].max_extra_net) g_post_close_tracks[i].max_extra_net=extra;

      // Diagnostic continuation stops at the same structural EdgeDying boundary used by the real trade.
      double signed_live=(side==SIDE_BUY ? g_snapshot.live_gap_atr : -g_snapshot.live_gap_atr);
      if(signed_live>g_post_close_tracks[i].health_peak_live)
         g_post_close_tracks[i].health_peak_live=signed_live;
      double health=PositionEdgeHealthV2(side,g_snapshot,g_post_close_tracks[i].health_peak_live);
      double live_gap_drop_pct=StructuralLiveGapDropPctV46(side,g_snapshot,g_post_close_tracks[i].health_peak_live);
      bool edge_alive=StructuralEdgeAliveV46(side,health,live_gap_drop_pct);
      if(edge_alive) continue;

      double missed=g_post_close_tracks[i].max_extra_net;
      double giveback=MathMax(0.0,g_post_close_tracks[i].preclose_peak-g_post_close_tracks[i].exit_net);
      double allowedGiveback=MathMax(0.50,g_post_close_tracks[i].preclose_peak*0.25);
      double postclose_potential=MathMax(g_post_close_tracks[i].preclose_peak,
                                         g_post_close_tracks[i].exit_net+missed);

      bool is_vault=(g_post_close_tracks[i].exit_mode==1 && g_post_close_tracks[i].exit_code==2);
      string verdict=(is_vault ? "VAULT_REVIEW" : "EXIT_REVIEW");
      if(missed>=2.0)
         verdict=(is_vault ? "VAULT_TOO_TIGHT" : "EXIT_TOO_EARLY");
      else if(missed<=0.30 && giveback>allowedGiveback)
         verdict=(is_vault ? "VAULT_TOO_WIDE" : "EXIT_TOO_WIDE");
      else if(missed<=0.30)
         verdict=(is_vault ? "VAULT_OK" : "EXIT_OK");

      EdgeContext e; ZeroMemory(e);
      e.engine=ENG_BASSO; e.side=side; e.sequence_id=g_post_close_tracks[i].sequence_id; e.health=health;
      e.score=g_post_close_tracks[i].entry_score; e.score_threshold=g_post_close_tracks[i].threshold;
      e.score_gap=g_post_close_tracks[i].score_gap; e.score_shape=g_post_close_tracks[i].score_shape;
      e.score_context=g_post_close_tracks[i].score_context; e.score_extra=g_post_close_tracks[i].score_extra;
      RiskContext r; ZeroMemory(r); r.equity=AccountInfoDouble(ACCOUNT_EQUITY);

      string source=ExitSourceName(g_post_close_tracks[i].exit_code);
      string mode=(g_post_close_tracks[i].exit_mode==1 ? "EDGE_DYNAMIC_TP" : "INVALID_MODE");
      string why=StringFormat("EXIT_SOURCE=%s;MODE=%s;TRACK_UNTIL_REAL_EDGE_DYING;MISSED_AFTER_CLOSE=%.2f;GIVEBACK=%.2f;POST_CLOSE_POTENTIAL=%.2f",
                              source,mode,missed,giveback,postclose_potential);
      LogEvent(g_snapshot,e,"POST_CLOSE",verdict,why,r,0,0,"",0,0,0,0,0,0,
               g_post_close_tracks[i].exit_net,
               postclose_potential,
               g_post_close_tracks[i].step_floor,health,1,0);
      DiagWritePostClose(g_post_close_tracks[i],missed,postclose_potential,giveback,health,verdict);

      // V46 post-close recovery classification is diagnostic only; no hard-loss recovery is armed.
      if(g_post_close_tracks[i].exit_code==3)
      {
         string recovery_status=(missed>=2.0 ? "RECOVERY_EDGE_PRESENT" : "NO_RECOVERY_EDGE");
         string recovery_reason=StringFormat("DIAGNOSTIC_ONLY;MISSED_AFTER_HARD_LOSS=%.2f;POST_CLOSE_POTENTIAL=%.2f;EDGE_DIED_AT_HEALTH=%.2f;ENTRY_RECOVERY_SCORE=%.2f;OPERATIONAL_RECOVERY=OFF",
                                             missed,postclose_potential,health,g_post_close_tracks[i].recovery_score);
         LogEvent(g_snapshot,e,"RECOVERY_DIAG",recovery_status,recovery_reason,r,0,0,"",0,0,0,0,0,0,
                  g_post_close_tracks[i].exit_net,postclose_potential,0.0,health,1,0);
      }
      g_post_close_tracks[i].active=false;
   }
}

EdgeContext Route(const MarketSnapshot &s)
{
   EdgeContext e; ZeroMemory(e);
   e.edge_id=NewEdgeID(); e.sequence_id=NewSequenceID(); e.engine=ENG_SCARTO; e.side=SIDE_NONE;
   e.continuation=false;
   e.recovery=false;
   e.reason="NO_DIRECTIONAL_EDGE";

   if(HistoricalSuperBuy(s)){ e.engine=ENG_SUPER; e.side=SIDE_BUY; e.score=95; e.health=90; e.reason="SUPER_SIGNATURE_BLOCK_NO_CHAIN"; return e; }
   if(HistoricalSuperSell(s)){ e.engine=ENG_SUPER; e.side=SIDE_SELL; e.score=95; e.health=90; e.reason="SUPER_SIGNATURE_BLOCK_NO_CHAIN"; return e; }

   double bg=0,bs=0,bc=0,be=0,sg=0,ss=0,sc=0,se=0;

   // V46 BUY CONTINUATION has priority only while an armed protected BUY floor exit is pending
   // and the current market still has BUY directional structure. It still returns the
   // same EdgeContext and therefore uses the same Risk/Execute pipeline as a normal entry.
   if(g_cont_reentry_armed && g_cont_reentry_side==SIDE_BUY && BrainBassoBuy(s))
   {
      double signed_live=s.live_gap_atr;
      if(signed_live>g_cont_reentry_peak_live) g_cont_reentry_peak_live=signed_live;
      double cont_health=PositionEdgeHealthV2(SIDE_BUY,s,MathMax(0.000001,g_cont_reentry_peak_live));
      double contScore=BuyContinuationScore(s,cont_health,bg,bs,bc,be);

      e.side=SIDE_BUY; e.continuation=true; e.score=contScore; e.health=cont_health;
      e.score_gap=bg; e.score_shape=bs; e.score_context=bc; e.score_extra=be;
      e.score_threshold=RequiredEntryScore(SIDE_BUY);
      double cont_drop=StructuralLiveGapDropPctV46(SIDE_BUY,s,MathMax(0.000001,g_cont_reentry_peak_live));
      EdgeStateV46 cont_state=StructuralEdgeStateV46(SIDE_BUY,cont_health,cont_drop);
      if(contScore>=e.score_threshold && cont_state==EDGE_STATE_STRONG)
      {
         e.engine=ENG_BASSO;
         e.reason="BASSO_BUY_CONTINUATION_SCORE_ACCEPT_EDGE_STRONG";
      }
      else
         e.reason=(cont_state==EDGE_STATE_WEAK ? "BASSO_BUY_CONTINUATION_WAIT_EDGE_WEAK" :
                  (cont_state==EDGE_STATE_DYING ? "BASSO_BUY_CONTINUATION_REJECT_EDGE_DYING" : "BASSO_BUY_CONTINUATION_SCORE_REJECT"));
      return e;
   }

   if(BrainBassoBuy(s))
   {
      double buyScore=BuyEntryScore(s,bg,bs,bc,be);
      e.side=SIDE_BUY; e.score=buyScore; e.health=s.buy_health;
      e.score_gap=bg; e.score_shape=bs; e.score_context=bc; e.score_extra=be;
      e.score_threshold=RequiredEntryScore(SIDE_BUY);
      if(buyScore>=BASSO_BUY_ENTRY_SCORE_MAX)
         e.reason="BASSO_BUY_OVEREXTENSION_REJECT_GE_75";
      else if(buyScore>=e.score_threshold)
      {
         e.engine=ENG_BASSO;
         e.reason="BASSO_BUY_SCORE_ACCEPT";
      }
      else
         e.reason="BASSO_BUY_SCORE_REJECT";
      return e;
   }

   if(BrainBassoSell(s))
   {
      double sellScore=SellEntryScore(s,sg,ss,sc,se);
      e.side=SIDE_SELL; e.score=sellScore; e.health=s.sell_health;
      e.score_gap=sg; e.score_shape=ss; e.score_context=sc; e.score_extra=se;
      e.score_threshold=RequiredEntryScore(SIDE_SELL);
      if(sellScore>=e.score_threshold)
      {
         e.engine=ENG_BASSO;
         e.reason="BASSO_SELL_SCORE_ACCEPT";
      }
      else
         e.reason="BASSO_SELL_SCORE_REJECT";
      return e;
   }

   return e;
}

bool EntrySessionOpen(datetime t)
{
   MqlDateTime dt; TimeToStruct(t,dt);
   if(InpTradeStartHour==InpTradeEndHour) return true;
   if(InpTradeStartHour<InpTradeEndHour)
      return (dt.hour>=InpTradeStartHour && dt.hour<InpTradeEndHour);
   return (dt.hour>=InpTradeStartHour || dt.hour<InpTradeEndHour);
}

//+------------------------------------------------------------------+
//| Risk / execution                                                  |
//+------------------------------------------------------------------+
int ManagedPositionCount(TradeSide side_filter=SIDE_NONE)
{
   int count=0;
   for(int i=PositionsTotal()-1;i>=0;i--)
   {
      ulong ticket=PositionGetTicket(i);
      if(ticket==0 || !PositionSelectByTicket(ticket)) continue;
      if(PositionGetString(POSITION_SYMBOL)!=g_symbol) continue;
      if((long)PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;

      if(side_filter!=SIDE_NONE)
      {
         long ptype=PositionGetInteger(POSITION_TYPE);
         TradeSide ps=(ptype==POSITION_TYPE_BUY ? SIDE_BUY : SIDE_SELL);
         if(ps!=side_filter) continue;
      }
      count++;
   }
   return count;
}

bool ManagedPositionExistsByIdentifier(ulong identifier)
{
   if(identifier==0) return false;
   for(int i=PositionsTotal()-1;i>=0;i--)
   {
      ulong ticket=PositionGetTicket(i);
      if(ticket==0 || !PositionSelectByTicket(ticket)) continue;
      if(PositionGetString(POSITION_SYMBOL)!=g_symbol) continue;
      if((long)PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;
      ulong pid=(ulong)PositionGetInteger(POSITION_IDENTIFIER);
      if(pid==0) pid=ticket;
      if(pid==identifier) return true;
   }
   return false;
}

int ManagedOrderCount()
{
   int count=0;
   for(int i=OrdersTotal()-1;i>=0;i--)
   {
      ulong order=OrderGetTicket(i);
      if(order==0 || !OrderSelect(order)) continue;
      if(OrderGetString(ORDER_SYMBOL)!=g_symbol) continue;
      if((long)OrderGetInteger(ORDER_MAGIC)!=InpMagic) continue;
      count++;
   }
   return count;
}

bool IsHedgingAccount()
{
   long mode=AccountInfoInteger(ACCOUNT_MARGIN_MODE);
   return (mode==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
}

bool CanOpenBassoSide(TradeSide side,string &reason)
{
   reason="APPROVED_ONE_POSITION";
   if(side!=SIDE_BUY && side!=SIDE_SELL)
   {
      reason="INVALID_SIDE";
      return false;
   }

   if(ManagedPositionCount()>0)
   {
      reason="ONE_POSITION_ALREADY_ACTIVE";
      return false;
   }
   if(ManagedOrderCount()>0)
   {
      reason="ENTRY_ORDER_ALREADY_ACTIVE";
      return false;
   }
   return true;
}

// -------------------------------------------------------------------
// V46 SINGLE ENTRY AUTHORITY.
// Every real order path (OnTick gate, BuildRisk and Execute) calls this
// exact function. No other function is allowed to reinterpret entry rules.
// -------------------------------------------------------------------
double RequiredEntryScore(TradeSide side)
{
   if(side==SIDE_BUY)  return InpBuyEntryScoreMin;
   if(side==SIDE_SELL) return InpSellEntryScoreMin;
   return 101.0;
}

bool EntryAuthorityPass(const MarketSnapshot &s,const EdgeContext &e,string &reason)
{
   reason="ENTRY_AUTHORITY_OK";

   if(!InpEnableTrading)
   {
      reason="TRADING_DISABLED";
      return false;
   }
   if(g_close_inflight)
   {
      reason="PREVIOUS_CLOSE_AWAITING_BROKER_TRUTH";
      return false;
   }
   if(e.engine!=ENG_BASSO || (e.side!=SIDE_BUY && e.side!=SIDE_SELL))
   {
      reason="ENGINE_OR_SIDE_NOT_EXECUTABLE";
      return false;
   }

   // V55: old EntryScore / overextension / fingerprint are SHADOW only.
   // Live gate requires V55 score already stamped on EdgeContext (>= InpV55EntryScore).
   if(e.score+0.0000001<InpV55EntryScore)
   {
      reason="V55_ENTRY_SCORE_BELOW_THRESHOLD";
      return false;
   }
   if(MathAbs(e.score_threshold-InpV55EntryScore)>0.0001)
   {
      reason="V55_ENTRY_SCORE_THRESHOLD_STATE_MISMATCH";
      return false;
   }
   if(!EntrySessionOpen(s.decision_time>0 ? s.decision_time : TimeCurrent()))
   {
      reason="OUTSIDE_08_20_ENTRY_SESSION";
      return false;
   }
   if(s.spread_pts>InpMaxSpreadPoints)
   {
      reason="SPREAD_LIMIT";
      return false;
   }

   string exposure_reason="";
   if(!CanOpenBassoSide(e.side,exposure_reason))
   {
      reason=exposure_reason;
      return false;
   }
   return true;
}

double MinimumTradableLot()
{
   double mn=SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_MIN);
   double mx=SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_MAX);
   double st=SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_STEP);
   if(st<=0 || mx<=0) return 0.0;

   double floor_lot=MathMax(InpMinimumOpenLot,mn);
   floor_lot=MathCeil(floor_lot/st-1e-9)*st;
   if(floor_lot>mx) return 0.0;
   return NormalizeDouble(floor_lot,8);
}

double NormalizeLotWithFloor(double lot)
{
   double mx=SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_MAX);
   double st=SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_STEP);
   double floor_lot=MinimumTradableLot();
   if(st<=0 || mx<=0 || floor_lot<=0) return 0.0;

   lot=MathMax(lot,floor_lot);
   lot=MathMin(lot,mx);
   lot=MathFloor(lot/st+1e-9)*st;
   if(lot<floor_lot) lot=floor_lot;
   return NormalizeDouble(lot,8);
}

double MoneyPerPointPerLot()
{
   double tick_value=SymbolInfoDouble(g_symbol,SYMBOL_TRADE_TICK_VALUE_LOSS);
   if(tick_value<=0) tick_value=SymbolInfoDouble(g_symbol,SYMBOL_TRADE_TICK_VALUE);
   double tick_size=SymbolInfoDouble(g_symbol,SYMBOL_TRADE_TICK_SIZE);
   double point=SymbolInfoDouble(g_symbol,SYMBOL_POINT);
   if(tick_value<=0 || tick_size<=0 || point<=0) return 0.0;
   return tick_value*(point/tick_size);
}

double MoneyPerPricePerLot()
{
   double tick_value=SymbolInfoDouble(g_symbol,SYMBOL_TRADE_TICK_VALUE_LOSS);
   if(tick_value<=0) tick_value=SymbolInfoDouble(g_symbol,SYMBOL_TRADE_TICK_VALUE);
   double tick_size=SymbolInfoDouble(g_symbol,SYMBOL_TRADE_TICK_SIZE);
   if(tick_value<=0 || tick_size<=0) return 0.0;
   return tick_value/tick_size;
}

bool TradeRetcodeOrderAccepted(uint code)
{
   // Market-entry request may be accepted/placed before the DEAL transaction arrives.
   return (code==TRADE_RETCODE_DONE ||
           code==TRADE_RETCODE_DONE_PARTIAL ||
           code==TRADE_RETCODE_PLACED);
}

bool TradeRetcodeModifyConfirmed(uint code)
{
   // A protection change is considered installed only after a confirmed modify result.
   return (code==TRADE_RETCODE_DONE || code==TRADE_RETCODE_NO_CHANGES);
}

bool TradeRetcodeCloseConfirmed(uint code)
{
   // Do not treat PLACED as a completed close: EXIT stays latched until broker DEAL_OUT truth arrives.
   return (code==TRADE_RETCODE_DONE || code==TRADE_RETCODE_DONE_PARTIAL);
}

double SlippageReserveEUR(double volume)
{
   return (double)InpSlippagePoints*MoneyPerPointPerLot()*volume;
}

double BrokerMinStopDistancePrice()
{
   double pt=SymbolInfoDouble(g_symbol,SYMBOL_POINT);
   long stops=(long)SymbolInfoInteger(g_symbol,SYMBOL_TRADE_STOPS_LEVEL);
   long freeze=(long)SymbolInfoInteger(g_symbol,SYMBOL_TRADE_FREEZE_LEVEL);
   long points=(stops>freeze ? stops : freeze);
   return (pt>0.0 ? (double)points*pt : 0.0);
}

double NormalizeProtectiveSLPrice(TradeSide side,double price)
{
   double tick=SymbolInfoDouble(g_symbol,SYMBOL_TRADE_TICK_SIZE);
   int digits=(int)SymbolInfoInteger(g_symbol,SYMBOL_DIGITS);
   if(tick<=0.0) return NormalizeDouble(price,digits);

   double steps=price/tick;
   double out=(side==SIDE_BUY ? MathCeil(steps-1e-9)*tick
                              : MathFloor(steps+1e-9)*tick);
   return NormalizeDouble(out,digits);
}

bool ProtectiveSLValidNow(TradeSide side,double sl)
{
   if(sl<=0.0) return false;
   MqlTick tk;
   if(!SymbolInfoTick(g_symbol,tk)) return false;

   double min_dist=BrokerMinStopDistancePrice();
   double tick=SymbolInfoDouble(g_symbol,SYMBOL_TRADE_TICK_SIZE);
   double eps=(tick>0.0 ? tick*0.25 : 0.0);

   if(side==SIDE_BUY)
      return (sl<tk.bid && sl<=tk.bid-min_dist+eps);

   if(side==SIDE_SELL)
      return (sl>tk.ask && sl>=tk.ask+min_dist-eps);

   return false;
}

bool MoreProtectiveSL(TradeSide side,double proposed,double existing)
{
   if(proposed<=0.0) return false;
   if(existing<=0.0) return true;
   double tick=SymbolInfoDouble(g_symbol,SYMBOL_TRADE_TICK_SIZE);
   double eps=(tick>0.0 ? tick*0.25 : 0.0);

   if(side==SIDE_BUY)  return proposed>existing+eps;
   if(side==SIDE_SELL) return proposed<existing-eps;
   return false;
}

// Converts a desired FINAL net result into a broker SL price.
// Entry cost is already known from broker history. Exit deal cost is estimated
// conservatively as the absolute entry deal cost for the same 0.01 position.
// Configured deviation is reserved in money before calculating the SL.
bool ProtectiveSLForFinalNet(ulong ticket,ulong identifier,TradeSide side,
                             double desired_final_net,double &sl_out,string &why)
{
   sl_out=0.0; why="";

   if(!PositionSelectByTicket(ticket))
   {
      why="POSITION_NOT_FOUND";
      return false;
   }

   double volume=PositionGetDouble(POSITION_VOLUME);
   double open_price=PositionGetDouble(POSITION_PRICE_OPEN);
   double mpp_price=MoneyPerPricePerLot();
   if(volume<=0.0 || open_price<=0.0 || mpp_price<=0.0)
   {
      why="INVALID_SL_MONEY_INPUT";
      return false;
   }

   double entry_cost=GVGet(identifier,"ECOST",0.0);
   double exit_cost_est=MathMax(MathAbs(entry_cost),BASSO_ROUNDTRIP_COST_RESERVE_EUR*0.5);
   double slip_reserve=SlippageReserveEUR(volume);

   // final_net = position_profit + entry_cost - estimated_exit_cost.
   // Add deviation reserve so the theoretical trigger is inside the hard/floor boundary.
   double target_position_profit=(desired_final_net+slip_reserve)-entry_cost+exit_cost_est;
   double delta=target_position_profit/(mpp_price*volume);

   double raw=(side==SIDE_BUY ? open_price+delta : open_price-delta);
   sl_out=NormalizeProtectiveSLPrice(side,raw);

   if(!ProtectiveSLValidNow(side,sl_out))
   {
      why="BROKER_STOP_OR_FREEZE_DISTANCE";
      return false;
   }

   why="OK";
   return true;
}

// Single broker-side SL write path. V50 uses it for hard-loss and the approved Profit Vault.
bool ApplyManagedSL(ulong ticket,double desired_sl,uint &retcode,string &ret_desc)
{
   trade.SetExpertMagicNumber(InpMagic);
   trade.SetDeviationInPoints(InpSlippagePoints);
   bool basic=trade.PositionModify(ticket,desired_sl,0.0);
   retcode=trade.ResultRetcode();
   ret_desc=trade.ResultRetcodeDescription();
   return (basic && TradeRetcodeModifyConfirmed(retcode));
}

// Positive-floor utility retained for compatibility; V50 uses the direct exact-floor path below, not adaptive widening.
// Finds the most protective POSITIVE floor target currently placeable by the broker, bounded by
// logical_floor + BASSO_FLOOR_ADAPTIVE_BUFFER_EUR. The logical floor itself is mandatory; the extra
// target is retention-only and therefore may never create a fail-safe close by itself.
bool BestPlaceablePositiveFloorSL(ulong ticket,ulong identifier,TradeSide side,double logical_floor,
                                  double &best_target,double &best_sl,string &why)
{
   best_target=logical_floor; best_sl=0.0; why="";
   if(logical_floor<=0.0)
   {
      why="INVALID_LOGICAL_FLOOR";
      return false;
   }

   string base_why="";
   double base_sl=0.0;
   if(!ProtectiveSLForFinalNet(ticket,identifier,side,logical_floor,base_sl,base_why))
   {
      why="LOGICAL_FLOOR_UNAVAILABLE;"+base_why;
      return false;
   }

   best_sl=base_sl;
   double lo=logical_floor;
   double hi=logical_floor+BASSO_FLOOR_ADAPTIVE_BUFFER_EUR;

   // Monotonic broker distance: higher positive target = tighter SL. Binary search the maximum placeable target.
   for(int i=0;i<12;i++)
   {
      double mid=(lo+hi)*0.5;
      double try_sl=0.0;
      string try_why="";
      if(ProtectiveSLForFinalNet(ticket,identifier,side,mid,try_sl,try_why))
      {
         lo=mid;
         best_target=mid;
         best_sl=try_sl;
      }
      else
         hi=mid;
   }

   why=StringFormat("BEST_PLACEABLE_POSITIVE_TARGET=%.2f",best_target);
   return true;
}

// -------------------------------------------------------------------
// V50 SINGLE PROTECTION / EXIT AUTHORITIES.
// These helpers are the only source of truth for required floor, SL audit
// and exit-latch priority. No caller is allowed to invent a parallel rule.
// EXIT codes: 0 none, 1 EdgeDying, 2 STEP floor, 3 hard loss,
//             5 invariant fail, 6 protection fail. Code 2 is the approved V50 Profit Vault.
// -------------------------------------------------------------------
double RequiredProtectedFloor(int exit_mode,double peak)
{
   // V50 mode 1 is EDGE_DYNAMIC_TP_PROFIT_VAULT. This helper remains the legacy V42 STEP reference for diagnostics only.
   // The returned value has ZERO V50 exit/SL authority; active Vault uses ProfitVaultFloorFromPeak(side,peak).
   double floor=StepLockFloorFromPeak(peak);
   if(peak>=BASSO_PEAK_ARM_EUR) floor=MathMax(floor,BASSO_MIN_PROTECTED_EUR);
   return floor;
}

double HardLossServerTargetNet()
{
   // Rule remains -3.50 maximum. V39 broker truth repeated a 1.97 EUR server-SL execution deviation.
   // V46 keeps the 1.97 EUR empirical reserve observed repeatedly in broker-truth tests.
   double protected_abs=InpBassoHardMaxLossEUR-BASSO_HARD_EXECUTION_RESERVE_EUR;
   return -MathMax(0.10,protected_abs);
}

int ExitAuthorityRank(int code)
{
   if(code==5) return 60;
   if(code==6) return 55;
   if(code==2) return 50;
   if(code==3) return 40;
   if(code==1) return 20;
   return 0;
}

void SetExitAuthority(ulong identifier,int new_code)
{
   if(identifier==0 || new_code<=0) return;
   int old_code=(int)GVGet(identifier,"EXIT",0.0);
   if(ExitAuthorityRank(new_code)>=ExitAuthorityRank(old_code))
      GVSet(identifier,"EXIT",(double)new_code);
}

bool FinalNetAtPrice(ulong ticket,ulong identifier,TradeSide side,double exit_price,double &final_net,string &why)
{
   final_net=0.0; why="";
   if(exit_price<=0.0 || !PositionSelectByTicket(ticket))
   {
      why="POSITION_OR_PRICE_INVALID";
      return false;
   }

   double volume=PositionGetDouble(POSITION_VOLUME);
   double open_price=PositionGetDouble(POSITION_PRICE_OPEN);
   if(volume<=0.0 || open_price<=0.0)
   {
      why="POSITION_VALUES_INVALID";
      return false;
   }

   ENUM_ORDER_TYPE ot=(side==SIDE_BUY ? ORDER_TYPE_BUY : ORDER_TYPE_SELL);
   double gross=0.0;
   if(!OrderCalcProfit(ot,g_symbol,volume,open_price,exit_price,gross))
   {
      why="ORDER_CALC_PROFIT_FAILED";
      return false;
   }

   double entry_cost=GVGet(identifier,"ECOST",0.0);
   double exit_cost_est=MathMax(MathAbs(entry_cost),BASSO_ROUNDTRIP_COST_RESERVE_EUR*0.5);
   final_net=gross+entry_cost-exit_cost_est;
   why="OK";
   return true;
}

bool InstalledSLMeetsAuthority(ulong ticket,ulong identifier,TradeSide side,double required_final_net,
                               double &installed_sl,double &projected_final_net,string &why)
{
   installed_sl=0.0; projected_final_net=0.0; why="";
   if(!PositionSelectByTicket(ticket))
   {
      why="POSITION_NOT_FOUND_AFTER_PROTECTION";
      return false;
   }
   installed_sl=PositionGetDouble(POSITION_SL);
   if(installed_sl<=0.0)
   {
      why="NO_SERVER_SL_INSTALLED";
      return false;
   }
   string calc="";
   if(!FinalNetAtPrice(ticket,identifier,side,installed_sl,projected_final_net,calc))
   {
      why="SL_FINAL_NET_AUDIT_"+calc;
      return false;
   }
   if(projected_final_net+0.01<required_final_net)
   {
      why=StringFormat("SERVER_SL_TOO_LOOSE;PROJECTED=%.2f;REQUIRED=%.2f",projected_final_net,required_final_net);
      return false;
   }
   why="SERVER_SL_AUTHORITY_OK";
   return true;
}

bool PositionStateInvariant(ulong identifier,TradeSide side,int exit_mode,double entry_score,double entry_threshold,
                            double peak,double protected_floor,bool floor_armed,string &why)
{
   why="POSITION_INVARIANT_OK";
   if(identifier==0)
   {
      why="ZERO_POSITION_IDENTIFIER";
      return false;
   }
   if(side!=SIDE_BUY && side!=SIDE_SELL)
   {
      why="INVALID_POSITION_SIDE";
      return false;
   }
   if(exit_mode!=1)
   {
      why="INVALID_STEP_ONLY_MODE";
      return false;
   }
   double required=InpV55EntryScore;
   if(entry_threshold<=0.0 || MathAbs(entry_threshold-required)>0.0001 || entry_score+0.0000001<required)
   {
      why="OPEN_POSITION_V55_ENTRY_SCORE_INVARIANT_BROKEN";
      return false;
   }

   // V50 invariant: the inherited main Vault remains exact from Peak >=1.60.
   // Below +1.60, BUY must remain unarmed; SELL may persist ONLY the approved +0.30 micro floor
   // after it was structurally armed in the [1.20,1.60) Peak band. Peak is monotonic, so a persisted
   // +0.30 state can never later appear below +1.20.
   double theoretical_floor=RequiredProtectedFloor(exit_mode,peak);
   if(theoretical_floor<0.0)
   {
      why="INVALID_DIAGNOSTIC_STEP_REFERENCE";
      return false;
   }
   double required_vault=ProfitVaultFloorFromPeak(side,peak);
   if(required_vault<=0.0)
   {
      if(side==SIDE_BUY)
      {
         if(floor_armed || protected_floor>0.0001)
         {
            why="BUY_PROFIT_VAULT_ARMED_BEFORE_1_60_PEAK";
            return false;
         }
      }
      else
      {
         bool micro_floor_present=(protected_floor>0.0001 || floor_armed);
         if(micro_floor_present)
         {
            if(peak<BASSO_SELL_MICRO_EMERGENCY_ARM_EUR || peak>=BASSO_VAULT_MICRO_ARM_EUR)
            {
               why="SELL_MICRO_VAULT_OUTSIDE_APPROVED_PEAK_BAND";
               return false;
            }
            if(!floor_armed || MathAbs(protected_floor-BASSO_SELL_MICRO_EMERGENCY_FLOOR_EUR)>0.01)
            {
               why="SELL_MICRO_VAULT_NOT_EQUAL_TO_APPROVED_0_30_FLOOR";
               return false;
            }
         }
      }
   }
   else
   {
      if(!floor_armed)
      {
         why="PROFIT_VAULT_REQUIRED_BUT_NOT_ARMED";
         return false;
      }
      if(MathAbs(protected_floor-required_vault)>0.01)
      {
         why="PROFIT_VAULT_NOT_EQUAL_TO_APPROVED_TABLE_FLOOR";
         return false;
      }
      if(protected_floor>peak+0.0001)
      {
         why="PROFIT_VAULT_ABOVE_PROFIT_PEAK";
         return false;
      }
   }
   return true;
}

RiskContext BuildRisk(const MarketSnapshot &s,const EdgeContext &e)
{
   RiskContext r;
   r.equity=AccountInfoDouble(ACCOUNT_EQUITY);
   r.risk_money=InpBassoHardMaxLossEUR;
   r.stop_points=0.0;
   r.raw_lot=BASSO_FIXED_LOT;
   r.final_lot=0.0;
   r.actual_risk=0.0;
   r.decision=RISK_BLOCK;
   r.reason="RISK_INIT";

   double pt=SymbolInfoDouble(g_symbol,SYMBOL_POINT);
   double mpp=MoneyPerPointPerLot();
   if(pt<=0.0 || mpp<=0.0 || r.equity<=0.0 || InpBassoHardMaxLossEUR<=0.0)
   {
      r.reason="INVALID_RISK_INPUT";
      return r;
   }

   // V39 second-line check calls the SAME single entry authority used immediately before send.
   string entry_authority_reason="";
   if(!EntryAuthorityPass(s,e,entry_authority_reason))
   {
      r.reason="ENTRY_AUTHORITY_"+entry_authority_reason;
      return r;
   }

   // Exact approved BASSO lot for this test.
   double mn=SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_MIN);
   double mx=SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_MAX);
   double st=SymbolInfoDouble(g_symbol,SYMBOL_VOLUME_STEP);
   if(st<=0.0 || mn<=0.0 || mx<=0.0)
   {
      r.reason="INVALID_BROKER_VOLUME_SPEC";
      return r;
   }

   double normalized=MathRound(BASSO_FIXED_LOT/st)*st;
   if(BASSO_FIXED_LOT<mn-1e-9 || BASSO_FIXED_LOT>mx+1e-9 ||
      MathAbs(normalized-BASSO_FIXED_LOT)>1e-9)
   {
      r.reason="BROKER_CANNOT_EXECUTE_FIXED_0_01";
      return r;
   }
   r.final_lot=BASSO_FIXED_LOT;

   // The order is NOT allowed to open unless a broker-side emergency SL can fit
   // inside the EUR3.50 cap after reserving observed round-trip deal costs and deviation.
   double slip_reserve=SlippageReserveEUR(r.final_lot);
   double hard_server_target_abs=MathAbs(HardLossServerTargetNet());
   double price_loss_budget=hard_server_target_abs-BASSO_ROUNDTRIP_COST_RESERVE_EUR-slip_reserve;
   if(price_loss_budget<=0.01)
   {
      r.reason="HARD_LOSS_CAP_UNACHIEVABLE_AFTER_COST_RESERVE";
      r.final_lot=0.0;
      return r;
   }

   r.stop_points=price_loss_budget/(r.final_lot*mpp);

   double broker_min_points=BrokerMinStopDistancePrice()/pt;
   if(r.stop_points<=broker_min_points+1e-6)
   {
      r.reason="HARD_LOSS_CAP_TIGHTER_THAN_BROKER_STOP_LEVEL";
      r.final_lot=0.0;
      return r;
   }

   r.actual_risk=r.final_lot*r.stop_points*mpp+BASSO_ROUNDTRIP_COST_RESERVE_EUR+slip_reserve;
   if(r.actual_risk>hard_server_target_abs+0.01 || r.actual_risk>InpBassoHardMaxLossEUR+0.01)
   {
      r.reason="HARD_LOSS_CAP_CALC_ERROR";
      r.final_lot=0.0;
      return r;
   }

   r.decision=RISK_APPROVE;
   r.reason="APPROVED_FIXED_0_01_HARD_CAP_3_50_WITH_V52_EMPIRICAL_RESERVE";
   return r;
}

//+------------------------------------------------------------------+
//| V59 FINAL execution authority — sole gate before broker send     |
//+------------------------------------------------------------------+
bool V59ReasonIsReentryOrContinuation(const EdgeContext &e)
{
   if(e.continuation) return true;
   if(e.recovery) return true;
   if(g_v55.reentry_armed) return true;
   if(g_cont_reentry_armed) return true;
   string r=e.reason;
   if(r=="V55_BUY_REENTRY" || r=="V55_SELL_REENTRY") return true;
   if(r=="BUY_REENTRY" || r=="SELL_REENTRY") return true;
   if(StringFind(r,"REENTRY")>=0) return true;
   if(StringFind(r,"CONTINUATION")>=0) return true;
   return false;
}

string V59AuthorityDetail(const EdgeContext &e)
{
   return StringFormat("side=%s;score=%.2f;f_signed_gap_std3=%.8f;gap_min=%.5f;gap_valid=%d;f_delta30_mean5=%.9f;delta30_max=%.9f;delta30_valid=%d;reason=%s;sequence_id=%I64u;edge_id=%I64u",
                       SideName(e.side),e.score,
                       e.v59_signed_gap_std3,V59_SELL_SIGNED_GAP_STD3_MIN,(e.v59_signed_gap_std3_valid?1:0),
                       e.v59_delta30_mean5,V59_SELL_DELTA30_MEAN5_MAX,(e.v59_delta30_mean5_valid?1:0),
                       e.reason,e.sequence_id,e.edge_id);
}

// true ONLY for: SELL, non-reentry, and BOTH approved pre-entry structural gates.
bool V59ExecutionAuthority(const EdgeContext &e,string &block_stage,string &block_detail)
{
   block_stage="";
   block_detail=V59AuthorityDetail(e);

   if(e.side==SIDE_BUY)
   {
      block_stage="V59_BUY_DISABLED";
      return false;
   }
   if(e.side!=SIDE_SELL)
   {
      block_stage="V59_EXECUTION_AUTHORITY_BLOCK";
      return false;
   }
   if(V59ReasonIsReentryOrContinuation(e))
   {
      block_stage="V59_REENTRY_BLOCKED";
      return false;
   }
   if(!e.v59_signed_gap_std3_valid || !e.v59_delta30_mean5_valid ||
      e.v59_signed_gap_std3<V59_SELL_SIGNED_GAP_STD3_MIN ||
      e.v59_delta30_mean5>V59_SELL_DELTA30_MEAN5_MAX)
   {
      block_stage="V59_SELL_SIGNATURE_REJECT";
      return false;
   }
   block_stage="V59_SELL_SIGNATURE_ACCEPT";
   return true;
}

bool Execute(const MarketSnapshot &s,const EdgeContext &e,const RiskContext &r)
{
   if(r.decision!=RISK_APPROVE)
   {
      LogEvent(s,e,"EXECUTE","BLOCK",r.reason,r,0,0,"",0,0,0,0,0,0,0,0,0,e.health,0,0);
      return false;
   }

   // V50 preserves the V47/V46 FINAL entry guard unchanged: the exact same authority is called immediately before broker send.
   string entry_authority_reason="";
   if(!EntryAuthorityPass(s,e,entry_authority_reason))
   {
      string why="ENTRY_AUTHORITY_"+entry_authority_reason;
      LogEvent(s,e,"EXECUTE","BLOCK",why,r,0,0,"",0,0,0,0,0,0,0,0,0,e.health,0,0);
      EdgeContext b=e; b.reason=why; AddRejectTrack(b,s,"EXECUTION_BLOCK");
      return false;
   }

   // V59 absolute final authority: no BUY, no reentry/continuation, SELL only with pre-entry signature.
   string v59_stage="";
   string v59_detail="";
   if(!V59ExecutionAuthority(e,v59_stage,v59_detail))
   {
      LogEvent(s,e,"V59_EXECUTION_AUTHORITY_BLOCK","BLOCK",v59_stage+";"+v59_detail,r,0,0,"",0,0,0,0,0,0,0,0,0,e.health,0,s.atr_m1);
      LogEvent(s,e,v59_stage,"BLOCK",v59_detail,r,0,0,"",0,0,0,0,0,0,0,0,0,e.health,0,s.atr_m1);
      EdgeContext b=e; b.reason=v59_stage;
      AddRejectTrack(b,s,"EXECUTION_BLOCK");
      return false;
   }
   LogEvent(s,e,"V59_EXECUTION_AUTHORITY_PASS","PASS",v59_detail,r,0,0,"",0,0,0,0,0,0,0,0,0,e.health,0,s.atr_m1);
   LogEvent(s,e,"V59_SELL_SIGNATURE_ACCEPT","ACCEPT",v59_detail,r,0,0,"",0,0,0,0,0,0,0,0,0,e.health,0,s.atr_m1);

   // Operational entry_reason forced at send-time (diagnostic origin may have been V55_SELL_EDGE).
   EdgeContext ex=e;
   ex.side=SIDE_SELL;
   ex.reason="V59_SELL_SIGNATURE";
   ex.continuation=false;
   ex.recovery=false;

   double margin_required=0.0;
   ENUM_ORDER_TYPE margin_type=ORDER_TYPE_SELL;
   double margin_price=s.bid;
   if(!OrderCalcMargin(margin_type,g_symbol,r.final_lot,margin_price,margin_required))
   {
      LogEvent(s,ex,"EXECUTE","BLOCK","MARGIN_CALC_FAILED",r,0,0,"",0,0,0,0,0,0,0,0,0,ex.health,0,0);
      EdgeContext b=ex; b.reason="MARGIN_CALC_FAILED"; AddRejectTrack(b,s,"EXECUTION_BLOCK");
      return false;
   }
   if(margin_required>AccountInfoDouble(ACCOUNT_MARGIN_FREE))
   {
      LogEvent(s,ex,"EXECUTE","BLOCK","INSUFFICIENT_FREE_MARGIN",r,0,0,"",0,0,0,0,0,0,0,0,0,ex.health,0,0);
      EdgeContext b=ex; b.reason="INSUFFICIENT_FREE_MARGIN"; AddRejectTrack(b,s,"EXECUTION_BLOCK");
      return false;
   }

   // REAL emergency server SL is attached to the market order itself.
   // This is the first physical layer of the -3.50 EUR authority.
   double pt=SymbolInfoDouble(g_symbol,SYMBOL_POINT);
   double reference=s.bid;
   double initial_sl=reference+r.stop_points*pt;
   initial_sl=NormalizeProtectiveSLPrice(SIDE_SELL,initial_sl);

   if(!ProtectiveSLValidNow(SIDE_SELL,initial_sl))
   {
      LogEvent(s,ex,"EXECUTE","BLOCK","INITIAL_HARD_SL_INVALID_OR_TOO_CLOSE",r,initial_sl,0,"",
               0,0,0,0,0,0,0,0,0,ex.health,0,0);
      EdgeContext b=ex; b.reason="INITIAL_HARD_SL_INVALID_OR_TOO_CLOSE"; AddRejectTrack(b,s,"EXECUTION_BLOCK");
      return false;
   }

   trade.SetExpertMagicNumber(InpMagic);
   trade.SetDeviationInPoints(InpSlippagePoints);

   string comment=StringFormat("V59_BS_%I64u",ex.sequence_id);

   // Persist ALL state before send so an immediate trade transaction cannot see an incomplete setup.
   int mode=1; // V49: EDGE_DYNAMIC_TP_PROFIT_VAULT; legacy STEP scale remains diagnostic only.
   double diag_entry_raw=PositionRawEdgeScore(SIDE_SELL,s.live_gap_atr);

   SeqGVSet(ex.sequence_id,"HPTS",r.stop_points);
   SeqGVSet(ex.sequence_id,"DENT",diag_entry_raw);
   SeqGVSet(ex.sequence_id,"MODE",(double)mode);
   SeqGVSet(ex.sequence_id,"ESCORE",ex.score);
   SeqGVSet(ex.sequence_id,"ETHR",ex.score_threshold);
   SeqGVSet(ex.sequence_id,"SGAP",ex.score_gap);
   SeqGVSet(ex.sequence_id,"SSHAPE",ex.score_shape);
   SeqGVSet(ex.sequence_id,"SCONT",ex.score_context);
   SeqGVSet(ex.sequence_id,"SEXTRA",ex.score_extra);
   SeqGVSet(ex.sequence_id,"EID",(double)ex.edge_id); // bridge: preserve V55 edge identity into position GV
   SeqGVSet(ex.sequence_id,"SGSTD3",ex.v59_signed_gap_std3);
   SeqGVSet(ex.sequence_id,"SGOK",(ex.v59_signed_gap_std3_valid?1.0:0.0));
   SeqGVSet(ex.sequence_id,"D30M5",ex.v59_delta30_mean5);
   SeqGVSet(ex.sequence_id,"D30OK",(ex.v59_delta30_mean5_valid?1.0:0.0));
   double recovery_score=SellRecoveryScoreV46(s);
   SeqGVSet(ex.sequence_id,"RSC",recovery_score);
   SeqGVSet(ex.sequence_id,"RECOV",0.0);
   SeqGVSet(ex.sequence_id,"EFLOOR",0.0);
   SeqGVSet(ex.sequence_id,"TFLOOR",0.0);
   SeqGVSet(ex.sequence_id,"PFA",0.0);
   SeqGVSet(ex.sequence_id,"DTP",DynamicTPFromPeak(0.0));

   {
      EdgeContext re=ex;
      RiskContext rr=r;
      string recovery_reason=StringFormat("DIAGNOSTIC_ONLY;RECOVERY_SCORE=%.2f;V45_OPERATIONAL_RECOVERY_REMOVED_4_OF_4_LOSSES",
                                          recovery_score);
      LogEvent(s,re,"RECOVERY_SCORE","DIAGNOSTIC",recovery_reason,rr,initial_sl,0,"",
               0,0,0,0,r.final_lot,0.0,0.0,0.0,0.0,ex.health,0,s.atr_m1,
               diag_entry_raw,diag_entry_raw,diag_entry_raw,100.0,0.0);
   }

   // DIAG2025 captures the complete PRE-ORDER signature before broker send.
   DiagStagePendingEntry(ex,s);

   // V59: BUY send path physically removed — only SELL after V59ExecutionAuthority.
   bool basic_ok=trade.Sell(r.final_lot,g_symbol,0.0,initial_sl,0.0,comment);

   uint ret=trade.ResultRetcode();
   bool accepted=(basic_ok && TradeRetcodeOrderAccepted(ret));

   if(!accepted)
   {
      DiagCancelPending(ex.sequence_id);
      SeqGVClear(ex.sequence_id);
      EdgeContext b=ex; b.reason=StringFormat("BROKER_SEND_REJECT_RETCODE_%u",ret);
      AddRejectTrack(b,s,"BROKER_REJECT");
   }
   else
   {
      g_sell_rearmed=false;
      // Any confirmed new position consumes every pending re-entry arm.
      g_cont_reentry_armed=false;
      g_cont_reentry_side=SIDE_NONE;
      g_cont_reentry_peak_live=0.0;
      g_v55.reentry_armed=false;
   }

   LogEvent(s,ex,"EXECUTE",accepted?"REQUEST_OK":"REQUEST_FAIL",
            accepted?"SENT_WITH_REAL_HARD_SL":"SEND_OR_BROKER_REJECT",
            r,initial_sl,ret,trade.ResultRetcodeDescription(),
            trade.ResultOrder(),trade.ResultDeal(),0,0,r.final_lot,trade.ResultPrice(),
            0.0,0.0,0.0,ex.health,0,s.atr_m1,diag_entry_raw,diag_entry_raw,diag_entry_raw,100.0,0.0);

   return accepted;
}

//+------------------------------------------------------------------+
//| Persistent BASSO lifecycle                                       |
//+------------------------------------------------------------------+
string SeqGVKey(ulong sequence_id,string suffix)
{
   return StringFormat("XV52_SEQ_%I64d_%I64u_%s",InpMagic,sequence_id,suffix);
}

void SeqGVSet(ulong sequence_id,string suffix,double value)
{
   if(sequence_id==0) return;
   GlobalVariableSet(SeqGVKey(sequence_id,suffix),value);
}

double SeqGVGet(ulong sequence_id,string suffix,double fallback=0.0)
{
   if(sequence_id==0) return fallback;
   string k=SeqGVKey(sequence_id,suffix);
   if(!GlobalVariableCheck(k)) return fallback;
   return GlobalVariableGet(k);
}

void SeqGVClear(ulong sequence_id)
{
   if(sequence_id==0) return;
   string suffixes[20]={"HPTS","DENT","MODE","ESCORE","ETHR","SGAP","SSHAPE","SCONT","SEXTRA","EID","RSC","RECOV","EFLOOR","TFLOOR","PFA","DTP","SGSTD3","SGOK","D30M5","D30OK"};
   for(int i=0;i<ArraySize(suffixes);i++)
   {
      string k=SeqGVKey(sequence_id,suffixes[i]);
      if(GlobalVariableCheck(k)) GlobalVariableDel(k);
   }
}

string GVKey(ulong identifier,string suffix)
{
   return StringFormat("XV52_%I64d_%I64u_%s",InpMagic,identifier,suffix);
}

void GVSet(ulong identifier,string suffix,double value)
{
   GlobalVariableSet(GVKey(identifier,suffix),value);
}

double GVGet(ulong identifier,string suffix,double fallback=0.0)
{
   string k=GVKey(identifier,suffix);
   if(!GlobalVariableCheck(k)) return fallback;
   return GlobalVariableGet(k);
}

void GVClear(ulong identifier)
{
   string suffixes[33]={"EATR","EGAP","PEAK","DC","LHB","ECOST","SIDE","SEQ","HPTS","EXIT","DENT","DPK",
                       "MODE","ESCORE","ETHR","EFLOOR","TFLOOR","PFA","DTP","PTMSC","DMSC","CRMSC","ESTATE",
                       "SGAP","SSHAPE","SCONT","SEXTRA","EID","RSC","RECOV","SLMODE","HPK","INITOK"};
   for(int i=0;i<ArraySize(suffixes);i++)
   {
      string k=GVKey(identifier,suffixes[i]);
      if(GlobalVariableCheck(k)) GlobalVariableDel(k);
   }
}

ulong ParseSequenceFromComment(string comment)
{
   // Newest prefixes first. V55/V56 comments MUST be recognized or MODE stays 0 → INVALID_STEP_ONLY_MODE.
   string prefixes[]={"V59_BB_","V59_BS_","V58_BB_","V58_BS_","V57_BB_","V57_BS_","V56_BB_","V56_BS_","V55_BB_","V55_BS_","V54_BB_","V54_BS_","V52_BB_","V52_BS_","V50_BB_","V50_BS_","V49_BB_","V49_BS_","V48_BB_","V48_BS_","V47_BB_","V47_BS_","V46_BB_","V46_BS_","V45_BB_","V45_BS_","V44_BB_","V44_BS_","V43_BB_","V43_BS_","V42_BB_","V42_BS_","V41_BB_","V41_BS_","V40_BB_","V40_BS_","V39_BB_","V39_BS_","V38_BB_","V38_BS_","V37_BB_","V37_BS_","V36_BB_","V36_BS_","V35_BB_","V35_BS_","V34_BB_","V34_BS_","V33_BB_","V33_BS_","V32_BB_","V32_BS_","V31_BB_","V31_BS_",
                        "V30_BB_","V30_BS_","V29_BB_","V29_BS_","V28_BB_","V28_BS_","V27_BB_","V27_BS_",
                        "V26_BB_","V26_BS_","V25_BB_","V25_BS_","V24_BB_","V24_BS_","V23_BB_","V23_BS_",
                        "V22_BB_","V22_BS_","V21_BB_","V21_BS_","V20_BB_","V20_BS_","V19_BB_","V19_BS_",
                        "V18_BB_","V18_BS_","V17_BB_","V17_BS_","V16_BB_","V16_BS_","V15_BB_","V15_BS_",
                        "V14_BB_","V14_BS_","V13_BB_","V13_BS_"};
   for(int i=0;i<ArraySize(prefixes);i++)
   {
      int p=StringFind(comment,prefixes[i]);
      if(p>=0)
      {
         string tail=StringSubstr(comment,p+StringLen(prefixes[i]));
         return (ulong)StringToInteger(tail);
      }
   }
   return 0;
}

double EntryCostsForPosition(ulong identifier)
{
   if(identifier==0) return 0.0;
   if(!HistorySelectByPosition(identifier)) return 0.0;
   int total=HistoryDealsTotal();
   double costs=0.0;
   for(int i=0;i<total;i++)
   {
      ulong deal=HistoryDealGetTicket(i);
      if(deal==0) continue;
      long entry=(long)HistoryDealGetInteger(deal,DEAL_ENTRY);
      if(entry!=DEAL_ENTRY_IN && entry!=DEAL_ENTRY_INOUT) continue;
      costs+=HistoryDealGetDouble(deal,DEAL_COMMISSION);
      costs+=HistoryDealGetDouble(deal,DEAL_FEE);
   }
   return costs;
}

void EnsurePositionState(ulong ticket)
{
   if(!PositionSelectByTicket(ticket)) return;
   ulong identifier=(ulong)PositionGetInteger(POSITION_IDENTIFIER);
   if(identifier==0) identifier=ticket;

   string k=GVKey(identifier,"EATR");
   if(GlobalVariableCheck(k)) return;

   double entry_atr=(g_snapshot_ok ? g_snapshot.atr_m1 : ATRValue(BASSO_OPS_TF,1));
   long ptype=PositionGetInteger(POSITION_TYPE);
   string comment=PositionGetString(POSITION_COMMENT);
   TradeSide diag_side=(ptype==POSITION_TYPE_BUY ? SIDE_BUY : SIDE_SELL);

   ulong seq=ParseSequenceFromComment(comment);
   // Bridge: SeqGV was written in Execute BEFORE send. MODE must become 1 here or RuleAudit fires INVALID_STEP_ONLY_MODE.
   double mode_from_seq=SeqGVGet(seq,"MODE",0.0);
   double escore_from_seq=SeqGVGet(seq,"ESCORE",0.0);
   double ethr_from_seq=SeqGVGet(seq,"ETHR",0.0);
   ulong edge_from_seq=(ulong)SeqGVGet(seq,"EID",0.0);

   GVSet(identifier,"EATR",entry_atr);
   GVSet(identifier,"EGAP",(g_snapshot_ok ? g_snapshot.gap_atr : 0.0));
   double initial_signed_live=(g_snapshot_ok ? (ptype==POSITION_TYPE_BUY ? g_snapshot.live_gap_atr : -g_snapshot.live_gap_atr) : 0.000001);
   GVSet(identifier,"HPK",MathMax(0.000001,initial_signed_live));

   double diag_entry_raw=SeqGVGet(seq,"DENT",
      (g_snapshot_ok ? PositionRawEdgeScore(diag_side,g_snapshot.live_gap_atr) : 0.0));
   GVSet(identifier,"DENT",diag_entry_raw);
   GVSet(identifier,"DPK",diag_entry_raw);

   GVSet(identifier,"PEAK",0.0);
   GVSet(identifier,"DC",0.0);
   GVSet(identifier,"LHB",g_snapshot_ok ? (double)g_snapshot.time : 0.0);
   GVSet(identifier,"ECOST",EntryCostsForPosition(identifier));
   GVSet(identifier,"SIDE",ptype==POSITION_TYPE_BUY ? 1.0 : -1.0);
   GVSet(identifier,"SEQ",(double)seq);
   GVSet(identifier,"EID",(double)edge_from_seq);
   GVSet(identifier,"HPTS",SeqGVGet(seq,"HPTS",0.0));
   GVSet(identifier,"EXIT",0.0);
   GVSet(identifier,"MODE",mode_from_seq);
   GVSet(identifier,"ESCORE",escore_from_seq);
   GVSet(identifier,"ETHR",ethr_from_seq);
   GVSet(identifier,"SGAP",SeqGVGet(seq,"SGAP",0.0));
   GVSet(identifier,"SSHAPE",SeqGVGet(seq,"SSHAPE",0.0));
   GVSet(identifier,"SCONT",SeqGVGet(seq,"SCONT",0.0));
   GVSet(identifier,"SEXTRA",SeqGVGet(seq,"SEXTRA",0.0));
   GVSet(identifier,"RSC",SeqGVGet(seq,"RSC",0.0));
   GVSet(identifier,"RECOV",SeqGVGet(seq,"RECOV",0.0));
   GVSet(identifier,"EFLOOR",SeqGVGet(seq,"EFLOOR",0.0));
   GVSet(identifier,"TFLOOR",SeqGVGet(seq,"TFLOOR",0.0));
   GVSet(identifier,"PFA",0.0); // Profit Vault starts unarmed; unchanged V50/V55 path
   GVSet(identifier,"DTP",SeqGVGet(seq,"DTP",DynamicTPFromPeak(0.0)));
   GVSet(identifier,"PTMSC",g_snapshot_ok ? (double)g_snapshot.tick_time_msc : 0.0);
   GVSet(identifier,"DMSC",0.0);
   GVSet(identifier,"CRMSC",0.0);
   GVSet(identifier,"ESTATE",-1.0);
   GVSet(identifier,"SLMODE",(PositionGetDouble(POSITION_SL)>0.0 ? 3.0 : 0.0));

   int init_ok=0;
   string init_why="OK";
   if(seq==0)
   {
      init_why="SEQ_PARSE_FAILED_FROM_COMMENT";
   }
   else if((int)mode_from_seq!=1)
   {
      init_why=StringFormat("MODE_NOT_1_AFTER_SEQ_BRIDGE;MODE=%.0f;COMMENT=%s",mode_from_seq,comment);
   }
   else if(escore_from_seq+0.0000001<InpV55EntryScore || MathAbs(ethr_from_seq-InpV55EntryScore)>0.0001)
   {
      init_why=StringFormat("V55_SCORE_STATE_INCOMPLETE;ESCORE=%.2f;ETHR=%.2f",escore_from_seq,ethr_from_seq);
   }
   else
      init_ok=1;
   GVSet(identifier,"INITOK",(double)init_ok);

   // Diagnostic only: proves DEAL_IN lifecycle bridge before ManagePositions/RuleAudit.
   {
      EdgeContext ie; ZeroMemory(ie);
      ie.engine=ENG_BASSO; ie.side=diag_side; ie.sequence_id=seq; ie.edge_id=edge_from_seq;
      ie.score=escore_from_seq; ie.score_threshold=ethr_from_seq;
      ie.reason=(init_ok==1 ? "V55_POSITION_STATE_INIT" : init_why);
      RiskContext ir; ZeroMemory(ir); ir.equity=AccountInfoDouble(ACCOUNT_EQUITY);
      string why=StringFormat("INIT_OK=%d;TICKET=%I64u;IDENTIFIER=%I64u;SEQ=%I64u;EID=%I64u;SIDE=%s;MODE=%.0f;ETHR=%.2f;ESCORE=%.2f;SLMODE=%.0f;COMMENT=%s;%s",
                              init_ok,ticket,identifier,seq,edge_from_seq,SideName(diag_side),
                              mode_from_seq,ethr_from_seq,escore_from_seq,GVGet(identifier,"SLMODE",0.0),
                              CsvSafe(comment),init_why);
      LogEvent(g_snapshot,ie,"V55_POSITION_STATE_INIT",init_ok==1?"OK":"FAIL",why,ir,
               PositionGetDouble(POSITION_SL),0,"",0,0,ticket,identifier,
               PositionGetDouble(POSITION_VOLUME),PositionGetDouble(POSITION_PRICE_OPEN),
               0.0,0.0,0.0,0.0,0,entry_atr);
   }

   SeqGVClear(seq);
}

double PositionNetNow(ulong identifier)
{
   double entry_cost=GVGet(identifier,"ECOST",0.0);
   return PositionGetDouble(POSITION_PROFIT)+PositionGetDouble(POSITION_SWAP)+entry_cost;
}

void ManagePositions(bool new_bar)
{
   for(int i=PositionsTotal()-1;i>=0;i--)
   {
      ulong ticket=PositionGetTicket(i);
      if(ticket==0 || !PositionSelectByTicket(ticket)) continue;
      if(PositionGetString(POSITION_SYMBOL)!=g_symbol) continue;
      if((long)PositionGetInteger(POSITION_MAGIC)!=InpMagic) continue;

      EnsurePositionState(ticket);
      if(!PositionSelectByTicket(ticket)) continue;

      ulong identifier=(ulong)PositionGetInteger(POSITION_IDENTIFIER);
      if(identifier==0) identifier=ticket;

      long ptype=PositionGetInteger(POSITION_TYPE);
      TradeSide side=(ptype==POSITION_TYPE_BUY ? SIDE_BUY : SIDE_SELL);
      double volume=PositionGetDouble(POSITION_VOLUME);
      double price=PositionGetDouble(POSITION_PRICE_CURRENT);
      double existing_sl=PositionGetDouble(POSITION_SL);

      double entry_atr=GVGet(identifier,"EATR",g_snapshot.atr_m1);
      double entry_cost=GVGet(identifier,"ECOST",0.0);
      double estimated_exit_cost=MathMax(MathAbs(entry_cost),BASSO_ROUNDTRIP_COST_RESERVE_EUR*0.5);

      // V39 current_net is estimated REALIZABLE net: open P/L + entry costs
      // minus estimated closing deal cost. This keeps +2/+1.50 decisions aligned
      // with the broker-truth net written at CLOSED.
      double current_net=PositionNetNow(identifier)-estimated_exit_cost;
      DiagUpdateRealTrade(identifier,current_net);

      double peak=GVGet(identifier,"PEAK",0.0);
      double entry_gap=GVGet(identifier,"EGAP",g_snapshot.gap_atr);
      int exit_pending=(int)GVGet(identifier,"EXIT",0.0);
      int exit_mode=(int)GVGet(identifier,"MODE",1.0); // V50 legal mode: 1=EDGE_DYNAMIC_TP_PROFIT_VAULT

      double entry_score=GVGet(identifier,"ESCORE",0.0);
      double entry_threshold=GVGet(identifier,"ETHR",0.0);
      double entry_score_gap=GVGet(identifier,"SGAP",0.0);
      double entry_score_shape=GVGet(identifier,"SSHAPE",0.0);
      double entry_score_context=GVGet(identifier,"SCONT",0.0);
      double entry_score_extra=GVGet(identifier,"SEXTRA",0.0);
      double step_floor=GVGet(identifier,"EFLOOR",0.0);          // V50 ACTIVE monotonic Profit Vault net floor
      double theoretical_floor=GVGet(identifier,"TFLOOR",0.0);  // V42 reference, diagnostic only
      bool floor_armed=(GVGet(identifier,"PFA",0.0)>=0.5);
      double dynamic_tp=GVGet(identifier,"DTP",DynamicTPFromPeak(peak));

      double signed_live_health=(side==SIDE_BUY ? g_snapshot.live_gap_atr : -g_snapshot.live_gap_atr);
      double health_peak_live=GVGet(identifier,"HPK",MathMax(0.000001,signed_live_health));
      if(signed_live_health>health_peak_live)
      {
         health_peak_live=signed_live_health;
         GVSet(identifier,"HPK",health_peak_live);
      }
      double health=PositionEdgeHealthV2(side,g_snapshot,health_peak_live);

      if(current_net>peak)
      {
         peak=current_net;
         GVSet(identifier,"PEAK",peak);
         GVSet(identifier,"PTMSC",(double)g_snapshot.tick_time_msc);
      }

      // V50 keeps the old STEP ladder only as a diagnostic comparison reference.
      theoretical_floor=RequiredProtectedFloor(exit_mode,peak);
      GVSet(identifier,"TFLOOR",theoretical_floor);

      // Structural deterioration is computed BEFORE retention decisions. It remains independent of P/L.
      double live_gap_drop_pct=StructuralLiveGapDropPctV46(side,g_snapshot,health_peak_live);

      // Inherited main Vault remains Peak-driven from +1.60. V50 adds ONLY the approved SELL micro pre-alert:
      // Peak [1.20,1.60), SELL EdgeHealth <=50 and live-gap deterioration >=2% => protect +0.30 net.
      bool sell_micro_eligible=SellMicroEmergencyVaultEligible(side,peak,health,live_gap_drop_pct);
      double required_vault=ProfitVaultFloorFromPeak(side,peak);
      if(sell_micro_eligible)
         required_vault=MathMax(required_vault,BASSO_SELL_MICRO_EMERGENCY_FLOOR_EUR);

      if(required_vault>step_floor+0.000001)
      {
         double old_floor=step_floor;
         step_floor=required_vault;
         floor_armed=(step_floor>0.0);
         GVSet(identifier,"EFLOOR",step_floor);
         GVSet(identifier,"PFA",floor_armed?1.0:0.0);

         ulong vs=(ulong)GVGet(identifier,"SEQ",0.0);
         EdgeContext ve; ZeroMemory(ve);
         ve.engine=ENG_BASSO; ve.side=side; ve.sequence_id=vs;
         ve.score=entry_score; ve.score_threshold=entry_threshold;
         ve.score_gap=entry_score_gap; ve.score_shape=entry_score_shape;
         ve.score_context=entry_score_context; ve.score_extra=entry_score_extra;
         RiskContext vr; ZeroMemory(vr); vr.equity=AccountInfoDouble(ACCOUNT_EQUITY);
         string vault_source=(sell_micro_eligible && step_floor<=BASSO_SELL_MICRO_EMERGENCY_FLOOR_EUR+0.000001
                              ? "SELL_MICRO_EMERGENCY" : "MAIN_PROFIT_VAULT");
         string vault_reason=StringFormat("SOURCE=%s;PEAK=%.2f;OLD_FLOOR=%.2f;NEW_FLOOR=%.2f;MONOTONIC=1;SIDE=%s;HEALTH=%.2f;LIVE_GAP_DROP_PCT=%.2f",
                                         vault_source,peak,old_floor,step_floor,SideName(side),health,live_gap_drop_pct);
         LogEvent(g_snapshot,ve,"PROFIT_VAULT","ADVANCE",vault_reason,vr,existing_sl,0,"",
                  0,0,ticket,identifier,volume,price,current_net,peak,step_floor,health,0,entry_atr);
      }
      else if(step_floor>0.0)
      {
         // Monotonic retention: once +0.30 or any higher Vault floor exists, structural recovery cannot remove it.
         floor_armed=true;
         GVSet(identifier,"PFA",1.0);
      }

      // Diagnostic raw edge remains diagnostic only.
      double diag_entry_raw=GVGet(identifier,"DENT",PositionRawEdgeScore(side,g_snapshot.live_gap_atr));
      double diag_current_raw=PositionRawEdgeScore(side,g_snapshot.live_gap_atr);
      double diag_peak_raw=GVGet(identifier,"DPK",diag_entry_raw);
      if(diag_current_raw>diag_peak_raw)
      {
         diag_peak_raw=diag_current_raw;
         GVSet(identifier,"DPK",diag_peak_raw);
      }
      double diag_relative_pct=(MathAbs(diag_peak_raw)>0.000001 ? 100.0*(diag_current_raw/diag_peak_raw) : 0.0);
      double diag_drop_pct=100.0-diag_relative_pct;

      // OLD EdgeHealth DYING: SHADOW/DIAGNOSTIC only — zero close authority in V55.
      EdgeStateV46 edge_state=StructuralEdgeStateV46(side,health,live_gap_drop_pct);
      bool old_edge_dying_shadow=(edge_state==EDGE_STATE_DYING);
      bool edge_dying=false; // legacy latch disabled; V55 score owns structural close
      double slip_reserve=SlippageReserveEUR(volume);

      // V55 structural score for this position side (same formula as entry authority).
      bool v55_ok=false;
      double v55_side_score=V55ScoreForSide(g_snapshot,side,v55_ok);
      if(v55_ok)
      {
         if(g_v55.side==side)
         {
            g_v55.current_score=v55_side_score;
            if(v55_side_score>g_v55.peak_score) g_v55.peak_score=v55_side_score;
         }
         if(side==SIDE_BUY) g_diag_v55_buy_score=v55_side_score;
         else g_diag_v55_sell_score=v55_side_score;
      }
      bool v55_struct_alive=(v55_ok && v55_side_score>=InpV55DeadScore);
      bool v55_dying_m5=(new_bar && v55_ok && v55_side_score<InpV55DeadScore);

      // V49: STRONG and WEAK are both structurally ALIVE. Profit Vault is a separate retention authority and does not alter edge state.
      int previous_edge_state=(int)GVGet(identifier,"ESTATE",-1.0);
      if(previous_edge_state!=(int)edge_state)
      {
         GVSet(identifier,"ESTATE",(double)edge_state);
         ulong ts=(ulong)GVGet(identifier,"SEQ",0.0);
         EdgeContext te; ZeroMemory(te);
         te.engine=ENG_BASSO; te.side=side; te.sequence_id=ts; te.health=health;
         te.score=entry_score; te.score_threshold=entry_threshold;
         te.score_gap=entry_score_gap; te.score_shape=entry_score_shape;
         te.score_context=entry_score_context; te.score_extra=entry_score_extra;
         RiskContext tr; ZeroMemory(tr); tr.equity=AccountInfoDouble(ACCOUNT_EQUITY);
         string why=StringFormat("SHADOW_OLD_EDGE;FROM=%s;TO=%s;OLD_EDGE_ALIVE=%d;V55_SCORE=%.2f;DYNAMIC_TP=%.2f;STEP_REFERENCE=%.2f;VAULT_FLOOR=%.2f",
                                 previous_edge_state<0?"INIT":EdgeStateNameV46((EdgeStateV46)previous_edge_state),
                                 EdgeStateNameV46(edge_state),old_edge_dying_shadow?0:1,v55_side_score,dynamic_tp,theoretical_floor,step_floor);
         LogEvent(g_snapshot,te,"EDGE_STATE_TRANSITION",old_edge_dying_shadow?"OLD_DYING_SHADOW":"HOLD",why,tr,existing_sl,0,"",
                  0,0,ticket,identifier,volume,price,current_net,peak,0.0,health,old_edge_dying_shadow?1:0,entry_atr,
                  diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);
      }

      // Virtual TP objective advances with ProfitPeak while V55 structural edge is alive. It has ZERO exit authority.
      double next_dynamic_tp=DynamicTPFromPeak(peak);
      if(v55_struct_alive && MathAbs(next_dynamic_tp-dynamic_tp)>0.000001)
      {
         double previous_dynamic_tp=dynamic_tp;
         dynamic_tp=next_dynamic_tp;
         GVSet(identifier,"DTP",dynamic_tp);
         ulong ts=(ulong)GVGet(identifier,"SEQ",0.0);
         EdgeContext te; ZeroMemory(te);
         te.engine=ENG_BASSO; te.side=side; te.sequence_id=ts; te.health=health;
         te.score=entry_score; te.score_threshold=entry_threshold;
         te.score_gap=entry_score_gap; te.score_shape=entry_score_shape;
         te.score_context=entry_score_context; te.score_extra=entry_score_extra;
         RiskContext tr; ZeroMemory(tr); tr.equity=AccountInfoDouble(ACCOUNT_EQUITY);
         string why=StringFormat("V55_EDGE_ALIVE=1;OLD_STATE_SHADOW=%s;OLD_TP=%.2f;NEW_TP=%.2f;PEAK=%.2f;NO_CLOSE_ON_TP=1",
                                 EdgeStateNameV46(edge_state),previous_dynamic_tp,dynamic_tp,peak);
         LogEvent(g_snapshot,te,"DYNAMIC_TP","ADVANCE",why,tr,existing_sl,0,"",
                  0,0,ticket,identifier,volume,price,current_net,peak,0.0,health,0,entry_atr,
                  diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);
      }

      // V50 virtual Profit Vault backup is checked BEFORE server synchronization.
      // If price has already touched/crossed the active protected NET floor, close immediately
      // instead of trying to install a stop behind the current market.
      if(floor_armed && current_net<=step_floor &&
         ExitAuthorityRank((int)GVGet(identifier,"EXIT",0.0))<ExitAuthorityRank(2))
      {
         SetExitAuthority(identifier,2);
         exit_pending=(int)GVGet(identifier,"EXIT",0.0);

         ulong vts=(ulong)GVGet(identifier,"SEQ",0.0);
         EdgeContext vte; ZeroMemory(vte);
         vte.engine=ENG_BASSO; vte.side=side; vte.sequence_id=vts; vte.health=health;
         vte.score=entry_score; vte.score_threshold=entry_threshold;
         vte.score_gap=entry_score_gap; vte.score_shape=entry_score_shape;
         vte.score_context=entry_score_context; vte.score_extra=entry_score_extra;
         RiskContext vtr; ZeroMemory(vtr); vtr.equity=AccountInfoDouble(ACCOUNT_EQUITY);
         string trigger_reason=StringFormat("CURRENT_NET=%.2f;PROTECTED_FLOOR=%.2f;PEAK=%.2f;EDGE_STATE_SHADOW=%s;IMMEDIATE_CLOSE=1",
                                           current_net,step_floor,peak,EdgeStateNameV46(edge_state));
         LogEvent(g_snapshot,vte,"PROFIT_VAULT","TRIGGER",trigger_reason,vtr,existing_sl,0,"",
                  0,0,ticket,identifier,volume,price,current_net,peak,step_floor,health,0,entry_atr,
                  diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);
      }

      // V55 EDGE_DYING: only on NEW M5 decision photograph when same-side score < DeadScore.
      if(v55_dying_m5 && ExitAuthorityRank((int)GVGet(identifier,"EXIT",0.0))<ExitAuthorityRank(1))
      {
         double death_msc=GVGet(identifier,"DMSC",0.0);
         if(death_msc<=0.0)
         {
            death_msc=(double)g_snapshot.tick_time_msc;
            GVSet(identifier,"DMSC",death_msc);
         }
         SetExitAuthority(identifier,1);
         exit_pending=(int)GVGet(identifier,"EXIT",0.0);
         g_v55.state=V55_EDGE_DYING;
         g_v55.current_score=v55_side_score;
         g_v55.reentry_armed=false;
         ulong ds=(ulong)GVGet(identifier,"SEQ",0.0);
         EdgeContext de; ZeroMemory(de); de.engine=ENG_BASSO; de.side=side; de.sequence_id=ds; de.edge_id=g_v55.edge_id; de.health=health;
         de.score=v55_side_score; de.score_threshold=InpV55EntryScore;
         de.score_gap=entry_score_gap; de.score_shape=entry_score_shape; de.score_context=entry_score_context; de.score_extra=entry_score_extra;
         RiskContext dr; ZeroMemory(dr); dr.equity=AccountInfoDouble(ACCOUNT_EQUITY);
         double peak_msc=GVGet(identifier,"PTMSC",0.0);
         long peak_to_dying_ms=(peak_msc>0.0 ? (long)MathMax(0.0,death_msc-peak_msc) : -1);
         double giveback=MathMax(0.0,peak-current_net);
         double retention=(peak>0.0 ? 100.0*current_net/peak : 0.0);
         string dying_reason=StringFormat("V55_EDGE_DYING;SCORE=%.2f;DEAD<%.2f;M5_BAR_ONLY;DYNAMIC_TP=%.2f;PEAK_TO_DYING_MS=%I64d;CURRENT_AT_DYING=%.2f;GIVEBACK=%.2f;RETENTION_PCT=%.2f;IMMEDIATE_CLOSE=1",
                                          v55_side_score,InpV55DeadScore,dynamic_tp,peak_to_dying_ms,current_net,giveback,retention);
         LogEvent(g_snapshot,de,"V55_EDGE_DYING","IMMEDIATE_EXIT_LATCH",dying_reason,dr,existing_sl,0,"",
                  0,0,ticket,identifier,volume,price,current_net,peak,step_floor,health,1,entry_atr,
                  diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);
      }

      // OLD EdgeDying shadow log (no latch).
      if(old_edge_dying_shadow && new_bar)
      {
         ulong ods=(ulong)GVGet(identifier,"SEQ",0.0);
         EdgeContext ode; ZeroMemory(ode); ode.engine=ENG_BASSO; ode.side=side; ode.sequence_id=ods; ode.health=health;
         ode.score=entry_score; ode.score_threshold=entry_threshold;
         RiskContext odr; ZeroMemory(odr); odr.equity=AccountInfoDouble(ACCOUNT_EQUITY);
         LogEvent(g_snapshot,ode,"EDGE_DYING_SIGNAL","SHADOW_ONLY","OLD_EDGEHEALTH_DYING_NO_AUTHORITY",odr,existing_sl,0,"",
                  0,0,ticket,identifier,volume,price,current_net,peak,step_floor,health,1,entry_atr,
                  diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);
      }

      // V50 POSITION STATE INVARIANT: main Vault plus optional persisted SELL +0.30 micro floor must remain legal.
      string invariant_reason="";
      bool invariant_ok=PositionStateInvariant(identifier,side,exit_mode,entry_score,entry_threshold,
                                               peak,step_floor,floor_armed,invariant_reason);
      if(ManagedPositionCount()>1)
      {
         invariant_ok=false;
         invariant_reason="MULTIPLE_MANAGED_POSITIONS_INVARIANT_BROKEN";
      }
      if(!invariant_ok)
      {
         SetExitAuthority(identifier,5);
         ulong as=(ulong)GVGet(identifier,"SEQ",0.0);
         EdgeContext ae; ZeroMemory(ae);
         ae.engine=ENG_BASSO; ae.side=side; ae.sequence_id=as; ae.health=health;
         ae.score=entry_score; ae.score_threshold=entry_threshold;
         ae.score_gap=entry_score_gap; ae.score_shape=entry_score_shape;
         ae.score_context=entry_score_context; ae.score_extra=entry_score_extra;
         RiskContext ar; ZeroMemory(ar); ar.equity=AccountInfoDouble(ACCOUNT_EQUITY);
         string why=StringFormat("%s;STATE=%s;THEORETICAL=%.2f;ARMED=%d;ACTIVE=%.2f",
                                 invariant_reason,EdgeStateNameV46(edge_state),theoretical_floor,floor_armed?1:0,step_floor);
         LogEvent(g_snapshot,ae,"RULE_AUDIT","ENFORCED_CLOSE",why,ar,existing_sl,0,"",
                  0,0,ticket,identifier,volume,price,current_net,peak,step_floor,health,edge_dying?1:0,entry_atr,
                  diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);
      }

      double mpp=MoneyPerPointPerLot();
      double hard_loss_trigger=HardLossServerTargetNet()+slip_reserve;
      double fixed_stop_points=GVGet(identifier,"HPTS",0.0);

      // ----------------------------------------------------------------
      // V50 SERVER-SIDE PROTECTION AUTHORITY.
      // Before Vault arming: unchanged hard-loss server authority.
      // After Vault arming: the SAME single broker SL is raised to the active positive net floor.
      // It is never loosened; virtual current_net<=floor remains a parallel immediate-close backup.
      // ----------------------------------------------------------------
      double logical_required_final_net=(floor_armed ? step_floor : -InpBassoHardMaxLossEUR);
      double mandatory_server_target_net=(floor_armed ? step_floor : HardLossServerTargetNet());
      double protection_audit_required_net=mandatory_server_target_net;
      int desired_sl_mode=(floor_armed ? 2 : 3);

      bool protection_ok=false;
      double audited_sl=0.0;
      double projected_sl_net=0.0;
      string protection_audit_reason="";

      // If an immediate exit authority is already latched (DYING or invariant/failsafe),
      // do NOT delay the market close with another SL calculation/modification on this tick.
      exit_pending=(int)GVGet(identifier,"EXIT",0.0);
      if(exit_pending!=0)
      {
         protection_ok=true;
         audited_sl=existing_sl;
         projected_sl_net=0.0;
         protection_audit_reason="SKIPPED_PROTECTION_SYNC_IMMEDIATE_EXIT_ALREADY_LATCHED";
      }
      else
      {
      // 1) Mandatory broker-state audit BEFORE any calculation or modification.
      if(PositionSelectByTicket(ticket))
      {
         protection_ok=InstalledSLMeetsAuthority(ticket,identifier,side,protection_audit_required_net,
                                                 audited_sl,projected_sl_net,protection_audit_reason);
         existing_sl=audited_sl;
      }
      else
         continue;

      // 2) If mandatory authority is insufficient, install EXACTLY the mandatory authority first.
      if(!protection_ok)
      {
         double desired_sl=0.0;
         string sl_calc_reason="";
         bool sl_calc_ok=ProtectiveSLForFinalNet(ticket,identifier,side,mandatory_server_target_net,desired_sl,sl_calc_reason);

         if(sl_calc_ok && MoreProtectiveSL(side,desired_sl,existing_sl))
         {
            uint mod_ret=0;
            string mod_desc="";
            bool mod_ok=ApplyManagedSL(ticket,desired_sl,mod_ret,mod_desc);

            ulong ps=(ulong)GVGet(identifier,"SEQ",0.0);
            EdgeContext pe; ZeroMemory(pe);
            pe.engine=ENG_BASSO; pe.side=side; pe.sequence_id=ps; pe.health=health;
            pe.score=entry_score; pe.score_threshold=entry_threshold;
            pe.score_gap=entry_score_gap; pe.score_shape=entry_score_shape;
            pe.score_context=entry_score_context; pe.score_extra=entry_score_extra;
            RiskContext pr; ZeroMemory(pr); pr.equity=AccountInfoDouble(ACCOUNT_EQUITY);

            string protect_reason=(floor_armed ? "MANDATORY_PROFIT_VAULT_SYNC" : "MANDATORY_HARD_LOSS_SYNC");
            LogEvent(g_snapshot,pe,"PROTECTION_SL",mod_ok?"MODIFY_OK":"MODIFY_FAIL",protect_reason,pr,
                     desired_sl,mod_ret,mod_desc,
                     trade.ResultOrder(),trade.ResultDeal(),ticket,identifier,volume,price,
                     current_net,peak,step_floor,health,edge_dying?1:0,entry_atr,
                     diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);

            if(mod_ok)
            {
               GVSet(identifier,"SLMODE",(double)desired_sl_mode);
               protection_ok=InstalledSLMeetsAuthority(ticket,identifier,side,protection_audit_required_net,
                                                       audited_sl,projected_sl_net,protection_audit_reason);
               existing_sl=audited_sl;
            }
            else
            {
               protection_ok=false;
               protection_audit_reason="MANDATORY_SL_MODIFY_REJECTED";
            }
         }
         else if(sl_calc_ok)
         {
            protection_ok=InstalledSLMeetsAuthority(ticket,identifier,side,protection_audit_required_net,
                                                    audited_sl,projected_sl_net,protection_audit_reason);
            existing_sl=audited_sl;
         }
         else
         {
            protection_ok=false;
            protection_audit_reason="MANDATORY_SL_UNAVAILABLE;"+sl_calc_reason;

            ulong cs=(ulong)GVGet(identifier,"SEQ",0.0);
            EdgeContext ce; ZeroMemory(ce);
            ce.engine=ENG_BASSO; ce.side=side; ce.sequence_id=cs; ce.health=health;
            ce.score=entry_score; ce.score_threshold=entry_threshold;
            ce.score_gap=entry_score_gap; ce.score_shape=entry_score_shape;
            ce.score_context=entry_score_context; ce.score_extra=entry_score_extra;
            RiskContext cr; ZeroMemory(cr); cr.equity=AccountInfoDouble(ACCOUNT_EQUITY);
            LogEvent(g_snapshot,ce,"PROTECTION_SL","MANDATORY_CALC_FAIL",protection_audit_reason,cr,
                     existing_sl,0,"",0,0,ticket,identifier,volume,price,
                     current_net,peak,step_floor,health,edge_dying?1:0,entry_atr,
                     diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);
         }
      }

      } // end protection synchronization only when no immediate exit was already latched

      // 3) One final audit result controls the next action. PASS = trade may HOLD.
      // Anything else = enforced close. No fallback and no alternate interpretation.
      ulong vas=(ulong)GVGet(identifier,"SEQ",0.0);
      EdgeContext vae; ZeroMemory(vae);
      vae.engine=ENG_BASSO; vae.side=side; vae.sequence_id=vas; vae.health=health;
      vae.score=entry_score; vae.score_threshold=entry_threshold;
      vae.score_gap=entry_score_gap; vae.score_shape=entry_score_shape;
      vae.score_context=entry_score_context; vae.score_extra=entry_score_extra;
      RiskContext var; ZeroMemory(var); var.equity=AccountInfoDouble(ACCOUNT_EQUITY);
      string audit_text=StringFormat("%s;STATE=%s;STEP_REFERENCE=%.2f;DYNAMIC_TP=%.2f;VAULT_ARMED=%d;VAULT_FLOOR=%.2f;PROJECTED_SL_NET=%.2f;AUDIT_REQUIRED=%.2f;LOGICAL_REQUIRED=%.2f;MANDATORY_SERVER_TARGET=%.2f",
                                     protection_audit_reason,EdgeStateNameV46(edge_state),theoretical_floor,dynamic_tp,
                                     floor_armed?1:0,step_floor,projected_sl_net,protection_audit_required_net,logical_required_final_net,mandatory_server_target_net);
      LogEvent(g_snapshot,vae,"RULE_AUDIT",protection_ok?"PASS":"ENFORCED_CLOSE",audit_text,var,audited_sl,0,"",
               0,0,ticket,identifier,volume,price,current_net,peak,step_floor,health,edge_dying?1:0,entry_atr,
               diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);

      if(!protection_ok)
      {
         exit_pending=6;
         SetExitAuthority(identifier,6);
      }

      // V49: main/micro Profit Vault share the same approved positive exit authority; hard loss and EdgeDying remain independently active.
      if(current_net<=hard_loss_trigger)
      {
         exit_pending=3;
         SetExitAuthority(identifier,3);
      }
      else if(exit_pending==0 && v55_dying_m5)
      {
         // Defensive fallback: V55 DYING normally latches before protection synchronization.
         exit_pending=1;
         SetExitAuthority(identifier,1);
      }

      // V39 ZERO-BYPASS ASSERTION. A required rule may be either SATISFIED or already ENFORCED by exit.
      // It is a true bypass only if the state is non-compliant AND no adequate exit authority is latched.
      int asserted_exit=(int)GVGet(identifier,"EXIT",0.0);
      bool logical_bypass=false;
      string bypass_reason="";
      if(!invariant_ok && ExitAuthorityRank(asserted_exit)<ExitAuthorityRank(5))
      {
         logical_bypass=true; bypass_reason="INVARIANT_FAIL_WITHOUT_FAILSAFE_AUTHORITY";
      }
      else if(!protection_ok && ExitAuthorityRank(asserted_exit)<ExitAuthorityRank(6))
      {
         logical_bypass=true; bypass_reason="PROTECTION_FAIL_WITHOUT_FAILSAFE_AUTHORITY";
      }
      else if(current_net<=hard_loss_trigger && ExitAuthorityRank(asserted_exit)<ExitAuthorityRank(3))
      {
         logical_bypass=true; bypass_reason="HARD_LOSS_TRIGGER_WITHOUT_EXIT_AUTHORITY";
      }
      else if(floor_armed && current_net<=step_floor && ExitAuthorityRank(asserted_exit)<ExitAuthorityRank(2))
      {
         logical_bypass=true; bypass_reason="PROFIT_VAULT_TRIGGER_WITHOUT_EXIT_AUTHORITY";
      }
      else if(v55_dying_m5 && ExitAuthorityRank(asserted_exit)<ExitAuthorityRank(1))
      {
         logical_bypass=true; bypass_reason="V55_EDGE_DYING_WITHOUT_EXIT_AUTHORITY";
      }

      if(logical_bypass)
      {
         SetExitAuthority(identifier,5);
         asserted_exit=(int)GVGet(identifier,"EXIT",0.0);
         ulong bs=(ulong)GVGet(identifier,"SEQ",0.0);
         EdgeContext be; ZeroMemory(be);
         be.engine=ENG_BASSO; be.side=side; be.sequence_id=bs; be.health=health;
         be.score=entry_score; be.score_threshold=entry_threshold;
         be.score_gap=entry_score_gap; be.score_shape=entry_score_shape;
         be.score_context=entry_score_context; be.score_extra=entry_score_extra;
         RiskContext br; ZeroMemory(br); br.equity=AccountInfoDouble(ACCOUNT_EQUITY);
         LogEvent(g_snapshot,be,"RULE_BYPASS","FAIL",bypass_reason,br,existing_sl,0,"",
                  0,0,ticket,identifier,volume,price,current_net,peak,step_floor,health,edge_dying?1:0,entry_atr,
                  diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);
      }

      // Read the single latched authority AFTER every possible rule/protection update.
      exit_pending=(int)GVGet(identifier,"EXIT",0.0);

      int death_count=(v55_dying_m5 || ((int)GVGet(identifier,"EXIT",0.0)==1) ? 1 : 0);
      GVSet(identifier,"DC",(double)death_count);

      // V54: OPS-bar edge path for M5 calibration (not every tick — avoids tester blow-up).
      if(new_bar)
      {
         ulong diag_seq=(ulong)GVGet(identifier,"SEQ",0.0);
         EdgeContext de; ZeroMemory(de);
         de.engine=ENG_BASSO; de.side=side; de.sequence_id=diag_seq; de.health=health;
         de.score=entry_score; de.score_threshold=entry_threshold;
         de.score_gap=entry_score_gap; de.score_shape=entry_score_shape;
         de.score_context=entry_score_context; de.score_extra=entry_score_extra;
         RiskContext dr; ZeroMemory(dr); dr.equity=AccountInfoDouble(ACCOUNT_EQUITY);
         MarketSnapshot ds=g_snapshot; ds.time=TimeCurrent();
         string diag_reason=StringFormat("OPS_BAR;STATE=%s;EDGE_ALIVE=%d;HEALTH=%.2f;LIVE_GAP_DROP_PCT=%.2f;DYNAMIC_TP=%.2f;VAULT_ARMED=%d;VAULT_FLOOR=%.2f;EXIT=%d;NET=%.2f;PEAK=%.2f;THRESHOLDS=M1_BASELINE_ON_M5",
                                         EdgeStateNameV46(edge_state),edge_dying?0:1,health,live_gap_drop_pct,dynamic_tp,
                                         floor_armed?1:0,step_floor,exit_pending,current_net,peak);
         LogEvent(ds,de,"EDGE_BAR","SAMPLE",diag_reason,dr,existing_sl,0,"",
                  0,0,ticket,identifier,volume,price,current_net,peak,step_floor,health,death_count,entry_atr,
                  diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,live_gap_drop_pct);
      }

      bool close_now=(exit_pending>0);
      string close_reason="";
      if(exit_pending==1) close_reason="V55_EDGE_DYING";
      if(exit_pending==2) close_reason="PROFIT_VAULT_PROTECTED_NET_CLOSE";
      if(exit_pending==3) close_reason="HARD_LOSS_3_50_PROTECTIVE_CLOSE";
      if(exit_pending==5) close_reason="RULE_INVARIANT_FAILSAFE_CLOSE";
      if(exit_pending==6) close_reason="PROTECTION_AUTHORITY_FAILSAFE_CLOSE";

      if(close_now)
      {
         ulong seq=(ulong)GVGet(identifier,"SEQ",0.0);
         EdgeContext e; ZeroMemory(e);
         e.engine=ENG_BASSO; e.side=side; e.sequence_id=seq; e.health=health;
         e.score=entry_score; e.score_threshold=entry_threshold;
         e.score_gap=entry_score_gap; e.score_shape=entry_score_shape;
         e.score_context=entry_score_context; e.score_extra=entry_score_extra;

         RiskContext rr; ZeroMemory(rr);
         rr.equity=AccountInfoDouble(ACCOUNT_EQUITY);
         rr.risk_money=InpBassoHardMaxLossEUR;
         rr.stop_points=(fixed_stop_points>0.0 ? fixed_stop_points :
                         (mpp>0.0 && volume>0.0 ? MathAbs(HardLossServerTargetNet())/(mpp*volume) : 0.0));
         rr.actual_risk=MathAbs(HardLossServerTargetNet());

         trade.SetExpertMagicNumber(InpMagic);
         trade.SetDeviationInPoints(InpSlippagePoints);
         if(exit_pending==1 && GVGet(identifier,"CRMSC",0.0)<=0.0)
            GVSet(identifier,"CRMSC",(double)g_snapshot.tick_time_msc);
         bool basic_close=trade.PositionClose(ticket,InpSlippagePoints);
         uint close_ret=trade.ResultRetcode();
         bool close_request_ok=(basic_close && TradeRetcodeCloseConfirmed(close_ret));
         if(close_request_ok) g_close_inflight=true;

         LogEvent(g_snapshot,e,"LIFECYCLE",close_request_ok?"CLOSE_REQUEST_OK":"CLOSE_REQUEST_FAIL",
                  close_reason,rr,existing_sl,close_ret,trade.ResultRetcodeDescription(),
                  trade.ResultOrder(),trade.ResultDeal(),ticket,identifier,volume,price,
                  current_net,peak,step_floor,health,death_count,entry_atr,
                  diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);
      }
      else if(new_bar)
      {
         ulong seq=(ulong)GVGet(identifier,"SEQ",0.0);
         EdgeContext e; ZeroMemory(e);
         e.engine=ENG_BASSO; e.side=side; e.sequence_id=seq; e.health=health;
         e.score=entry_score; e.score_threshold=entry_threshold;
         e.score_gap=entry_score_gap; e.score_shape=entry_score_shape;
         e.score_context=entry_score_context; e.score_extra=entry_score_extra;
         RiskContext rr; ZeroMemory(rr); rr.equity=AccountInfoDouble(ACCOUNT_EQUITY);

         LogEvent(g_snapshot,e,"LIFECYCLE","HOLD",StringFormat("EDGE_ALIVE_HOLD;DYNAMIC_TP=%.2f;STATE=%s;NO_PROFIT_EXIT=1",dynamic_tp,EdgeStateNameV46(edge_state)),rr,existing_sl,0,"",
                  0,0,ticket,identifier,volume,price,current_net,peak,step_floor,health,death_count,entry_atr,
                  diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);
      }
   }
}

bool ValidateOperationalInputs(string &why)
{
   why="OK";
   if(InpV55EntryScore<=0.0 || InpV55EntryScore>100.0)
   { why="INVALID_V55_ENTRY_SCORE"; return false; }
   if(InpV55StrongScore<InpV55EntryScore || InpV55StrongScore>100.0)
   { why="INVALID_V55_STRONG_SCORE"; return false; }
   if(InpV55DeadScore<=0.0 || InpV55DeadScore>=InpV55EntryScore)
   { why="INVALID_V55_DEAD_SCORE"; return false; }
   if(InpBuyEntryScoreMin<=0.0 || InpBuyEntryScoreMin>100.0)
   { why="INVALID_OLD_BUY_ENTRY_SCORE_THRESHOLD_SHADOW"; return false; }
   if(BASSO_BUY_ENTRY_SCORE_MAX<=InpBuyEntryScoreMin || BASSO_BUY_ENTRY_SCORE_MAX>100.0)
   { why="INVALID_OLD_BUY_OVEREXTENSION_MAX_SHADOW"; return false; }
   if(InpSellEntryScoreMin<=0.0 || InpSellEntryScoreMin>100.0)
   { why="INVALID_OLD_SELL_ENTRY_SCORE_THRESHOLD_SHADOW"; return false; }
   if(InpBassoHardMaxLossEUR<=0.0)
   { why="INVALID_HARD_MAX_LOSS"; return false; }
   if(BASSO_HARD_EXECUTION_RESERVE_EUR<=0.0 || BASSO_HARD_EXECUTION_RESERVE_EUR>=InpBassoHardMaxLossEUR)
   { why="INVALID_HARD_EXECUTION_RESERVE"; return false; }
   if(BASSO_PEAK_ARM_EUR<=BASSO_MIN_PROTECTED_EUR || BASSO_MIN_PROTECTED_EUR<=0.0)
   { why="INVALID_PROFIT_PROTECTION_CONSTANTS"; return false; }
   if(InpSlippagePoints<0 || InpMaxSpreadPoints<=0)
   { why="INVALID_EXECUTION_LIMITS"; return false; }
   if(InpTradeStartHour<0 || InpTradeStartHour>23 || InpTradeEndHour<0 || InpTradeEndHour>23)
   { why="INVALID_ENTRY_SESSION_HOURS"; return false; }
   if(InpEdgeStrongHealth<=0.0 || InpEdgeStrongHealth>100.0)
   { why="INVALID_EDGE_STRONG_HEALTH"; return false; }
   if(InpBuyEdgeAliveHealthV2<=0.0 || InpBuyEdgeAliveHealthV2>100.0)
   { why="INVALID_BUY_EDGEHEALTH_V2"; return false; }
   if(InpSellEdgeAliveHealthV2<=0.0 || InpSellEdgeAliveHealthV2>100.0)
   { why="INVALID_SELL_EDGEHEALTH_V2"; return false; }
   if(InpBuyEdgeDyingHealthV46<=InpBuyEdgeAliveHealthV2 || InpBuyEdgeDyingHealthV46>100.0)
   { why="INVALID_BUY_EDGE_DYING_HEALTH_V46"; return false; }
   if(InpBuyEdgeDyingDropPctV46<0.0 || InpBuyEdgeDyingDropPctV46>100.0)
   { why="INVALID_BUY_EDGE_DYING_DROP_V46"; return false; }
   if(InpSellEdgeDyingHealthV46<=InpSellEdgeAliveHealthV2 || InpSellEdgeDyingHealthV46>100.0)
   { why="INVALID_SELL_EDGE_DYING_HEALTH_V46"; return false; }
   if(InpSellEdgeDyingDropPctV46<0.0 || InpSellEdgeDyingDropPctV46>100.0)
   { why="INVALID_SELL_EDGE_DYING_DROP_V46"; return false; }
   if(InpBuyEdgeWeakHealthV46<=InpBuyEdgeDyingHealthV46 || InpBuyEdgeWeakHealthV46>100.0)
   { why="INVALID_BUY_EDGE_WEAK_HEALTH_V46"; return false; }
   if(InpBuyEdgeWeakDropPctV46<0.0 || InpBuyEdgeWeakDropPctV46>=InpBuyEdgeDyingDropPctV46)
   { why="INVALID_BUY_EDGE_WEAK_DROP_V46"; return false; }
   if(InpSellEdgeWeakHealthV46<=InpSellEdgeDyingHealthV46 || InpSellEdgeWeakHealthV46>100.0)
   { why="INVALID_SELL_EDGE_WEAK_HEALTH_V46"; return false; }
   if(InpSellEdgeWeakDropPctV46<0.0 || InpSellEdgeWeakDropPctV46>=InpSellEdgeDyingDropPctV46)
   { why="INVALID_SELL_EDGE_WEAK_DROP_V46"; return false; }
   return true;
}

//+------------------------------------------------------------------+
//| Init / Tick / broker truth                                        |
//+------------------------------------------------------------------+
int OnInit()
{
   g_diag_run_tag=StringFormat("R%08u",(uint)GetTickCount());
   CsvCreate();
   // Static EVENTS schema: header columns must equal LogEvent row values (DIAG_EVENTS_COLUMNS).
   int events_hdr_cols=DiagCsvFieldCount(DiagEventHeader());
   if(events_hdr_cols!=DIAG_EVENTS_COLUMNS)
      PrintFormat("EVENTS header column count mismatch: header=%d expected=%d",events_hdr_cols,DIAG_EVENTS_COLUMNS);
   string input_reason="";
   if(!ValidateOperationalInputs(input_reason))
   {
      LogSystem("INIT","FAIL",input_reason);
      return INIT_PARAMETERS_INCORRECT;
   }
   if(!ResolveXauUsdSymbol())
   {
      LogSystem("INIT","FAIL",g_symbol_resolution);
      return INIT_FAILED;
   }

   g_close_inflight=false;
   ZeroMemory(g_v55);
   g_v55.state=V55_EDGE_OFF;
   g_diag_v55_entry_authority="NA";
   g_diag_v55_old_score_decision="NA";
   g_diag_v55_old_fp_decision="NA";
   g_runtime_ok=SymbolRuntimeUsable(g_symbol);
   trade.SetExpertMagicNumber(InpMagic);
   trade.SetDeviationInPoints(InpSlippagePoints);
   bool nb=false;
   RefreshSnapshot(nb);
   LogSystem("INIT",g_runtime_ok?"OK":"FAIL",g_symbol_resolution+";symbol="+g_symbol+";ops_tf=PERIOD_M5;delta_horizon_bars=6;V59_SELL_ONLY;NO_BUY_TO_SELL_REMAP=1;signed_gap_std3_min="+DoubleToString(V59_SELL_SIGNED_GAP_STD3_MIN,5)+";delta30_mean5_max="+DoubleToString(V59_SELL_DELTA30_MEAN5_MAX,9)+";V55_entry="+DoubleToString(InpV55EntryScore,1)+";V55_strong="+DoubleToString(InpV55StrongScore,1)+";V55_dead="+DoubleToString(InpV55DeadScore,1)+";trading="+(InpEnableTrading?"ON":"OFF")+";old_brain=SHADOW_ONLY;no_auto_reentry");
   return g_runtime_ok ? INIT_SUCCEEDED : INIT_FAILED;
}

void OnDeinit(const int reason)
{
   if(g_snapshot_ok) DiagFlushOpenRejects("TEST_END_OR_DEINIT");
   DiagFlushDaily();
}

void OnTick()
{
   if(!g_runtime_ok) return;

   bool new_bar=false;
   if(!RefreshSnapshot(new_bar)) return;
   if(new_bar) DiagDailyTouch(DiagDayClockFromSnapshot(g_snapshot));

   // Hard Loss + Profit Vault: every tick. V55 EDGE_DYING latch: only inside ManagePositions on new_bar.
   ManagePositions(new_bar);
   UpdateRejectTracks();
   DiagUpdateRejectTracks();
   UpdatePostCloseTracks();

   // Legacy V50 continuation path remains disarmed (no operational authority).
   if(g_cont_reentry_armed)
   {
      g_cont_reentry_armed=false;
      g_cont_reentry_side=SIDE_NONE;
      g_cont_reentry_peak_live=0.0;
   }

   RiskContext blank; ZeroMemory(blank); blank.equity=AccountInfoDouble(ACCOUNT_EQUITY);

   // V59: automatic Profit-Vault re-entry is fully disabled (no V55_BUY/SELL_REENTRY).
   if(g_v55.reentry_armed)
      g_v55.reentry_armed=false;
   // Stale BUY edge must never direction-lock SELL-only book.
   if(ManagedPositionCount()==0 && !g_close_inflight && g_v55.side==SIDE_BUY)
      V55ResetEdge("V59_CLEAR_STALE_BUY_EDGE");

   if(!new_bar) return;

   // One structural snapshot per completed OPS (M5) decision point.
   PushFingerprintSnapshot(g_snapshot);

   // --- OLD brain SHADOW (no entry authority) ---
   EdgeContext old_e=Route(g_snapshot);
   FingerprintDecision old_fp; ZeroMemory(old_fp);
   old_fp.bad_id="NA"; old_fp.good_id="NA"; old_fp.decision="NOT_EVALUATED";
   if(old_e.side==SIDE_BUY || old_e.side==SIDE_SELL)
      EvaluateFingerprint(old_e.side,old_e,old_fp);
   g_diag_v55_old_score_decision=(old_e.engine==ENG_BASSO ? "OLD_SCORE_ACCEPT" :
                                  (old_e.engine==ENG_SUPER ? "OLD_SUPER_BLOCK" :
                                   (old_e.engine==ENG_SCARTO ? "OLD_SCORE_REJECT" : "OLD_NONE")));
   g_diag_v55_old_fp_decision=(old_fp.decision=="" ? "NOT_EVALUATED" : old_fp.decision);
   LogEvent(g_snapshot,old_e,"V55_OLD_BRAIN_SHADOW","SHADOW",
            StringFormat("OLD_ENGINE=%s;OLD_SIDE=%s;OLD_SCORE=%.2f;OLD_REASON=%s;OLD_FP=%s",
                         EngineName(old_e.engine),SideName(old_e.side),old_e.score,old_e.reason,g_diag_v55_old_fp_decision),
            blank,0,0,"",0,0,0,0,0,0,0,0,0,old_e.health,0,g_snapshot.atr_m1,
            0,0,0,0,0,old_fp.ready?1:0,old_fp.bad_match,old_fp.good_match,old_fp.bad_id,old_fp.good_id,old_fp.decision);
   if(old_e.engine==ENG_SCARTO && old_e.side!=SIDE_NONE)
      AddRejectTrack(old_e,g_snapshot,"SCORE_REJECT"); // mirror only
   if(old_e.engine==ENG_SUPER)
   {
      EdgeContext srej=old_e; srej.reason="SUPER_CANNOT_START_FROM_ZERO";
      AddRejectTrack(srej,g_snapshot,"SUPER_BLOCK");
   }
   if(old_fp.reject)
   {
      EdgeContext frej=old_e;
      frej.reason="OLD_FINGERPRINT_SHADOW_REJECT";
      AddRejectTrack(frej,g_snapshot,"FINGERPRINT_REJECT");
   }

   // --- V55 scores (always both) ---
   bool buy_ok=false,sell_ok=false;
   double buy_sc=V55BuyScore(g_snapshot,buy_ok);
   double sell_sc=V55SellScore(g_snapshot,sell_ok);
   if(!buy_ok || !sell_ok)
   {
      V55PublishDiag(0,0,SIDE_NONE,0,"INVALID_ATR");
      EdgeContext ie; ZeroMemory(ie); ie.engine=ENG_BASSO;
      LogEvent(g_snapshot,ie,"V55_ENTRY_REJECT","BLOCK","INVALID_ATR",blank,0,0,"",0,0,0,0,0,0,0,0,0,0,0,0);
      return;
   }

   TradeSide sel=SIDE_NONE;
   double sel_sc=0.0;
   if(buy_sc>=InpV55EntryScore && sell_sc>=InpV55EntryScore)
   {
      if(buy_sc>=sell_sc){ sel=SIDE_BUY; sel_sc=buy_sc; }
      else { sel=SIDE_SELL; sel_sc=sell_sc; }
   }
   else if(buy_sc>=InpV55EntryScore){ sel=SIDE_BUY; sel_sc=buy_sc; }
   else if(sell_sc>=InpV55EntryScore){ sel=SIDE_SELL; sel_sc=sell_sc; }

   // V59 absolute side rule: preserve the original V56 winner. If BUY wins, BUY is blocked and there is NO BUY->SELL remap.
   if(sel==SIDE_BUY)
   {
      EdgeContext bd; ZeroMemory(bd); bd.engine=ENG_BASSO; bd.side=SIDE_BUY;
      bd.score=sel_sc; bd.score_threshold=InpV55EntryScore; bd.reason="V59_BUY_DISABLED";
      LogEvent(g_snapshot,bd,"V59_BUY_DISABLED","BLOCK",
               StringFormat("raw_side=BUY;buy=%.2f;sell=%.2f;remap=NONE",buy_sc,sell_sc),
               blank,0,0,"",0,0,0,0,0,0,0,0,0,0,0,g_snapshot.atr_m1);
      sel=SIDE_NONE;
      sel_sc=0.0;
   }

   V55PublishDiag(buy_sc,sell_sc,sel,sel_sc,(sel==SIDE_NONE?"V55_NO_ENTRY":"V55_CANDIDATE"));

   // Update / start V55 edge state on M5 decision. V59: only SELL may start an edge.
   bool v55_edge_started_this_bar=false;
   if(g_v55.state==V55_EDGE_OFF)
   {
      if(sel==SIDE_SELL)
      {
         g_v55.state=V55StateFromScore(sel_sc);
         g_v55.side=SIDE_SELL;
         g_v55.edge_id=NewEdgeID();
         g_v55.entry_score=sel_sc;
         g_v55.current_score=sel_sc;
         g_v55.peak_score=sel_sc;
         g_v55.reentry_armed=false;
         v55_edge_started_this_bar=true;
         EdgeContext se; ZeroMemory(se); se.engine=ENG_BASSO; se.side=SIDE_SELL; se.edge_id=g_v55.edge_id;
         se.score=sel_sc; se.score_threshold=InpV55EntryScore;
         LogEvent(g_snapshot,se,"V55_EDGE_START","START",
                  StringFormat("SIDE=SELL;SCORE=%.2f;STATE=%s",sel_sc,V55EdgeStateName(g_v55.state)),
                  blank,0,0,"",0,0,0,0,0,0,0,0,0,0,0,g_snapshot.atr_m1);
         if(g_v55.state==V55_EDGE_STRONG)
            LogEvent(g_snapshot,se,"V55_EDGE_STRONG","STRONG",StringFormat("SCORE=%.2f",sel_sc),blank,0,0,"",0,0,0,0,0,0,0,0,0,0,0,0);
      }
   }
   else
   {
      bool sok=false;
      double side_sc=V55ScoreForSide(g_snapshot,g_v55.side,sok);
      if(sok)
      {
         g_v55.current_score=side_sc;
         if(side_sc>g_v55.peak_score) g_v55.peak_score=side_sc;
         V55EdgeState prev=g_v55.state;
         if(side_sc<InpV55DeadScore)
         {
            g_v55.state=V55_EDGE_DYING;
            if(ManagedPositionCount()==0)
            {
               EdgeContext de; ZeroMemory(de); de.engine=ENG_BASSO; de.side=g_v55.side; de.edge_id=g_v55.edge_id;
               de.score=side_sc; de.score_threshold=InpV55EntryScore;
               LogEvent(g_snapshot,de,"V55_EDGE_DYING","DEAD_FLAT",StringFormat("SCORE=%.2f",side_sc),blank,0,0,"",0,0,0,0,0,0,0,0,0,0,0,0);
               V55ResetEdge("V55_EDGE_DEAD_FLAT");
            }
         }
         else
         {
            g_v55.state=V55StateFromScore(side_sc);
            if(prev!=V55_EDGE_STRONG && g_v55.state==V55_EDGE_STRONG)
            {
               EdgeContext se; ZeroMemory(se); se.engine=ENG_BASSO; se.side=g_v55.side; se.edge_id=g_v55.edge_id;
               se.score=side_sc; se.score_threshold=InpV55EntryScore;
               LogEvent(g_snapshot,se,"V55_EDGE_STRONG","STRONG",StringFormat("SCORE=%.2f",side_sc),blank,0,0,"",0,0,0,0,0,0,0,0,0,0,0,0);
            }
         }
      }
   }

   if(ManagedPositionCount()>0 || g_close_inflight) return;
   if(sel==SIDE_NONE)
   {
      EdgeContext ne; ZeroMemory(ne); ne.engine=ENG_BASSO;
      LogEvent(g_snapshot,ne,"V55_ENTRY_REJECT","BLOCK",
               StringFormat("BUY=%.2f;SELL=%.2f;NEED>=%.2f",buy_sc,sell_sc,InpV55EntryScore),
               blank,0,0,"",0,0,0,0,0,0,0,0,0,0,0,0);
      return;
   }

   // No reverse while opposite-side edge still alive (>= DeadScore).
   if(g_v55.state!=V55_EDGE_OFF && g_v55.state!=V55_EDGE_DYING &&
      g_v55.side!=SIDE_NONE && g_v55.side!=sel && g_v55.current_score>=InpV55DeadScore)
   {
      EdgeContext be; ZeroMemory(be); be.engine=ENG_BASSO; be.side=sel; be.edge_id=g_v55.edge_id;
      be.score=sel_sc; be.score_threshold=InpV55EntryScore;
      LogEvent(g_snapshot,be,"V55_ENTRY_REJECT","BLOCK","DIRECTION_LOCKED_ACTIVE_EDGE",blank,0,0,"",0,0,0,0,0,0,0,0,0,0,0,0);
      return;
   }

   // Initial entry only on the M5 bar that starts a new edge. V59: no automatic re-entry.
   if(g_v55.reentry_armed) g_v55.reentry_armed=false;
   if(!v55_edge_started_this_bar) return;

   EdgeContext e; ZeroMemory(e);
   e.engine=ENG_BASSO;
   e.side=sel;
   e.edge_id=g_v55.edge_id;
   e.sequence_id=NewSequenceID();
   e.score=sel_sc;
   e.score_threshold=InpV55EntryScore;
   e.reason=(sel==SIDE_BUY ? "V55_BUY_EDGE" : "V55_SELL_EDGE");
   e.health=(sel==SIDE_BUY ? g_snapshot.buy_health : g_snapshot.sell_health);
   // Preserve OLD score components diagnostically on the context for CSV comparison.
   e.score_gap=old_e.score_gap; e.score_shape=old_e.score_shape;
   e.score_context=old_e.score_context; e.score_extra=old_e.score_extra;

   V55PublishDiag(buy_sc,sell_sc,sel,sel_sc,"V55_ENTRY_ACCEPT");

   // V59: BUY remains diagnostic/shadow only — never open a real BUY.
   if(sel!=SIDE_SELL)
   {
      e.reason="V59_BUY_DISABLED";
      LogEvent(g_snapshot,e,"V59_BUY_DISABLED","BLOCK",
               StringFormat("side=%s;score=%.2f;decision=BLOCK",SideName(sel),sel_sc),
               blank,0,0,"",0,0,0,0,0,0,0,0,0,e.health,0,g_snapshot.atr_m1);
      DiagWriteM5Candidate(g_snapshot,e,"REJECT","V59_BUY_DISABLED",old_fp,true);
      AddRejectTrack(e,g_snapshot,"ENTRY_AUTHORITY_BLOCK");
      return;
   }

   // V59 mandatory SELL gates: both features are pre-entry values from the completed-M5 fingerprint history (no lookahead).
   FingerprintFeatures sell_ff;
   bool sell_ff_ok=BuildFingerprintFeatures(SIDE_SELL,sell_ff);
   double signed_gap_std3=(sell_ff_ok ? sell_ff.signed_gap_std3 : 0.0);
   double delta30_mean5=(sell_ff_ok ? sell_ff.delta30_mean5 : 0.0);
   e.v59_signed_gap_std3_valid=sell_ff_ok;
   e.v59_signed_gap_std3=signed_gap_std3;
   e.v59_delta30_mean5_valid=sell_ff_ok;
   e.v59_delta30_mean5=delta30_mean5;
   bool signature_ok=(sell_ff_ok &&
                      signed_gap_std3>=V59_SELL_SIGNED_GAP_STD3_MIN &&
                      delta30_mean5<=V59_SELL_DELTA30_MEAN5_MAX);
   string sig_detail=StringFormat("side=SELL;score=%.2f;f_signed_gap_std3=%.8f;gap_min=%.5f;f_delta30_mean5=%.9f;delta30_max=%.9f;decision=%s;sequence_id=%I64u;edge_id=%I64u",
                                  sel_sc,signed_gap_std3,V59_SELL_SIGNED_GAP_STD3_MIN,
                                  delta30_mean5,V59_SELL_DELTA30_MEAN5_MAX,
                                  signature_ok?"ACCEPT":"REJECT",e.sequence_id,e.edge_id);
   if(!signature_ok)
   {
      e.reason="V59_SELL_SIGNATURE_REJECT";
      LogEvent(g_snapshot,e,"V59_SELL_SIGNATURE_REJECT","BLOCK",sig_detail,
               blank,0,0,"",0,0,0,0,0,0,0,0,0,e.health,0,g_snapshot.atr_m1);
      DiagWriteM5Candidate(g_snapshot,e,"REJECT","V59_SELL_SIGNATURE_REJECT",old_fp,true);
      AddRejectTrack(e,g_snapshot,"ENTRY_AUTHORITY_BLOCK");
      return;
   }
   e.reason="V59_SELL_SIGNATURE";
   LogEvent(g_snapshot,e,"V59_SELL_SIGNATURE_ACCEPT","ACCEPT",sig_detail,
            blank,0,0,"",0,0,0,0,0,0,0,0,0,e.health,0,g_snapshot.atr_m1);

   string entry_authority_reason="";
   if(!EntryAuthorityPass(g_snapshot,e,entry_authority_reason))
   {
      string stage="V55_ENTRY_REJECT";
      string origin="ENTRY_AUTHORITY_BLOCK";
      if(entry_authority_reason=="OUTSIDE_08_20_ENTRY_SESSION"){ stage="SESSION_GATE"; origin="SESSION_BLOCK"; }
      else if(entry_authority_reason=="SPREAD_LIMIT"){ origin="SPREAD_BLOCK"; }
      else if(entry_authority_reason=="ONE_POSITION_ALREADY_ACTIVE" ||
              entry_authority_reason=="ENTRY_ORDER_ALREADY_ACTIVE"){ origin="POSITION_BLOCK"; }
      DiagWriteM5Candidate(g_snapshot,e,"REJECT",entry_authority_reason,old_fp,true);
      LogEvent(g_snapshot,e,stage,"BLOCK",entry_authority_reason,blank,0,0,"",0,0,0,0,0,0,0,0,0,e.health,0,0);
      EdgeContext blocked=e; blocked.reason=entry_authority_reason;
      AddRejectTrack(blocked,g_snapshot,origin);
      return;
   }

   RiskContext r=BuildRisk(g_snapshot,e);
   LogEvent(g_snapshot,e,"RISK",r.decision==RISK_APPROVE?"APPROVE":"BLOCK",r.reason,r,0,0,"",0,0,0,0,0,0,0,0,0,e.health,0,g_snapshot.atr_m1);
   if(r.decision!=RISK_APPROVE)
   {
      V55PublishDiag(buy_sc,sell_sc,sel,sel_sc,"RISK_BLOCK");
      DiagWriteM5Candidate(g_snapshot,e,"REJECT",r.reason,old_fp,true);
      LogEvent(g_snapshot,e,"V55_ENTRY_REJECT","BLOCK",r.reason,r,0,0,"",0,0,0,0,0,0,0,0,0,e.health,0,0);
      EdgeContext rb=e; rb.reason=r.reason;
      AddRejectTrack(rb,g_snapshot,"RISK_BLOCK");
      return;
   }

   LogEvent(g_snapshot,e,"V55_ENTRY_ACCEPT","ACCEPT",e.reason,r,0,0,"",0,0,0,0,0,0,0,0,0,e.health,0,g_snapshot.atr_m1);
   DiagWriteM5Candidate(g_snapshot,e,"ACCEPT",e.reason,old_fp,true);
   g_v55.entry_score=sel_sc;
   g_v55.current_score=sel_sc;
   if(sel_sc>g_v55.peak_score) g_v55.peak_score=sel_sc;
   Execute(g_snapshot,e,r);
}

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;

   string sym=HistoryDealGetString(trans.deal,DEAL_SYMBOL);
   long magic=(long)HistoryDealGetInteger(trans.deal,DEAL_MAGIC);
   if(sym!=g_symbol || magic!=InpMagic) return;

   long entry=(long)HistoryDealGetInteger(trans.deal,DEAL_ENTRY);
   long dtype=(long)HistoryDealGetInteger(trans.deal,DEAL_TYPE);
   long deal_reason=(long)HistoryDealGetInteger(trans.deal,DEAL_REASON);

   ulong identifier=(ulong)HistoryDealGetInteger(trans.deal,DEAL_POSITION_ID);
   if(identifier==0) identifier=trans.position;

   TradeSide side=SIDE_NONE;
   double broker_sl=0.0;
   bool entry_filled_without_sl=false;
   bool entry_state_invariant_fail=false;
   bool entry_protection_audit_fail=false;
   string entry_audit_reason="";
   if(entry==DEAL_ENTRY_IN || entry==DEAL_ENTRY_INOUT)
   {
      side=(dtype==DEAL_TYPE_BUY ? SIDE_BUY : (dtype==DEAL_TYPE_SELL ? SIDE_SELL : SIDE_NONE));
      if(PositionSelectByTicket(trans.position))
      {
         EnsurePositionState(trans.position);
         if(PositionSelectByTicket(trans.position))
         {
            broker_sl=PositionGetDouble(POSITION_SL);
            if(broker_sl<=0.0)
            {
               entry_filled_without_sl=true;
               entry_protection_audit_fail=true;
               entry_audit_reason="ENTRY_FILLED_WITHOUT_SERVER_SL";
               SetExitAuthority(identifier,6);
               GVSet(identifier,"SLMODE",0.0);
            }

            double filled_score=GVGet(identifier,"ESCORE",0.0);
            double filled_threshold=GVGet(identifier,"ETHR",0.0);
            int filled_mode=(int)GVGet(identifier,"MODE",-1.0);
            double filled_peak=GVGet(identifier,"PEAK",0.0);
            double filled_floor=GVGet(identifier,"EFLOOR",0.0);
            bool filled_floor_armed=(GVGet(identifier,"PFA",0.0)>=0.5);
            string state_reason="";
            if(!PositionStateInvariant(identifier,side,filled_mode,filled_score,filled_threshold,
                                       filled_peak,filled_floor,filled_floor_armed,state_reason))
            {
               entry_state_invariant_fail=true;
               entry_audit_reason=state_reason;
               SetExitAuthority(identifier,5);
            }

            if(!entry_filled_without_sl)
            {
               double audited_sl=0.0, projected_net=0.0;
               string protect_reason="";
               if(!InstalledSLMeetsAuthority(trans.position,identifier,side,HardLossServerTargetNet(),
                                             audited_sl,projected_net,protect_reason))
               {
                  entry_protection_audit_fail=true;
                  entry_audit_reason=protect_reason;
                  SetExitAuthority(identifier,6);
               }
               broker_sl=audited_sl;
            }
         }
      }
   }
   else
   {
      double stored_side=GVGet(identifier,"SIDE",0.0);
      if(stored_side>0.0) side=SIDE_BUY;
      else if(stored_side<0.0) side=SIDE_SELL;
      else side=(dtype==DEAL_TYPE_BUY ? SIDE_SELL : (dtype==DEAL_TYPE_SELL ? SIDE_BUY : SIDE_NONE));
   }

   ulong seq=(ulong)GVGet(identifier,"SEQ",0.0);
   ulong edge_id_gv=(ulong)GVGet(identifier,"EID",0.0);
   double peak=GVGet(identifier,"PEAK",0.0);
   int dc=(int)GVGet(identifier,"DC",0.0);
   double eatr=GVGet(identifier,"EATR",0.0);
   double egap=GVGet(identifier,"EGAP",g_snapshot.gap_atr);
   double step_floor=GVGet(identifier,"EFLOOR",0.0);

   double entry_score=GVGet(identifier,"ESCORE",0.0);
   double entry_threshold=GVGet(identifier,"ETHR",0.0);
   double entry_score_gap=GVGet(identifier,"SGAP",0.0);
   double entry_score_shape=GVGet(identifier,"SSHAPE",0.0);
   double entry_score_context=GVGet(identifier,"SCONT",0.0);
   double entry_score_extra=GVGet(identifier,"SEXTRA",0.0);

   int exit_mode=(int)GVGet(identifier,"MODE",1.0);
   int exit_code=(int)GVGet(identifier,"EXIT",0.0);
   int sl_mode=(int)GVGet(identifier,"SLMODE",0.0);
   bool was_recovery=(GVGet(identifier,"RECOV",0.0)>0.5);

   double health=0.0;
   if(side!=SIDE_NONE)
   {
      double signed_live_health=(side==SIDE_BUY ? g_snapshot.live_gap_atr : -g_snapshot.live_gap_atr);
      double health_peak_live=GVGet(identifier,"HPK",MathMax(0.000001,signed_live_health));
      if(signed_live_health>health_peak_live)
      {
         health_peak_live=signed_live_health;
         GVSet(identifier,"HPK",health_peak_live);
      }
      health=PositionEdgeHealthV2(side,g_snapshot,health_peak_live);
   }

   double diag_entry_raw=GVGet(identifier,"DENT",
      side==SIDE_NONE ? 0.0 : PositionRawEdgeScore(side,g_snapshot.live_gap_atr));
   double diag_current_raw=(side==SIDE_NONE ? 0.0 : PositionRawEdgeScore(side,g_snapshot.live_gap_atr));
   double diag_peak_raw=GVGet(identifier,"DPK",diag_entry_raw);
   if(diag_current_raw>diag_peak_raw) diag_peak_raw=diag_current_raw;
   double diag_relative_pct=(MathAbs(diag_peak_raw)>0.000001 ? 100.0*(diag_current_raw/diag_peak_raw) : 0.0);
   double diag_drop_pct=100.0-diag_relative_pct;

   double deal_net=HistoryDealGetDouble(trans.deal,DEAL_PROFIT)+
                   HistoryDealGetDouble(trans.deal,DEAL_COMMISSION)+
                   HistoryDealGetDouble(trans.deal,DEAL_SWAP)+
                   HistoryDealGetDouble(trans.deal,DEAL_FEE);

   // Broker-truth CLOSED net includes costs already paid on entry.
   if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_OUT_BY)
      deal_net+=GVGet(identifier,"ECOST",0.0);

   MarketSnapshot s=g_snapshot;
   s.time=(datetime)HistoryDealGetInteger(trans.deal,DEAL_TIME);

   EdgeContext e; ZeroMemory(e);
   e.engine=ENG_BASSO; e.side=side; e.sequence_id=seq; e.edge_id=edge_id_gv; e.health=health; e.recovery=was_recovery;
   e.score=entry_score; e.score_threshold=entry_threshold;
   e.score_gap=entry_score_gap; e.score_shape=entry_score_shape;
   e.score_context=entry_score_context; e.score_extra=entry_score_extra;

   RiskContext rr; ZeroMemory(rr); rr.equity=AccountInfoDouble(ACCOUNT_EQUITY);

   string stage=(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_OUT_BY ? "CLOSED" : "DEAL");
   string status=(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_OUT_BY ? "DEAL_OUT" : "DEAL_IN");
   string broker_reason="BROKER_TRUTH";
   if(entry_state_invariant_fail)
      broker_reason="BROKER_TRUTH;ENTRY_STATE_INVARIANT_FAILSAFE_LATCHED;"+entry_audit_reason;
   else if(entry_protection_audit_fail)
      broker_reason="BROKER_TRUTH;ENTRY_PROTECTION_FAILSAFE_LATCHED;"+entry_audit_reason;
   else if(entry_filled_without_sl)
      broker_reason="BROKER_TRUTH;ENTRY_FILLED_WITHOUT_SL_FAILSAFE_LATCHED";

   int effective_exit_code=exit_code;
   if((entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_OUT_BY) &&
      deal_reason==DEAL_REASON_SL && sl_mode>0)
      effective_exit_code=sl_mode; // broker truth overrides any older latched market-close reason

   if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_OUT_BY)
   {
      if(effective_exit_code==3)
      {
         double cap_deviation=MathMax(0.0,(-deal_net)-InpBassoHardMaxLossEUR);
         double recovery_score=GVGet(identifier,"RSC",0.0);
         broker_reason=StringFormat("BROKER_TRUTH;HARD_LOSS_EXECUTED;DEAL_REASON=%d;CAP_DEVIATION=%.2f;RECOVERY_SCORE=%.2f;WAS_RECOVERY=%d",
                                    (int)deal_reason,cap_deviation,recovery_score,was_recovery?1:0);
      }
      else if(effective_exit_code==2)
      {
         double floor_deviation=deal_net-step_floor;
         double giveback=MathMax(0.0,peak-deal_net);
         double retention=(peak>0.0 ? 100.0*deal_net/peak : 0.0);
         broker_reason=StringFormat("BROKER_TRUTH;PROFIT_VAULT_EXECUTED;DEAL_REASON=%d;FLOOR=%.2f;FLOOR_DEVIATION=%.2f;FINAL_GIVEBACK=%.2f;FINAL_RETENTION_PCT=%.2f",
                                    (int)deal_reason,step_floor,floor_deviation,giveback,retention);
      }
      else if(effective_exit_code==5)
         broker_reason=StringFormat("BROKER_TRUTH;RULE_INVARIANT_FAILSAFE_EXECUTED;DEAL_REASON=%d",(int)deal_reason);
      else if(effective_exit_code==6)
         broker_reason=StringFormat("BROKER_TRUTH;PROTECTION_AUTHORITY_FAILSAFE_EXECUTED;DEAL_REASON=%d",(int)deal_reason);
      else if(effective_exit_code==1)
      {
         long deal_msc=(long)HistoryDealGetInteger(trans.deal,DEAL_TIME_MSC);
         double death_msc=GVGet(identifier,"DMSC",0.0);
         double request_msc=GVGet(identifier,"CRMSC",0.0);
         long death_to_request_ms=(death_msc>0.0 && request_msc>0.0 ? (long)MathMax(0.0,request_msc-death_msc) : -1);
         long request_to_deal_ms=(request_msc>0.0 ? (long)MathMax(0.0,(double)deal_msc-request_msc) : -1);
         long death_to_deal_ms=(death_msc>0.0 ? (long)MathMax(0.0,(double)deal_msc-death_msc) : -1);
         double giveback=MathMax(0.0,peak-deal_net);
         double retention=(peak>0.0 ? 100.0*deal_net/peak : 0.0);
         broker_reason=StringFormat("BROKER_TRUTH;V55_EDGE_DYING_EXECUTED;DEAL_REASON=%d;DEATH_TO_REQUEST_MS=%I64d;REQUEST_TO_DEAL_MS=%I64d;DEATH_TO_DEAL_MS=%I64d;FINAL_GIVEBACK=%.2f;FINAL_RETENTION_PCT=%.2f",
                                    (int)deal_reason,death_to_request_ms,request_to_deal_ms,death_to_deal_ms,giveback,retention);
      }
      else
         broker_reason=StringFormat("BROKER_TRUTH;UNCLASSIFIED_EXIT;DEAL_REASON=%d;SLMODE=%d;EXIT=%d",
                                    (int)deal_reason,sl_mode,exit_code);
   }

   if(entry==DEAL_ENTRY_IN || entry==DEAL_ENTRY_INOUT)
   {
      long deal_msc=(long)HistoryDealGetInteger(trans.deal,DEAL_TIME_MSC);
      DiagRegisterDealIn(identifier,e,s.time,deal_msc,trans.price,trans.volume,deal_net);
   }

   LogEvent(s,e,stage,status,broker_reason,rr,broker_sl,result.retcode,result.comment,
            trans.order,trans.deal,trans.position,identifier,trans.volume,trans.price,
            deal_net,peak,step_floor,health,dc,eatr,
            diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);

   if(entry==DEAL_ENTRY_OUT || entry==DEAL_ENTRY_OUT_BY)
   {
      // Do not finalize state on a partial exit. The same position must retain the same
      // MODE/score/floor/EXIT authority until broker truth says it is fully gone.
      bool still_open=ManagedPositionExistsByIdentifier(identifier);
      if(still_open)
      {
         g_close_inflight=true;
      }
      else
      {
         long exit_msc=(long)HistoryDealGetInteger(trans.deal,DEAL_TIME_MSC);
         double gross_price_profit=HistoryDealGetDouble(trans.deal,DEAL_PROFIT);
         double exit_commission=HistoryDealGetDouble(trans.deal,DEAL_COMMISSION);
         double exit_swap=HistoryDealGetDouble(trans.deal,DEAL_SWAP);
         double exit_fee=HistoryDealGetDouble(trans.deal,DEAL_FEE);
         double entry_costs=GVGet(identifier,"ECOST",0.0);
         DiagFinalizeTrade(identifier,e,s.time,exit_msc,trans.price,deal_net,peak,effective_exit_code,exit_mode,
                           gross_price_profit,exit_commission,exit_swap,exit_fee,entry_costs);

         // V49: every REAL fully closed trade is followed diagnostically until the same
         // structural EdgeDying boundary, regardless of exit source. This has zero exit authority.
         AddPostCloseTrack(seq,side,trans.price,egap,deal_net,peak,step_floor,
                           entry_score,entry_threshold,
                           entry_score_gap,entry_score_shape,entry_score_context,entry_score_extra,GVGet(identifier,"RSC",0.0),
                           effective_exit_code,exit_mode);

         // V50 hard-loss recovery stays DIAGNOSTIC ONLY. V45 operational BUY recovery lost 4/4 trades,
         // therefore no hard-loss close can arm a new execution path in V50.
         if(effective_exit_code==3)
         {
            EdgeContext re=e; re.recovery=false;
            RiskContext rr2; ZeroMemory(rr2); rr2.equity=AccountInfoDouble(ACCOUNT_EQUITY);
            string why=StringFormat("SIDE=%s;WAS_RECOVERY=%d;RECOVERY_SCORE=%.2f;NO_OPERATIONAL_REENTRY",
                                    SideName(side),was_recovery?1:0,GVGet(identifier,"RSC",0.0));
            LogEvent(g_snapshot,re,"RECOVERY_REENTRY","DIAGNOSTIC_ONLY","V50_HARDLOSS_RECOVERY_DISABLED;"+why,rr2,
                     0,0,"",0,0,0,0,0,0,deal_net,peak,step_floor,health,0,eatr,
                     diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);
         }

         // V59: vault close banks profit — no auto reentry AND reset edge so a NEW SELL
         // candidate (+ signature gate) is required. Do not keep a live edge that only
         // blocked fresh entries until DYING (that stranded the book after vault).
         if(effective_exit_code==2)
         {
            g_cont_reentry_armed=false;
            g_cont_reentry_side=SIDE_NONE;
            g_cont_reentry_peak_live=0.0;
            g_v55.reentry_armed=false;

            EdgeContext ce=e; ce.continuation=false; ce.edge_id=g_v55.edge_id;
            ce.score=g_v55.current_score; ce.score_threshold=InpV55EntryScore;
            RiskContext cr; ZeroMemory(cr); cr.equity=AccountInfoDouble(ACCOUNT_EQUITY);
            LogEvent(g_snapshot,ce,"V55_REENTRY_CANCEL","NO_ARM",
                     StringFormat("V59_VAULT_BANKED;NO_AUTO_REENTRY;EDGE_RESET;NET=%.2f;NEED_NEW_SELL+GATE",deal_net),cr,
                     0,0,"",0,0,0,0,0,0,deal_net,peak,step_floor,health,0,eatr,
                     diag_entry_raw,diag_current_raw,diag_peak_raw,diag_relative_pct,diag_drop_pct);
            V55ResetEdge("V59_VAULT_CLOSE_RESET_NO_REENTRY");
         }
         else if(effective_exit_code==3 || effective_exit_code==1 || deal_net<=0.0 ||
                 effective_exit_code==5 || effective_exit_code==6)
         {
            // Hard loss / V55 dying / negative / failsafe: kill edge, no reentry.
            string kill_why=StringFormat("EXIT_CODE=%d;NET=%.2f",effective_exit_code,deal_net);
            if(effective_exit_code==1) kill_why="V55_EDGE_DYING_CLOSE";
            if(effective_exit_code==3) kill_why="HARD_LOSS_CLOSE";
            if(deal_net<=0.0 && effective_exit_code!=3 && effective_exit_code!=1) kill_why="NEGATIVE_OR_UNRECOGNIZED_CLOSE";
            V55ResetEdge(kill_why);
         }

         g_close_inflight=false;
         g_buy_rearmed=true;
         g_sell_rearmed=true;
         GVClear(identifier);
      }
   }
}
//+------------------------------------------------------------------+
