"""Validate integrated dynamic beta ACB v6 + full OB stack. Full engine stack (all layers active): 1. Signal: vel_div <= -0.02 primary threshold 2. Vol gate: > p60 3. IRP ARS asset selector: min_irp_alignment=0.45 4. OB Sub-1: depth-quality hard-skip (asset picker lift) 5. OB Sub-2: per-asset imbalance (SmartPlacer / MM rates, better fills) 6. OB Sub-3: cross-asset consensus multiplier (overall market health indicator) 7. OB Sub-4: withdrawal cascade beta (4th dimension, ACB integration) 8. ACBv6 dynamic beta: INVERSE_MODE, log-boost, 3-scale meta-boost (`if beta>0:` gate) 9. Cubic-convex dynamic leverage: max 5x, convexity 3.0 10. ExF: 4 NPZ factors (funding/dvol/fng/taker) via ACB._load_external_factors() 11. EsoF: tail clipper, hazard=0.0 (neutral — N=6 tail events, insufficient for non-zero) 12. MC-Forewarner: SVM envelope + XGB watchdog, per-date RED/ORANGE gate Note: envelope is broad (SVM trained on 1.5-12x range, N=6 tail events). 0 interventions on current 55-day dataset — all configs within envelope. Wired correctly; will activate when re-trained on multi-year dataset. 10. TP/max-hold exits: 99bps TP, 120-bar max hold (OB-aware accordion) OB calibration (real-observed 2025-01-15 data via MockOBProvider): BTC: -0.086 imbalance (sell pressure, confirms SHORT) ETH: -0.092 imbalance (sell pressure, confirms SHORT) BNB: +0.05 imbalance (mild buy pressure, mild contradict) SOL: +0.05 imbalance (mild buy pressure, mild contradict) Full-stack benchmark (55-day dataset, 2025-12-31 to 2026-02-25, abs_max_leverage=6.0): ROI ~+44.89%, PF ~1.123, DD ~14.95%, Sharpe ~2.50, Trades ~2128 DD < champion freeze (21.41%). Sharpe improved vs uncapped (1.89→2.50). Pre-cap benchmark (abs_max_leverage=∞, historical reference only): ROI ~+104.85%, PF ~1.120, DD ~32.35%, Sharpe ~1.89 ACBv6-only baseline (no OB, no cap): ROI ~+61.86%, PF ~1.125, DD ~29.57%, Sharpe ~1.81 Leverage cap doctrine: ACB/EsoF/MC steer from 5x toward 6x ceiling; 6x is never breached. process_day() encapsulates all per-date/per-bar orchestration — test is data-loading only. Note on benchmark history: - ACBv6-only baseline 61.86% is data-driven (Feb 5-8, 2026 cluster adds 4 adverse high-leverage days against SHORT). Code is correct; update after dataset expansion. - Two real bugs were fixed (see git log): 1. evaluate() TypeError: regime_size_mult kwarg — now accepted as no-op param. 2. ACBv6 leverage clamp was dead code; reverted to uncapped (champion behavior). """ import sys, time, math, json, csv sys.stdout.reconfigure(encoding='utf-8', errors='replace') from pathlib import Path from datetime import datetime from collections import defaultdict import numpy as np import pandas as pd sys.path.insert(0, str(Path(__file__).parent)) print("Compiling numba kernels...") t0c = time.time() from nautilus_dolphin.nautilus.alpha_asset_selector import compute_irp_nb, compute_ars_nb, rank_assets_irp_nb from nautilus_dolphin.nautilus.alpha_bet_sizer import compute_sizing_nb from nautilus_dolphin.nautilus.alpha_signal_generator import check_dc_nb from nautilus_dolphin.nautilus.ob_features import ( OBFeatureEngine, compute_imbalance_nb, compute_depth_1pct_nb, compute_depth_quality_nb, compute_fill_probability_nb, compute_spread_proxy_nb, compute_depth_asymmetry_nb, compute_imbalance_persistence_nb, compute_withdrawal_velocity_nb, compute_market_agreement_nb, compute_cascade_signal_nb, ) from nautilus_dolphin.nautilus.ob_provider import MockOBProvider _p = np.array([1.0, 2.0, 3.0], dtype=np.float64) compute_irp_nb(_p, -1); compute_ars_nb(1.0, 0.5, 0.01) rank_assets_irp_nb(np.ones((10, 2), dtype=np.float64), 8, -1, 5, 500.0, 20, 0.20) compute_sizing_nb(-0.03, -0.02, -0.05, 3.0, 0.5, 5.0, 0.20, True, True, 0.0, np.zeros(4, dtype=np.int64), np.zeros(4, dtype=np.int64), np.zeros(5, dtype=np.float64), 0, -1, 0.01, 0.04) check_dc_nb(_p, 3, 1, 0.75) # OB kernel warmup _b = np.array([100.0, 200.0, 300.0, 400.0, 500.0], dtype=np.float64) _a = np.array([110.0, 190.0, 310.0, 390.0, 510.0], dtype=np.float64) compute_imbalance_nb(_b, _a); compute_depth_1pct_nb(_b, _a) compute_depth_quality_nb(210.0, 200.0); compute_fill_probability_nb(1.0) compute_spread_proxy_nb(_b, _a); compute_depth_asymmetry_nb(_b, _a) compute_imbalance_persistence_nb(np.array([0.1, -0.1], dtype=np.float64), 2) compute_withdrawal_velocity_nb(np.array([100.0, 110.0], dtype=np.float64), 1) compute_market_agreement_nb(np.array([0.1, -0.05], dtype=np.float64), 2) compute_cascade_signal_nb(np.array([-0.05, -0.15], dtype=np.float64), 2, -0.10) print(f" JIT: {time.time() - t0c:.1f}s") # EsoF orchestrator: has set_esoteric_hazard_multiplier + updated set_mc_forewarner_status from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker from mc.mc_ml import DolphinForewarner VBT_DIR = Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\vbt_cache") META_COLS = {'timestamp', 'scan_number', 'v50_lambda_max_velocity', 'v150_lambda_max_velocity', 'v300_lambda_max_velocity', 'v750_lambda_max_velocity', 'vel_div', 'instability_50', 'instability_150'} ENGINE_KWARGS = dict( initial_capital=25000.0, vel_div_threshold=-0.02, vel_div_extreme=-0.05, min_leverage=0.5, max_leverage=5.0, leverage_convexity=3.0, fraction=0.20, fixed_tp_pct=0.0095, stop_pct=1.0, max_hold_bars=120, use_direction_confirm=True, dc_lookback_bars=7, dc_min_magnitude_bps=0.75, dc_skip_contradicts=True, dc_leverage_boost=1.0, dc_leverage_reduce=0.5, use_asset_selection=True, min_irp_alignment=0.45, use_sp_fees=True, use_sp_slippage=True, sp_maker_entry_rate=0.62, sp_maker_exit_rate=0.50, use_ob_edge=True, ob_edge_bps=5.0, ob_confirm_rate=0.40, lookback=100, use_alpha_layers=True, use_dynamic_leverage=True, seed=42, ) # --- MC-Forewarner: champion config dict (frozen, max_leverage overridden per-date inside engine) --- MC_MODELS_DIR = str(Path(r"C:\Users\Lenovo\Documents\- DOLPHIN NG HD HCM TSF Predict\nautilus_dolphin\mc_results\models")) MC_BASE_CFG = { 'trial_id': 0, 'vel_div_threshold': -0.020, 'vel_div_extreme': -0.050, 'use_direction_confirm': True, 'dc_lookback_bars': 7, 'dc_min_magnitude_bps': 0.75, 'dc_skip_contradicts': True, 'dc_leverage_boost': 1.00, 'dc_leverage_reduce': 0.50, 'vd_trend_lookback': 10, 'min_leverage': 0.50, 'max_leverage': 5.00, 'leverage_convexity': 3.00, 'fraction': 0.20, 'use_alpha_layers': True, 'use_dynamic_leverage': True, 'fixed_tp_pct': 0.0095, 'stop_pct': 1.00, 'max_hold_bars': 120, 'use_sp_fees': True, 'use_sp_slippage': True, 'sp_maker_entry_rate': 0.62, 'sp_maker_exit_rate': 0.50, 'use_ob_edge': True, 'ob_edge_bps': 5.00, 'ob_confirm_rate': 0.40, 'ob_imbalance_bias': -0.09, 'ob_depth_scale': 1.00, 'use_asset_selection': True, 'min_irp_alignment': 0.45, 'lookback': 100, 'acb_beta_high': 0.80, 'acb_beta_low': 0.20, 'acb_w750_threshold_pct': 60, } print("\nLoading MC-Forewarner trained models...") forewarner = DolphinForewarner(models_dir=MC_MODELS_DIR) print(" MC-Forewarner ready (One-Class SVM envelope + XGBoost champion/catas classifiers)") parquet_files = sorted(VBT_DIR.glob("*.parquet")) parquet_files = [p for p in parquet_files if 'catalog' not in str(p)] # --- Initialize ACB v6 with dynamic beta --- print("\nInitializing ACB v6...") acb = AdaptiveCircuitBreaker() date_strings = [pf.stem for pf in parquet_files] acb.preload_w750(date_strings) print(f" w750 threshold (p60): {acb._w750_threshold:.6f}") print(f" Dates with w750 data: {sum(1 for v in acb._w750_vel_cache.values() if v != 0.0)}/{len(date_strings)}") # Show per-date beta assignments print("\n Per-date dynamic beta:") for ds in date_strings: info = acb.get_dynamic_boost_for_date(ds) w = acb._w750_vel_cache.get(ds, 0.0) print(f" {ds}: w750={w:+.6f} beta={info['beta']:.1f} boost={info['boost']:.2f}x signals={info['signals']:.1f}") # Pre-load data all_vols = [] for pf in parquet_files[:2]: df = pd.read_parquet(pf) if 'BTCUSDT' not in df.columns: continue pr = df['BTCUSDT'].values for i in range(60, len(pr)): seg = pr[max(0,i-50):i] if len(seg)<10: continue v = float(np.std(np.diff(seg)/seg[:-1])) if v > 0: all_vols.append(v) vol_p60 = float(np.percentile(all_vols, 60)) pq_data = {} all_assets = set() for pf in parquet_files: df = pd.read_parquet(pf) ac = [c for c in df.columns if c not in META_COLS] all_assets.update(ac) bp = df['BTCUSDT'].values if 'BTCUSDT' in df.columns else None dv = np.full(len(df), np.nan) if bp is not None: for i in range(50, len(bp)): seg = bp[max(0,i-50):i] if len(seg)<10: continue dv[i] = float(np.std(np.diff(seg)/seg[:-1])) pq_data[pf.stem] = (df, ac, dv) # --- Build OB engine (4 dimensions, real-calibrated MockOBProvider) --- # Calibration from real Binance OB data (2025-01-15 observation): # BTC: -0.086 (sell pressure, confirms SHORT) # ETH: -0.092 (sell pressure, confirms SHORT) # BNB: +0.05 (mild buy pressure, mild contradict) # SOL: +0.05 (mild buy pressure, mild contradict) OB_ASSETS = sorted(list(all_assets)) _mock_ob = MockOBProvider( imbalance_bias=-0.09, depth_scale=1.0, assets=OB_ASSETS, imbalance_biases={"BTCUSDT": -0.086, "ETHUSDT": -0.092, "BNBUSDT": +0.05, "SOLUSDT": +0.05}, ) ob_eng = OBFeatureEngine(_mock_ob) ob_eng.preload_date("mock", OB_ASSETS) # --- Assemble full stack --- print(f"\n=== Running full stack: ACBv6 + OB 4D + MC-Forewarner + EsoF(neutral) + ExF ===") t0 = time.time() engine = NDAlphaEngine(**ENGINE_KWARGS) engine.set_ob_engine(ob_eng) # OB Sub-1/2/3/4 engine.set_acb(acb) # ACBv6 dynamic beta + 3-scale meta-boost (internal) engine.set_mc_forewarner(forewarner, MC_BASE_CFG) # per-date envelope gate (internal) engine.set_esoteric_hazard_multiplier(0.0) # EsoF neutral (hazard=0 → 6x ceiling intact) engine._bar_log_enabled = True # Enable per-bar state capture for E2E parity tests # MC-Forewarner historical status: AMBER (catastrophic_prob=3.1% during 55-day period) # AMBER → bet_sizer.max_leverage stays at 5.0x (not GREEN → no 6x unlock) # abs_max_leverage=6.0 is the ceiling; ACB intelligence steers 5x→6x within it. # --- Main loop: one call per day, engine handles everything internally --- dstats = [] bar_records = [] for pf in parquet_files: ds = pf.stem df, acols, dvol = pq_data[ds] vol_ok = np.where(np.isfinite(dvol), dvol > vol_p60, False) stats = engine.process_day(ds, df, acols, vol_regime_ok=vol_ok) dstats.append({**stats, 'cap': engine.capital}) bar_records.extend({'date': ds, **b} for b in engine._bar_log) elapsed = time.time() - t0 # Results tr = engine.trade_history w = [t for t in tr if t.pnl_absolute > 0]; l = [t for t in tr if t.pnl_absolute <= 0] gw = sum(t.pnl_absolute for t in w) if w else 0 gl = abs(sum(t.pnl_absolute for t in l)) if l else 0 roi = (engine.capital - 25000) / 25000 * 100 pf = gw / gl if gl > 0 else 999 dr = [s['pnl']/25000*100 for s in dstats] sharpe = np.mean(dr) / np.std(dr) * np.sqrt(365) if np.std(dr) > 0 else 0 peak_cap = 25000.0; max_dd = 0.0 for s in dstats: peak_cap = max(peak_cap, s['cap']) dd = (peak_cap - s['cap']) / peak_cap * 100 max_dd = max(max_dd, dd) mid = len(parquet_files) // 2 h1 = sum(s['pnl'] for s in dstats[:mid]) h2 = sum(s['pnl'] for s in dstats[mid:]) w_count = len(w); l_count = len(l) wr = w_count / len(tr) * 100 if tr else 0.0 avg_win = float(np.mean([t.pnl_pct for t in w]) * 100) if w else 0.0 avg_loss = float(np.mean([t.pnl_pct for t in l]) * 100) if l else 0.0 # MC-Forewarner intervention summary red_days = [s for s in dstats if s['mc_status'] == 'RED'] orng_days = [s for s in dstats if s['mc_status'] == 'ORANGE'] print(f"\n MC-Forewarner interventions ({len(red_days)} RED, {len(orng_days)} ORANGE):") for s in dstats: if s['mc_status'] != 'OK': print(f" {s['date']}: {s['mc_status']:6s} boost={s['boost']:.2f}x P&L={s['pnl']:+.0f}") print(f"\n{'='*65}") print(f" Full Stack: ACBv6 + OB 4D + MC-Forewarner + EsoF(neutral) + ExF") print(f"{'='*65}") print(f" ROI: {roi:+.2f}%") print(f" PF: {pf:.3f}") print(f" DD: {max_dd:.2f}%") print(f" Sharpe: {sharpe:.2f}") print(f" WR: {wr:.1f}% (W={w_count} L={l_count})") print(f" AvgWin: {avg_win:+.3f}% AvgLoss: {avg_loss:+.3f}%") print(f" Trades: {len(tr)}") print(f" Capital: ${engine.capital:,.2f}") print(f" H1 P&L: ${h1:+,.2f}") print(f" H2 P&L: ${h2:+,.2f}") h2h1 = h2/h1 if h1 != 0 else float('nan') print(f" H2/H1: {h2h1:.2f}") print(f" Time: {elapsed:.0f}s") print(f"{'='*65}") print(f" ACBv6-only baseline (no OB): ROI=+61.86%, PF=1.125, DD=29.57%") print(f" Champion git freeze target: ROI=+85.72%, PF=1.178, DD=21.41%") print(f" Full-stack confirmed: ROI=+104.85%, PF=1.120, DD=32.35%, H2/H1=0.98") print(f"{'='*65}") # ═══════════════════════════════════════════════════════════════ # ROBUSTNESS & OVERFIT ANALYSIS # ═══════════════════════════════════════════════════════════════ # Import scipy here (after all parquet/data loading) to avoid heap fragmentation from scipy.stats import chi2, skew as sp_skew, kurtosis as sp_kurt dr_all = np.array([s['pnl'] / 25000.0 * 100 for s in dstats]) n_d = len(dstats) actual_sh = float(np.mean(dr_all) / np.std(dr_all) * np.sqrt(365)) if np.std(dr_all) > 0 else 0.0 def _sub_metrics(sub, label, cap_start): """Print metrics for a dstats sub-list; normalise daily returns by cap_start.""" if not sub: return {} pnls_s = [s['pnl'] for s in sub] caps_s = [s['cap'] for s in sub] dr_s = [p / cap_start * 100 for p in pnls_s] roi_s = (caps_s[-1] - cap_start) / cap_start * 100 sh_s = float(np.mean(dr_s) / np.std(dr_s) * np.sqrt(365)) if np.std(dr_s) > 0 else 0.0 pk = cap_start; mdd_s = 0.0 for c in caps_s: pk = max(pk, c); mdd_s = max(mdd_s, (pk - c) / pk * 100) n_t = sum(s['trades'] for s in sub) print(f" {label:<38} ROI={roi_s:+6.1f}% DD={mdd_s:4.1f}% Sh={sh_s:+5.2f} T={n_t}") return {'roi': roi_s, 'dd': mdd_s, 'sharpe': sh_s, 'trades': n_t} print(f"\n{'═'*65}") print(f" ROBUSTNESS & OVERFIT ANALYSIS") print(f"{'═'*65}") # ─── A. Sub-period breakdown ─────────────────────────────────── print(f"\n A. SUB-PERIOD (Q1/Q2/Q3/Q4 + H1/H2 + monthly)") q = n_d // 4 for qi, (qa, qb) in enumerate([(0,q),(q,2*q),(2*q,3*q),(3*q,n_d)]): sl = dstats[qa:qb] if not sl: continue ic_q = dstats[qa-1]['cap'] if qa > 0 else 25000.0 _sub_metrics(sl, f" Q{qi+1} ({sl[0]['date']} – {sl[-1]['date']})", ic_q) print() mid_d = n_d // 2 _sub_metrics(dstats[:mid_d], f" H1 ({dstats[0]['date']} – {dstats[mid_d-1]['date']})", 25000.0) _sub_metrics(dstats[mid_d:], f" H2 ({dstats[mid_d]['date']} – {dstats[-1]['date']})", dstats[mid_d-1]['cap']) print() date_to_idx = {s['date']: i for i, s in enumerate(dstats)} monthly_buckets = defaultdict(list) for s in dstats: monthly_buckets[s['date'][:7]].append(s) for mo in sorted(monthly_buckets): sl_m = monthly_buckets[mo] fi_m = date_to_idx[sl_m[0]['date']] ic_m = dstats[fi_m-1]['cap'] if fi_m > 0 else 25000.0 _sub_metrics(sl_m, f" {mo} ({sl_m[0]['date']} – {sl_m[-1]['date']})", ic_m) # ─── B. Walk-Forward 3-Fold Validation ──────────────────────── print(f"\n B. WALK-FORWARD 3-FOLD (fresh engine per fold, ~{n_d//3} days/fold)") fold_n = n_d // 3 fold_def = [(0, fold_n, 'Fold-1'), (fold_n, 2*fold_n, 'Fold-2'), (2*fold_n, n_d, 'Fold-3')] wf_sharpes = [] for fa, fb, fn in fold_def: fold_files = parquet_files[fa:fb] ef = NDAlphaEngine(**ENGINE_KWARGS) ef.set_ob_engine(ob_eng) ef.set_acb(acb) ef.set_mc_forewarner(forewarner, MC_BASE_CFG) ef.set_esoteric_hazard_multiplier(0.0) fd_stats = [] for fold_pf in fold_files: df_f, ac_f, dv_f = pq_data[fold_pf.stem] vok_f = np.where(np.isfinite(dv_f), dv_f > vol_p60, False) st_f = ef.process_day(fold_pf.stem, df_f, ac_f, vol_regime_ok=vok_f) fd_stats.append({**st_f, 'cap': ef.capital}) tr_f = ef.trade_history w_f = [t for t in tr_f if t.pnl_absolute > 0] l_f = [t for t in tr_f if t.pnl_absolute <= 0] gw_f = sum(t.pnl_absolute for t in w_f) if w_f else 0.0 gl_f = abs(sum(t.pnl_absolute for t in l_f)) if l_f else 0.0 pff = gw_f / gl_f if gl_f > 0 else 999.0 dr_f = np.array([s['pnl'] / 25000.0 * 100 for s in fd_stats]) sh_f = float(np.mean(dr_f) / np.std(dr_f) * np.sqrt(365)) if np.std(dr_f) > 0 else 0.0 pk_f = 25000.0; mdd_f = 0.0 for s in fd_stats: pk_f = max(pk_f, s['cap']); mdd_f = max(mdd_f, (pk_f - s['cap']) / pk_f * 100) wr_f = len(w_f) / len(tr_f) * 100 if tr_f else 0.0 roi_f = (ef.capital - 25000.0) / 25000.0 * 100 d0, d1 = fold_files[0].stem, fold_files[-1].stem wf_sharpes.append(sh_f) print(f" {fn} ({d0}–{d1}): ROI={roi_f:+6.1f}% PF={pff:.3f} DD={mdd_f:4.1f}% Sh={sh_f:+.2f} WR={wr_f:.1f}% T={len(tr_f)}") wf_consistent = all(s > 0 for s in wf_sharpes) print(f" → All folds Sh>0: {'YES — temporal consistency confirmed' if wf_consistent else 'NO — temporal inconsistency detected'}") # ─── C. Bootstrap 95% CIs ───────────────────────────────────── print(f"\n C. BOOTSTRAP 95% CIs (2000 resamples, daily P&L + trade-level PF)") rng_b = np.random.default_rng(42) boot_sh = []; boot_pf_b = [] pnls_abs = np.array([t.pnl_absolute for t in tr]) for _ in range(2000): idx = rng_b.integers(0, n_d, size=n_d) s_b = dr_all[idx] boot_sh.append(float(np.mean(s_b) / np.std(s_b) * np.sqrt(365)) if np.std(s_b) > 0 else 0.0) idx_t = rng_b.integers(0, len(pnls_abs), size=len(pnls_abs)) p_b = pnls_abs[idx_t] gw_b = p_b[p_b > 0].sum(); gl_b = abs(p_b[p_b <= 0].sum()) boot_pf_b.append(gw_b / gl_b if gl_b > 0 else 999.0) boot_sh = np.array(boot_sh) boot_pf_b = np.array(boot_pf_b) print(f" Sharpe actual={actual_sh:+.3f} 95% CI: [{np.percentile(boot_sh,2.5):+.2f}, {np.percentile(boot_sh,97.5):+.2f}]") print(f" PF actual={pf:.3f} 95% CI: [{np.percentile(boot_pf_b,2.5):.3f}, {np.percentile(boot_pf_b,97.5):.3f}]") print(f" Sharpe > 0: {100*np.mean(boot_sh>0):.1f}% of resamples positive") print(f" PF > 1.0: {100*np.mean(boot_pf_b>1.0):.1f}% of resamples > 1.0") # ─── D. Permutation Significance Test ───────────────────────── # H₀: daily returns have zero mean (no edge). Sharpe is order-invariant so we # permute CENTERED returns — each shuffle has mean≈0, std unchanged → Sharpe≈0. # p-value = fraction of null Sharpes >= actual. This is the correct one-sided test. print(f"\n D. PERMUTATION TEST (1000 shuffles, H0: mean=0 via centered returns)") dr_centered = dr_all - np.mean(dr_all) def _perm_sharpe(rng, arr): s = rng.permutation(arr) sd = np.std(s) return float(np.mean(s) / sd * np.sqrt(365)) if sd > 0 else 0.0 perm_sh = np.array([_perm_sharpe(rng_b, dr_centered) for _ in range(1000)]) pval = float(np.mean(perm_sh >= actual_sh)) t_stat = actual_sh / np.sqrt(365) * np.sqrt(n_d) print(f" Actual Sharpe: {actual_sh:.3f} (t-stat ≈ {t_stat:.2f} for N={n_d} days)") print(f" Null (H0 mean=0): mean={np.mean(perm_sh):.3f} std={np.std(perm_sh):.3f} p95={np.percentile(perm_sh,95):.3f}") print(f" p-value: {pval:.4f} → {'SIGNIFICANT (p<0.05)' if pval<0.05 else f'p>{0.05:.2f} — N={n_d} days too short for formal significance at 5%'}") print(f" (Ref: need t≈1.67 for 5% one-tailed; need ~{int((1.67*np.sqrt(365)/actual_sh)**2)+1} days for this Sharpe to reach significance)") # ─── E. Return Autocorrelation + Ljung-Box ──────────────────── print(f"\n E. RETURN AUTOCORRELATION (daily P&L, lags 1–7)") dm = dr_all - np.mean(dr_all) var0 = float(np.sum(dm**2)) acf_vals = [float(np.sum(dm[k:] * dm[:n_d-k]) / var0) if var0 > 0 else 0.0 for k in range(1, 8)] bart_ci = 1.96 / np.sqrt(n_d) for k, ac in enumerate(acf_vals, 1): bar_str = '▐' * max(0, int(abs(ac) * 25)) sig_str = ' ← significant' if abs(ac) > bart_ci else '' print(f" lag={k} ACF={ac:+.4f} {bar_str}{sig_str}") lb_q = float(n_d * (n_d + 2) * sum(acf_vals[k-1]**2 / (n_d - k) for k in range(1, 6))) lb_p = float(1.0 - chi2.cdf(lb_q, df=5)) print(f" Ljung-Box Q(5)={lb_q:.3f} p={lb_p:.4f} → {'serial correlation present' if lb_p<0.05 else 'no significant serial correlation (OK)'}") print(f" Lag-1 ACF={acf_vals[0]:+.4f} (pre-OB baseline was +0.082; OB target was -0.053)") # ─── F. Trade Distribution ──────────────────────────────────── print(f"\n F. TRADE DISTRIBUTION STATISTICS") pnl_pcts = np.array([t.pnl_pct * 100 for t in tr]) levs_arr = np.array([t.leverage for t in tr]) bh_arr = np.array([t.bars_held for t in tr]) exit_ctr = {} for t in tr: exit_ctr[t.exit_reason] = exit_ctr.get(t.exit_reason, 0) + 1 print(f" P&L: mean={np.mean(pnl_pcts):+.4f}% σ={np.std(pnl_pcts):.4f}% skew={float(sp_skew(pnl_pcts)):.3f} kurt={float(sp_kurt(pnl_pcts)):.3f}") print(f" Leverage: mean={np.mean(levs_arr):.2f}x max={np.max(levs_arr):.2f}x p90={np.percentile(levs_arr,90):.2f}x p95={np.percentile(levs_arr,95):.2f}x p99={np.percentile(levs_arr,99):.2f}x") print(f" BarsHeld: mean={np.mean(bh_arr):.1f} median={np.median(bh_arr):.0f} p90={np.percentile(bh_arr,90):.0f} (cap=120)") print(f" Exits: {dict(sorted(exit_ctr.items()))}") w_pnls = pnl_pcts[pnl_pcts > 0]; l_pnls = pnl_pcts[pnl_pcts <= 0] if len(w_pnls) and len(l_pnls): print(f" Tail: p95-win={np.percentile(w_pnls,95):+.3f}% p05-loss={np.percentile(l_pnls,5):+.3f}% ratio={float(np.percentile(w_pnls,95)/abs(np.percentile(l_pnls,5))):.2f}") # Win/loss streak lengths streaks_w = []; streaks_l = []; cur_w = 0; cur_l = 0; prev_is_w = None for t in tr: is_w = t.pnl_absolute > 0 if prev_is_w is None: cur_w = int(is_w); cur_l = int(not is_w) elif is_w == prev_is_w: if is_w: cur_w += 1 else: cur_l += 1 else: if prev_is_w: streaks_w.append(cur_w); cur_w = 1; cur_l = 0 else: streaks_l.append(cur_l); cur_l = 1; cur_w = 0 prev_is_w = is_w if prev_is_w: streaks_w.append(cur_w) else: streaks_l.append(cur_l) max_win_streak = max(streaks_w) if streaks_w else 0 max_loss_streak = max(streaks_l) if streaks_l else 0 print(f" Streaks: max-win={max_win_streak} max-loss={max_loss_streak} avg-win-run={np.mean(streaks_w):.1f} avg-loss-run={np.mean(streaks_l):.1f}") # ─── G. Save logs ───────────────────────────────────────────── print(f"\n G. SAVING LOGS") LOG_DIR = Path(__file__).parent / "run_logs" LOG_DIR.mkdir(exist_ok=True) run_ts = datetime.now().strftime("%Y%m%d_%H%M%S") trades_path = LOG_DIR / f"trades_{run_ts}.csv" with open(trades_path, 'w', newline='') as f: cw = csv.writer(f) cw.writerow(['trade_id','asset','direction','entry_price','exit_price', 'entry_bar','exit_bar','bars_held','leverage','notional', 'pnl_pct','pnl_absolute','exit_reason','bucket_idx']) for t in tr: cw.writerow([t.trade_id, t.asset, t.direction, f"{t.entry_price:.6f}", f"{t.exit_price:.6f}", t.entry_bar, t.exit_bar, t.bars_held, f"{t.leverage:.4f}", f"{t.notional:.4f}", f"{t.pnl_pct:.8f}", f"{t.pnl_absolute:.4f}", t.exit_reason, t.bucket_idx]) daily_path = LOG_DIR / f"daily_{run_ts}.csv" with open(daily_path, 'w', newline='') as f: cw = csv.writer(f) cw.writerow(['date','pnl','capital','dd_pct','boost','beta','mc_status','trades']) pk_log = 25000.0 for s in dstats: pk_log = max(pk_log, s['cap']) cw.writerow([s['date'], f"{s['pnl']:.4f}", f"{s['cap']:.4f}", f"{(pk_log - s['cap'])/pk_log*100:.4f}", f"{s['boost']:.4f}", f"{s['beta']:.2f}", s['mc_status'], s['trades']]) summary_d = { 'run_ts': run_ts, 'n_days': n_d, 'n_trades': len(tr), # ── Performance ── 'roi_pct': round(roi, 4), 'pf': round(pf, 4), 'max_dd_pct': round(max_dd, 4), 'sharpe_annualized': round(actual_sh, 4), 'wr_pct': round(wr, 4), 'capital_final': round(engine.capital, 4), 'avg_win_pct': round(float(np.mean(pnl_pcts[pnl_pcts>0])),4) if any(pnl_pcts>0) else None, 'avg_loss_pct': round(float(np.mean(pnl_pcts[pnl_pcts<=0])),4) if any(pnl_pcts<=0) else None, 'h1_pnl': round(h1, 4), 'h2_pnl': round(h2, 4), 'h2h1_ratio': round(h2h1, 4) if not math.isnan(h2h1) else None, # ── Robustness ── 'walk_forward_sharpes': [round(s, 4) for s in wf_sharpes], 'walk_forward_consistent': wf_consistent, 'bootstrap_sharpe_ci95': [round(float(np.percentile(boot_sh, 2.5)), 3), round(float(np.percentile(boot_sh, 97.5)), 3)], 'bootstrap_pf_ci95': [round(float(np.percentile(boot_pf_b, 2.5)), 3), round(float(np.percentile(boot_pf_b, 97.5)), 3)], 'bootstrap_sharpe_pct_positive': round(float(100 * np.mean(boot_sh > 0)), 1), 'bootstrap_pf_pct_above_1': round(float(100 * np.mean(boot_pf_b > 1.0)), 1), 'permutation_pvalue': round(pval, 4), 't_stat': round(t_stat, 4), 'ljung_box_q5': round(lb_q, 4), 'ljung_box_p5': round(lb_p, 4), 'lag1_acf': round(acf_vals[0], 4), 'lag2_acf': round(acf_vals[1], 4), # ── Trade distribution ── 'leverage_mean': round(float(np.mean(levs_arr)), 3), 'leverage_max': round(float(np.max(levs_arr)), 3), 'leverage_p90': round(float(np.percentile(levs_arr, 90)), 3), 'leverage_p95': round(float(np.percentile(levs_arr, 95)), 3), 'leverage_p99': round(float(np.percentile(levs_arr, 99)), 3), 'bars_held_mean': round(float(np.mean(bh_arr)), 2), 'bars_held_median': int(np.median(bh_arr)), 'pnl_skew': round(float(sp_skew(pnl_pcts)), 4), 'pnl_kurt': round(float(sp_kurt(pnl_pcts)), 4), 'max_win_streak': max_win_streak, 'max_loss_streak': max_loss_streak, 'exit_reasons': exit_ctr, # ── Full parameter snapshot (all hyperparams) ── 'engine_kwargs': ENGINE_KWARGS, 'mc_base_cfg': MC_BASE_CFG, 'acb_config': { 'w750_threshold_pct': 60, 'beta_high': acb._beta_high if hasattr(acb, '_beta_high') else 0.8, 'beta_low': acb._beta_low if hasattr(acb, '_beta_low') else 0.2, 'w750_threshold_value': round(float(acb._w750_threshold), 8), 'n_dates': len(date_strings), }, 'ob_config': { 'n_dims': 4, 'provider': 'MockOBProvider', 'static_maker_fill_prob': 0.62, 'calibration': 'real Binance 2025-01-15', }, 'esof_hazard': 0.0, 'abs_max_leverage': ENGINE_KWARGS.get('max_leverage', 5.0), # soft-cap applied inside engine 'abs_max_leverage_ceiling': 6.0, } summary_path = LOG_DIR / f"summary_{run_ts}.json" with open(summary_path, 'w') as f: json.dump(summary_d, f, indent=2) bars_path = LOG_DIR / f"bars_{run_ts}.csv" with open(bars_path, 'w', newline='') as f: cw = csv.writer(f) cw.writerow(['date', 'bar_idx', 'vel_div', 'vol_ok', 'posture', 'regime_size_mult', 'position_open', 'boost', 'beta']) for b in bar_records: cw.writerow([b['date'], b['bar_idx'], f"{b['vel_div']:.8f}", b['vol_ok'], b['posture'], f"{b['regime_size_mult']:.6f}", b['position_open'], f"{b['boost']:.4f}", f"{b['beta']:.2f}"]) print(f" trades → {trades_path} ({len(tr)} rows)") print(f" daily → {daily_path} ({n_d} rows)") print(f" bars → {bars_path} ({len(bar_records)} rows)") print(f" summary → {summary_path}") print(f"{'═'*65}")