Files
DOLPHIN/nautilus_dolphin/dvae/exp6_stop_test.py
hjnormey 01c19662cb initial: import DOLPHIN baseline 2026-04-21 from dolphinng5_predict working tree
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.
2026-04-21 16:58:38 +02:00

324 lines
13 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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()