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.
This commit is contained in:
323
nautilus_dolphin/dvae/exp6_stop_test.py
Executable file
323
nautilus_dolphin/dvae/exp6_stop_test.py
Executable file
@@ -0,0 +1,323 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user