426 lines
18 KiB
Python
426 lines
18 KiB
Python
|
|
"""
|
|||
|
|
Exp 8 — scale_boost Robustness & Adaptive Parameterization
|
|||
|
|
|
|||
|
|
Two questions from exp7 scale_boost winner (thr=0.35, a=1.0):
|
|||
|
|
|
|||
|
|
Q1. Is it overfitting? (+5pp ROI AND -0.54pp DD on same 55 days it was found)
|
|||
|
|
Test: temporal split — first-half (days 1–27) vs second-half (days 28–55)
|
|||
|
|
If improvement holds in BOTH halves independently, it's structurally real.
|
|||
|
|
If only one half drives it, the result is temporally fragile.
|
|||
|
|
|
|||
|
|
Q2. Are threshold and alpha regime-dependent?
|
|||
|
|
Hypothesis: proxy_B is more discriminating in high-eigenvalue-regime days
|
|||
|
|
(high ACB beta). On those days, "calm" entries should receive stronger boost,
|
|||
|
|
and the threshold for "what qualifies as calm" should be tighter.
|
|||
|
|
|
|||
|
|
Adaptive formulas (using ACB state available in _try_entry as self._day_base_boost
|
|||
|
|
and self._day_beta):
|
|||
|
|
alpha_eff = alpha * day_base_boost (more boost on stressed days)
|
|||
|
|
thr_eff = threshold / day_base_boost (tighter gate on stressed days)
|
|||
|
|
Both together: combine both adjustments
|
|||
|
|
|
|||
|
|
Also test dvol-proxy adaptation: use day_beta directly as a continuous scaler.
|
|||
|
|
|
|||
|
|
Configs:
|
|||
|
|
0. Baseline
|
|||
|
|
1. Fixed: thr=0.35, a=1.0 (exp7 winner — must reproduce exp7 results)
|
|||
|
|
2. Adaptive-alpha: alpha_eff = 1.0 * day_base_boost, thr fixed at 0.35
|
|||
|
|
3. Adaptive-threshold: thr_eff = 0.35 / day_base_boost, alpha fixed at 1.0
|
|||
|
|
4. Adaptive-both: both formulas combined
|
|||
|
|
5. Beta-scaled alpha: alpha_eff = 1.0 * (1 + day_beta), thr fixed at 0.35
|
|||
|
|
(day_beta is the ACB eigenvalue signal; more direct than base_boost)
|
|||
|
|
|
|||
|
|
Results include:
|
|||
|
|
- Full 55-day metrics (standard)
|
|||
|
|
- First-half (days 1–27) and second-half (days 28–55) metrics split out
|
|||
|
|
to test temporal stability of the DD reduction
|
|||
|
|
- Per-day scale distribution analysis
|
|||
|
|
|
|||
|
|
Results logged to exp8_boost_robustness_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,
|
|||
|
|
)
|
|||
|
|
from nautilus_dolphin.nautilus.esf_alpha_orchestrator import NDAlphaEngine
|
|||
|
|
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── Re-use ProxyBaseEngine from exp7 (copy-minimal) ──────────────────────────
|
|||
|
|
|
|||
|
|
class ProxyBaseEngine(NDAlphaEngine):
|
|||
|
|
def __init__(self, *args, **kwargs):
|
|||
|
|
super().__init__(*args, **kwargs)
|
|||
|
|
self._current_proxy_b: float = 0.0
|
|||
|
|
self._proxy_b_history: list = []
|
|||
|
|
|
|||
|
|
def _update_proxy(self, inst: float, v750: float) -> float:
|
|||
|
|
pb = inst - v750
|
|||
|
|
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:]
|
|||
|
|
return pb
|
|||
|
|
|
|||
|
|
def _proxy_prank(self) -> float:
|
|||
|
|
if not self._proxy_b_history:
|
|||
|
|
return 0.5
|
|||
|
|
n = len(self._proxy_b_history)
|
|||
|
|
return sum(v < self._current_proxy_b for v in self._proxy_b_history) / n
|
|||
|
|
|
|||
|
|
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')
|
|||
|
|
self._update_proxy(inst, v750)
|
|||
|
|
|
|||
|
|
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()
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── Adaptive scale_boost engine ───────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
class AdaptiveBoostEngine(ProxyBaseEngine):
|
|||
|
|
"""
|
|||
|
|
scale_boost with optionally regime-adaptive threshold and alpha.
|
|||
|
|
|
|||
|
|
Fixed mode (adaptive_alpha=False, adaptive_thr=False, adaptive_beta=False):
|
|||
|
|
scale = 1 + alpha * max(0, threshold - prank)
|
|||
|
|
Identical to exp7 ProxyScaleEngine(mode='boost').
|
|||
|
|
|
|||
|
|
Adaptive modes use ACB state (self._day_base_boost, self._day_beta)
|
|||
|
|
which is set by begin_day() before any _try_entry calls in that day:
|
|||
|
|
|
|||
|
|
adaptive_alpha: alpha_eff = alpha * day_base_boost
|
|||
|
|
→ High-boost day (stressed eigenspace regime) → stronger boost on calm entries
|
|||
|
|
→ Low-boost day → modest boost
|
|||
|
|
|
|||
|
|
adaptive_thr: thr_eff = threshold / day_base_boost
|
|||
|
|
→ High-boost day → lower threshold → more selective (only very calm entries qualify)
|
|||
|
|
→ Low-boost day → higher threshold → more entries qualify
|
|||
|
|
|
|||
|
|
adaptive_beta: alpha_eff = alpha * (1 + day_beta)
|
|||
|
|
→ day_beta is the ACB's direct eigenvalue signal (0 when inactive)
|
|||
|
|
→ More discriminating on days where eigenvalue regime is active
|
|||
|
|
|
|||
|
|
Parameters can be combined freely.
|
|||
|
|
"""
|
|||
|
|
def __init__(self, *args,
|
|||
|
|
threshold: float = 0.35,
|
|||
|
|
alpha: float = 1.0,
|
|||
|
|
adaptive_alpha: bool = False,
|
|||
|
|
adaptive_thr: bool = False,
|
|||
|
|
adaptive_beta: bool = False,
|
|||
|
|
**kwargs):
|
|||
|
|
super().__init__(*args, **kwargs)
|
|||
|
|
self.threshold = threshold
|
|||
|
|
self.alpha = alpha
|
|||
|
|
self.adaptive_alpha = adaptive_alpha
|
|||
|
|
self.adaptive_thr = adaptive_thr
|
|||
|
|
self.adaptive_beta = adaptive_beta
|
|||
|
|
self._scale_history: list = []
|
|||
|
|
self._alpha_eff_history: list = []
|
|||
|
|
self._thr_eff_history: list = []
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def sizing_scale_mean(self) -> float:
|
|||
|
|
return float(np.mean(self._scale_history)) if self._scale_history else 1.0
|
|||
|
|
|
|||
|
|
def _try_entry(self, bar_idx, vel_div, prices, price_histories,
|
|||
|
|
v50_vel=0.0, v750_vel=0.0):
|
|||
|
|
result = super()._try_entry(bar_idx, vel_div, prices, price_histories,
|
|||
|
|
v50_vel, v750_vel)
|
|||
|
|
if result and self.position:
|
|||
|
|
boost = max(1.0, getattr(self, '_day_base_boost', 1.0))
|
|||
|
|
beta = max(0.0, getattr(self, '_day_beta', 0.0))
|
|||
|
|
|
|||
|
|
# Effective parameters
|
|||
|
|
alpha_eff = self.alpha
|
|||
|
|
if self.adaptive_alpha:
|
|||
|
|
alpha_eff *= boost # more boost on stressed-regime days
|
|||
|
|
if self.adaptive_beta:
|
|||
|
|
alpha_eff *= (1.0 + beta) # beta signal scales aggression
|
|||
|
|
|
|||
|
|
thr_eff = self.threshold
|
|||
|
|
if self.adaptive_thr:
|
|||
|
|
# High boost → lower threshold → be more selective about "calm"
|
|||
|
|
thr_eff = self.threshold / max(1.0, boost)
|
|||
|
|
|
|||
|
|
prank = self._proxy_prank()
|
|||
|
|
scale = 1.0 + alpha_eff * max(0.0, thr_eff - prank)
|
|||
|
|
|
|||
|
|
self.position.notional *= scale
|
|||
|
|
self._scale_history.append(scale)
|
|||
|
|
self._alpha_eff_history.append(alpha_eff)
|
|||
|
|
self._thr_eff_history.append(thr_eff)
|
|||
|
|
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
def reset(self):
|
|||
|
|
super().reset()
|
|||
|
|
self._scale_history = []
|
|||
|
|
self._alpha_eff_history = []
|
|||
|
|
self._thr_eff_history = []
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── Run harness with half-split ───────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def _run(engine_factory, name, d, fw):
|
|||
|
|
"""Full run + temporal split (first vs second half of days)."""
|
|||
|
|
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)
|
|||
|
|
|
|||
|
|
pf_list = d['parquet_files']
|
|||
|
|
n_days = len(pf_list)
|
|||
|
|
half = n_days // 2 # split point
|
|||
|
|
|
|||
|
|
daily_caps, daily_pnls = [], []
|
|||
|
|
half_caps = [[], []] # [first_half, second_half]
|
|||
|
|
half_pnls = [[], []]
|
|||
|
|
half_trades_n = [0, 0]
|
|||
|
|
|
|||
|
|
for i, pf in enumerate(pf_list):
|
|||
|
|
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)
|
|||
|
|
cap_after = eng.capital
|
|||
|
|
daily_caps.append(cap_after)
|
|||
|
|
daily_pnls.append(cap_after - cap_before)
|
|||
|
|
h = 0 if i < half else 1
|
|||
|
|
half_caps[h].append(cap_after)
|
|||
|
|
half_pnls[h].append(cap_after - cap_before)
|
|||
|
|
|
|||
|
|
tr = eng.trade_history
|
|||
|
|
n = len(tr)
|
|||
|
|
roi = (eng.capital - 25000.0) / 25000.0 * 100.0
|
|||
|
|
|
|||
|
|
def _metrics(caps, pnls, start_cap=25000.0):
|
|||
|
|
"""Compute metrics for a sub-period given daily capitals and a starting capital."""
|
|||
|
|
if not caps:
|
|||
|
|
return dict(roi=0.0, dd=0.0, sharpe=0.0)
|
|||
|
|
peak = start_cap
|
|||
|
|
max_dd = 0.0
|
|||
|
|
for c in caps:
|
|||
|
|
peak = max(peak, c)
|
|||
|
|
max_dd = max(max_dd, (peak - c) / peak * 100.0)
|
|||
|
|
total_pnl = sum(pnls)
|
|||
|
|
roi_sub = total_pnl / start_cap * 100.0
|
|||
|
|
dr = np.array([p / start_cap * 100.0 for p in pnls])
|
|||
|
|
sharpe = float(dr.mean() / (dr.std() + 1e-9) * math.sqrt(365)) if len(dr) > 1 else 0.0
|
|||
|
|
return dict(roi=roi_sub, dd=max_dd, sharpe=sharpe, n_days=len(caps))
|
|||
|
|
|
|||
|
|
if n == 0:
|
|||
|
|
return dict(name=name, roi=roi, pf=0.0, dd=0.0, wr=0.0, sharpe=0.0,
|
|||
|
|
trades=0, sizing_scale_mean=1.0)
|
|||
|
|
|
|||
|
|
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_val = 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
|
|||
|
|
|
|||
|
|
# First/second half split — using capital at end of first-half as baseline for second half
|
|||
|
|
cap_at_halftime = half_caps[0][-1] if half_caps[0] else 25000.0
|
|||
|
|
h1 = _metrics(half_caps[0], half_pnls[0], start_cap=25000.0)
|
|||
|
|
h2 = _metrics(half_caps[1], half_pnls[1], start_cap=cap_at_halftime)
|
|||
|
|
|
|||
|
|
sizing_scale_mean = getattr(eng, 'sizing_scale_mean', 1.0)
|
|||
|
|
|
|||
|
|
# Alpha/threshold eff distributions for adaptive engines
|
|||
|
|
alpha_mean = 1.0
|
|||
|
|
thr_mean = 0.35
|
|||
|
|
eng_ae = eng if isinstance(eng, AdaptiveBoostEngine) else None
|
|||
|
|
if eng_ae:
|
|||
|
|
if eng_ae._alpha_eff_history:
|
|||
|
|
alpha_mean = float(np.mean(eng_ae._alpha_eff_history))
|
|||
|
|
if eng_ae._thr_eff_history:
|
|||
|
|
thr_mean = float(np.mean(eng_ae._thr_eff_history))
|
|||
|
|
|
|||
|
|
return dict(
|
|||
|
|
name=name,
|
|||
|
|
roi=roi, pf=pf_val, dd=max_dd, wr=wr, sharpe=sharpe, trades=n,
|
|||
|
|
sizing_scale_mean=sizing_scale_mean,
|
|||
|
|
alpha_eff_mean=alpha_mean,
|
|||
|
|
thr_eff_mean=thr_mean,
|
|||
|
|
# Temporal split
|
|||
|
|
h1_roi=h1['roi'], h1_dd=h1['dd'], h1_sharpe=h1['sharpe'],
|
|||
|
|
h2_roi=h2['roi'], h2_dd=h2['dd'], h2_sharpe=h2['sharpe'],
|
|||
|
|
split_days=(half, n_days - half),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── Main ──────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
t_start = time.time()
|
|||
|
|
print("=" * 74)
|
|||
|
|
print("Exp 8 — scale_boost Robustness & Adaptive Parameterization")
|
|||
|
|
print("=" * 74)
|
|||
|
|
|
|||
|
|
ensure_jit()
|
|||
|
|
d = load_data()
|
|||
|
|
fw = load_forewarner()
|
|||
|
|
|
|||
|
|
configs = [
|
|||
|
|
("0_baseline",
|
|||
|
|
lambda kw: NDAlphaEngine(**kw)),
|
|||
|
|
("1_fixed_thr035_a1.0",
|
|||
|
|
lambda kw: AdaptiveBoostEngine(threshold=0.35, alpha=1.0, **kw)),
|
|||
|
|
("2_adaptive_alpha__thr035_a1.0xboost",
|
|||
|
|
lambda kw: AdaptiveBoostEngine(threshold=0.35, alpha=1.0,
|
|||
|
|
adaptive_alpha=True, **kw)),
|
|||
|
|
("3_adaptive_thr__thr035/boost_a1.0",
|
|||
|
|
lambda kw: AdaptiveBoostEngine(threshold=0.35, alpha=1.0,
|
|||
|
|
adaptive_thr=True, **kw)),
|
|||
|
|
("4_adaptive_both__thr/boost_axboost",
|
|||
|
|
lambda kw: AdaptiveBoostEngine(threshold=0.35, alpha=1.0,
|
|||
|
|
adaptive_alpha=True, adaptive_thr=True, **kw)),
|
|||
|
|
("5_adaptive_beta__thr035_ax(1+beta)",
|
|||
|
|
lambda kw: AdaptiveBoostEngine(threshold=0.35, alpha=1.0,
|
|||
|
|
adaptive_beta=True, **kw)),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
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" scale={res['sizing_scale_mean']:.4f} alpha_eff={res['alpha_eff_mean']:.4f}"
|
|||
|
|
f" ({elapsed:.0f}s)")
|
|||
|
|
print(f" H1(days 1-{res['split_days'][0]}): ROI={res['h1_roi']:.2f}%"
|
|||
|
|
f" DD={res['h1_dd']:.2f}% Sharpe={res['h1_sharpe']:.3f}")
|
|||
|
|
print(f" H2(days {res['split_days'][0]+1}-{sum(res['split_days'])}): ROI={res['h2_roi']:.2f}%"
|
|||
|
|
f" DD={res['h2_dd']:.2f}% Sharpe={res['h2_sharpe']:.3f}")
|
|||
|
|
results.append(res)
|
|||
|
|
|
|||
|
|
# Baseline verification
|
|||
|
|
b = results[0]
|
|||
|
|
fixed = results[1]
|
|||
|
|
gold_match = (abs(b['roi'] - GOLD['roi']) < 0.5 and abs(b['dd'] - GOLD['dd']) < 0.5
|
|||
|
|
and abs(b['trades'] - GOLD['trades']) < 10)
|
|||
|
|
fixed_match = (abs(fixed['roi'] - 93.61) < 0.5 and abs(fixed['dd'] - 14.51) < 0.5)
|
|||
|
|
print(f"\n{'='*74}")
|
|||
|
|
print(f"VERIFICATION:")
|
|||
|
|
print(f" Baseline vs gold: {'PASS ✓' if gold_match else 'FAIL ✗'} "
|
|||
|
|
f"(ROI={b['roi']:.2f}% DD={b['dd']:.2f}%)")
|
|||
|
|
print(f" Fixed vs exp7 winner: {'PASS ✓' if fixed_match else 'FAIL ✗'} "
|
|||
|
|
f"(ROI={fixed['roi']:.2f}% DD={fixed['dd']:.2f}%)")
|
|||
|
|
|
|||
|
|
print(f"\n{'='*74}")
|
|||
|
|
print(f"FULL-PERIOD RESULTS (target: DD<15.05% AND ROI>=84.1%)")
|
|||
|
|
hdr = f"{'Config':<46} {'ROI%':>7} {'PF':>6} {'DD%':>6} {'ΔDD':>6} {'ΔROI':>6} {'scale':>7} {'alpha':>7} {'OK':>4}"
|
|||
|
|
print(hdr); print('-' * 98)
|
|||
|
|
base_roi = b['roi']; base_dd = b['dd']
|
|||
|
|
for r in results:
|
|||
|
|
dROI = r['roi'] - base_roi; dDD = r['dd'] - base_dd
|
|||
|
|
ok = 'Y' if (r['dd'] < GOLD['dd'] and r['roi'] >= GOLD['roi'] * 0.95) else 'N'
|
|||
|
|
print(f"{r['name']:<46} {r['roi']:>7.2f} {r['pf']:>6.4f} {r['dd']:>6.2f} "
|
|||
|
|
f"{dDD:>+6.2f} {dROI:>+6.2f} {r['sizing_scale_mean']:>7.4f} "
|
|||
|
|
f"{r['alpha_eff_mean']:>7.4f} {ok:>4}")
|
|||
|
|
|
|||
|
|
print(f"\n{'='*74}")
|
|||
|
|
print("TEMPORAL SPLIT — Overfitting check (does improvement hold in both halves?)")
|
|||
|
|
h_days = results[0]['split_days']
|
|||
|
|
print(f"Split: H1=days 1–{h_days[0]}, H2=days {h_days[0]+1}–{sum(h_days)}")
|
|||
|
|
print(f"{'Config':<46} {'H1 ROI':>8} {'H1 DD':>7} {'H2 ROI':>8} {'H2 DD':>7} "
|
|||
|
|
f"{'ΔH1DD':>7} {'ΔH2DD':>7}")
|
|||
|
|
print('-' * 98)
|
|||
|
|
b_h1dd = b['h1_dd']; b_h2dd = b['h2_dd']
|
|||
|
|
for r in results:
|
|||
|
|
dH1 = r['h1_dd'] - b_h1dd; dH2 = r['h2_dd'] - b_h2dd
|
|||
|
|
print(f"{r['name']:<46} {r['h1_roi']:>8.2f} {r['h1_dd']:>7.2f} "
|
|||
|
|
f"{r['h2_roi']:>8.2f} {r['h2_dd']:>7.2f} {dH1:>+7.2f} {dH2:>+7.2f}")
|
|||
|
|
|
|||
|
|
print(f"\n{'='*74}")
|
|||
|
|
print("OVERFITTING VERDICT:")
|
|||
|
|
for r in results[1:]:
|
|||
|
|
h1_better = r['h1_dd'] < b_h1dd
|
|||
|
|
h2_better = r['h2_dd'] < b_h2dd
|
|||
|
|
both = h1_better and h2_better
|
|||
|
|
neither = (not h1_better) and (not h2_better)
|
|||
|
|
verdict = "BOTH halves improve DD ✓" if both else \
|
|||
|
|
"NEITHER half improves DD ✗" if neither else \
|
|||
|
|
f"Mixed: H1={'↓' if h1_better else '↑'} H2={'↓' if h2_better else '↑'}"
|
|||
|
|
print(f" {r['name']:<46}: {verdict}")
|
|||
|
|
|
|||
|
|
# Adaptive summary
|
|||
|
|
print(f"\n{'='*74}")
|
|||
|
|
print("ADAPTIVE PARAMETERIZATION — alpha_eff and thr_eff distributions:")
|
|||
|
|
for r in results[2:]:
|
|||
|
|
print(f" {r['name']:<46}: alpha_eff_mean={r['alpha_eff_mean']:.4f}"
|
|||
|
|
f" thr_eff_mean={r['thr_eff_mean']:.4f}")
|
|||
|
|
|
|||
|
|
outfile = _HERE / "exp8_boost_robustness_results.json"
|
|||
|
|
log_results(results, outfile, gold=GOLD, meta={
|
|||
|
|
"exp": "exp8",
|
|||
|
|
"question": "Is scale_boost overfitting? Are threshold/alpha regime-dependent?",
|
|||
|
|
"total_elapsed_s": round(time.time() - t_start, 1),
|
|||
|
|
"gold_match": gold_match,
|
|||
|
|
"fixed_match": fixed_match,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
total = time.time() - t_start
|
|||
|
|
print(f"\nTotal elapsed: {total/60:.1f} min")
|
|||
|
|
print("Done.")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|