Includes core prod + GREEN/BLUE subsystems: - prod/ (BLUE harness, configs, scripts, docs) - nautilus_dolphin/ (GREEN Nautilus-native impl + dvae/ preserved) - adaptive_exit/ (AEM engine + models/bucket_assignments.pkl) - Observability/ (EsoF advisor, TUI, dashboards) - external_factors/ (EsoF producer) - mc_forewarning_qlabs_fork/ (MC regime/envelope) Excludes runtime caches, logs, backups, and reproducible artifacts per .gitignore.
324 lines
13 KiB
Python
Executable File
324 lines
13 KiB
Python
Executable File
"""
|
||
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()
|