""" Exp 6 — Stop-Tightening: Global vs proxy_B-Gated Stop Test Context: proxy_B = instability_50 − v750_lambda_max_velocity AUC=0.715 for eigenspace stress, r=+0.42 (p=0.003) with intraday MAE. Retroactive pure_stop benchmark (stop=0.003) showed +1.36pp ROI, −0.14pp DD, 18 triggers. Hypothesis: gated stop (only when proxy_B_entry > gate_pct percentile) should preferentially protect worst-MAE trades while leaving the rest alone. Configs tested: A. Baseline — stop_pct=1.0 (gold control; must reproduce gold metrics) B. Global stop sweep — stop_pct ∈ [0.003, 0.005, 0.010] (3 configs) C. proxy_B gated — gate_pct × tight_stop: [0.65, 0.75] × [0.005, 0.010] = 4 configs Total: 8 full AE runs. Focus metric: DD < 15.05% AND ROI >= 84.1% (95% of gold=88.55%). Also tracked: n_override_set (entries that received tight stop), stop_exits, exit_reason breakdown. Results logged to exp6_stop_test_results.json. """ import sys, time, json, math sys.stdout.reconfigure(encoding='utf-8', errors='replace') from pathlib import Path import numpy as np _HERE = Path(__file__).resolve().parent sys.path.insert(0, str(_HERE.parent)) from exp_shared import ( ensure_jit, ENGINE_KWARGS, GOLD, MC_BASE_CFG, load_data, load_forewarner, log_results, print_table, ) from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker # ── proxy_B Gated Stop Engine ───────────────────────────────────────────────── class ProxyStopGatedEngine(NDAlphaEngine): """ Extends NDAlphaEngine with proxy_B-gated per-trade stop logic. At each bar, proxy_B = instability_50 - v750_lambda_max_velocity is computed and tracked in a rolling 500-bar history. At entry, if proxy_B_at_entry > percentile(history, gate_pct): → sets _pending_stop_override = tight_stop Otherwise _pending_stop_override = None (global stop_pct=1.0 applies). Efficiency hypothesis: gated stop should reduce DD with less ROI cost per unit of DD reduction vs global stop (since it preferentially targets high-MAE trades). """ def __init__(self, *args, gate_pct: float = 0.75, tight_stop: float = 0.005, **kwargs): super().__init__(*args, **kwargs) self.gate_pct = gate_pct self.tight_stop = tight_stop self._current_proxy_b: float = 0.0 self._proxy_b_history: list = [] # rolling 500-bar window self.n_override_set: int = 0 # entries where tight stop was applied def process_day(self, date_str, df, asset_columns, vol_regime_ok=None, direction=None, posture='APEX'): self.begin_day(date_str, posture=posture, direction=direction) bid = 0 for ri in range(len(df)): row = df.iloc[ri] vd = row.get('vel_div') if vd is None or not np.isfinite(float(vd)): self._global_bar_idx += 1; bid += 1; continue def gf(col): v = row.get(col) if v is None: return 0.0 try: return float(v) if np.isfinite(float(v)) else 0.0 except: return 0.0 v50 = gf('v50_lambda_max_velocity') v750 = gf('v750_lambda_max_velocity') inst = gf('instability_50') pb = inst - v750 # Update proxy_B state before step_bar so _try_entry can read it self._current_proxy_b = pb self._proxy_b_history.append(pb) if len(self._proxy_b_history) > 500: self._proxy_b_history = self._proxy_b_history[-500:] prices = {} for ac in asset_columns: p = row.get(ac) if p is not None and p > 0 and np.isfinite(float(p)): prices[ac] = float(p) if not prices: self._global_bar_idx += 1; bid += 1; continue vrok = bool(vol_regime_ok[ri]) if vol_regime_ok is not None else (bid >= 100) self.step_bar(bar_idx=ri, vel_div=float(vd), prices=prices, vol_regime_ok=vrok, v50_vel=v50, v750_vel=v750) bid += 1 return self.end_day() def _try_entry(self, bar_idx, vel_div, prices, price_histories, v50_vel=0.0, v750_vel=0.0): # Gate: set _pending_stop_override when proxy_B is elevated if len(self._proxy_b_history) >= 20: threshold = np.percentile(self._proxy_b_history, self.gate_pct * 100.0) if self._current_proxy_b > threshold: self._pending_stop_override = self.tight_stop self.n_override_set += 1 else: self._pending_stop_override = None else: self._pending_stop_override = None return super()._try_entry(bar_idx, vel_div, prices, price_histories, v50_vel, v750_vel) def reset(self): super().reset() self._current_proxy_b = 0.0 self._proxy_b_history = [] self.n_override_set = 0 # ── Run harness ─────────────────────────────────────────────────────────────── def _run(engine_factory, name, d, fw): """ Full 55-day backtest using the shared data dict `d`. Returns metrics dict with additional stop-specific fields. """ from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker import pandas as pd kw = ENGINE_KWARGS.copy() acb = AdaptiveCircuitBreaker() acb.preload_w750(d['date_strings']) eng = engine_factory(kw) eng.set_ob_engine(d['ob_eng']) eng.set_acb(acb) if fw is not None: eng.set_mc_forewarner(fw, MC_BASE_CFG) eng.set_esoteric_hazard_multiplier(0.0) daily_caps, daily_pnls = [], [] for pf in d['parquet_files']: ds = pf.stem df, acols, dvol = d['pq_data'][ds] cap_before = eng.capital vol_ok = np.where(np.isfinite(dvol), dvol > d['vol_p60'], False) eng.process_day(ds, df, acols, vol_regime_ok=vol_ok) daily_caps.append(eng.capital) daily_pnls.append(eng.capital - cap_before) tr = eng.trade_history n = len(tr) roi = (eng.capital - 25000.0) / 25000.0 * 100.0 if n == 0: return dict(name=name, roi=roi, pf=0.0, dd=0.0, wr=0.0, sharpe=0.0, trades=0, stop_exits=0, n_override_set=0, exit_reasons={}) def _abs(t): return t.pnl_absolute if hasattr(t, 'pnl_absolute') else t.pnl_pct * 250.0 wins = [t for t in tr if _abs(t) > 0] losses = [t for t in tr if _abs(t) <= 0] wr = len(wins) / n * 100.0 pf = sum(_abs(t) for t in wins) / max(abs(sum(_abs(t) for t in losses)), 1e-9) peak_cap, max_dd = 25000.0, 0.0 for cap in daily_caps: peak_cap = max(peak_cap, cap) max_dd = max(max_dd, (peak_cap - cap) / peak_cap * 100.0) dr = np.array([p / 25000.0 * 100.0 for p in daily_pnls]) sharpe = float(dr.mean() / (dr.std() + 1e-9) * math.sqrt(365)) if len(dr) > 1 else 0.0 # Exit reason breakdown exit_reasons = {} for t in tr: r = t.exit_reason if hasattr(t, 'exit_reason') else 'UNKNOWN' exit_reasons[r] = exit_reasons.get(r, 0) + 1 # Stop-specific stats stop_exits = getattr(eng, 'stop_exits', 0) n_override_set = getattr(eng, 'n_override_set', 0) # Efficiency: ROI cost per DD unit reduced vs baseline # (computed in main after baseline is known) return dict( name=name, roi=roi, pf=pf, dd=max_dd, wr=wr, sharpe=sharpe, trades=n, stop_exits=stop_exits, n_override_set=n_override_set, stop_trigger_rate=stop_exits / n if n > 0 else 0.0, override_trigger_rate=n_override_set / n if n > 0 else 0.0, exit_reasons=exit_reasons, ) # ── Main ────────────────────────────────────────────────────────────────────── def main(): t_start = time.time() print("=" * 70) print("Exp 6 — Stop-Tightening: Global vs proxy_B-Gated Stop Test") print("=" * 70) ensure_jit() d = load_data() fw = load_forewarner() configs = [] # A. Baseline configs.append(("A_baseline", lambda kw: NDAlphaEngine(**kw))) # B. Global stop sweep for sp in [0.003, 0.005, 0.010]: sp_str = f"{sp:.3f}" def _make_global(stop_val): def _factory(kw): k2 = dict(kw); k2['stop_pct'] = stop_val return NDAlphaEngine(**k2) return _factory configs.append((f"B_global_stop={sp_str}", _make_global(sp))) # C. proxy_B gated stop for gate_pct in [0.65, 0.75]: for tight_stop in [0.005, 0.010]: tag = f"C_gated_gate={gate_pct:.2f}_stop={tight_stop:.3f}" def _make_gated(gp, ts): def _factory(kw): return ProxyStopGatedEngine(gate_pct=gp, tight_stop=ts, **kw) return _factory configs.append((tag, _make_gated(gate_pct, tight_stop))) results = [] for i, (name, factory) in enumerate(configs): t0 = time.time() print(f"\n[{i+1}/{len(configs)}] {name} ...") res = _run(factory, name, d, fw) elapsed = time.time() - t0 print(f" ROI={res['roi']:.2f}% PF={res['pf']:.4f} DD={res['dd']:.2f}% " f"WR={res['wr']:.2f}% Sharpe={res['sharpe']:.3f} Trades={res['trades']} " f"stop_exits={res['stop_exits']} n_override={res['n_override_set']} " f"({elapsed:.0f}s)") results.append(res) # Verification check baseline = results[0] print(f"\n{'='*70}") print(f"VERIFICATION — Baseline vs Gold:") print(f" ROI: {baseline['roi']:.2f}% (gold={GOLD['roi']:.2f}%)") print(f" PF: {baseline['pf']:.4f} (gold={GOLD['pf']:.4f})") print(f" DD: {baseline['dd']:.2f}% (gold={GOLD['dd']:.2f}%)") print(f" Trades: {baseline['trades']} (gold={GOLD['trades']})") gold_match = ( abs(baseline['roi'] - GOLD['roi']) < 0.5 and abs(baseline['pf'] - GOLD['pf']) < 0.005 and abs(baseline['dd'] - GOLD['dd']) < 0.5 and abs(baseline['trades'] - GOLD['trades']) < 10 ) print(f" Match: {'PASS ✓' if gold_match else 'FAIL ✗ — check engine state'}") # Efficiency analysis vs baseline base_roi = baseline['roi'] base_dd = baseline['dd'] target_roi = GOLD['roi'] * 0.95 # 84.1% target_dd = GOLD['dd'] # 15.05% print(f"\n{'='*70}") print(f"EFFICIENCY TABLE (target: DD<{target_dd:.2f}% AND ROI>={target_roi:.1f}%)") print(f"{'Config':<42} {'ROI%':>7} {'PF':>6} {'DD%':>6} {'ΔDD':>6} {'ΔROI':>6} {'stops':>6} {'ovrd':>6} {'OK':>4}") print('-' * 90) for r in results: delta_roi = r['roi'] - base_roi delta_dd = r['dd'] - base_dd ok = 'Y' if (r['dd'] < target_dd and r['roi'] >= target_roi) else 'N' stops_str = str(r['stop_exits']) ovrd_str = str(r['n_override_set']) if r['n_override_set'] > 0 else '-' print(f"{r['name']:<42} {r['roi']:>7.2f} {r['pf']:>6.4f} {r['dd']:>6.2f} " f"{delta_dd:>+6.2f} {delta_roi:>+6.2f} {stops_str:>6} {ovrd_str:>6} {ok:>4}") # Gated vs global efficiency: ROI cost per DD-point gained print(f"\n{'='*70}") print("EFFICIENCY RATIO (|ΔROI| / |ΔDD|) — lower = better DD reduction per ROI cost") for r in results[1:]: # skip baseline delta_roi = r['roi'] - base_roi delta_dd = r['dd'] - base_dd if abs(delta_dd) > 0.01: eff = abs(delta_roi) / abs(delta_dd) print(f" {r['name']}: |ΔROI/ΔDD| = {eff:.3f} (ΔROI={delta_roi:+.2f}%, ΔDD={delta_dd:+.2f}%)") else: print(f" {r['name']}: ΔDD≈0, no ratio") # Exit reason breakdown for all configs print(f"\n{'='*70}") print("EXIT REASON BREAKDOWN:") for r in results: reasons = r.get('exit_reasons', {}) parts = ', '.join(f"{k}={v}" for k, v in sorted(reasons.items())) print(f" {r['name']}: {parts}") # Save results outfile = _HERE / "exp6_stop_test_results.json" log_results(results, outfile, gold=GOLD, meta={ "exp": "exp6", "hypothesis": "proxy_B gated stop reduces DD with less ROI cost per unit vs global stop", "total_elapsed_s": round(time.time() - t_start, 1), "gold_match": gold_match, }) total = time.time() - t_start print(f"\nTotal elapsed: {total/60:.1f} min") print("Done.") if __name__ == "__main__": main()