409 lines
20 KiB
Python
409 lines
20 KiB
Python
|
|
"""
|
|||
|
|
Exp 9 — Extended Leverage Ceiling Test
|
|||
|
|
|
|||
|
|
Motivation:
|
|||
|
|
GOLD (adaptive_beta) achieved DD=14.32% (−0.72pp vs Silver=15.05%). The question is whether
|
|||
|
|
this headroom permits raising the leverage ceiling above the current 5x soft / 6x hard cap
|
|||
|
|
without exceeding Silver's DD budget — or ideally staying below GOLD's DD while gaining ROI.
|
|||
|
|
|
|||
|
|
Architecture recap (DO NOT MODIFY any of the below):
|
|||
|
|
bet_sizer.max_leverage (5.0) → convex curve soft cap — strength_score^3 tops out here
|
|||
|
|
base_max_leverage (5.0) → base for ACB regime excursions
|
|||
|
|
abs_max_leverage (6.0) → hard ceiling: min(base*regime_mult, abs_max)
|
|||
|
|
ACB regime_size_mult (1.0–1.6) → can push raw_leverage above bet_sizer.max_leverage
|
|||
|
|
→ Current actual range: 0.5x – 6.0x (6x hard cap is binding on extreme signals)
|
|||
|
|
|
|||
|
|
Fork strategy:
|
|||
|
|
ExtendedLeverageEngine(AdaptiveBoostEngine) — subclass only, zero parent modification.
|
|||
|
|
Sets all three caps consistently: bet_sizer.max_leverage = base_max_leverage = extended_soft_cap,
|
|||
|
|
abs_max_leverage = extended_abs_cap.
|
|||
|
|
Example at 7.0/8.0: extreme signal gets bet_sizer→7.0 × regime_mult≈1.2 = 8.4, clamped at 8.0.
|
|||
|
|
|
|||
|
|
MC-Forewarner interaction (CRITICAL):
|
|||
|
|
begin_day() feeds MC: mc_cfg['max_leverage'] = base_max_leverage * day_base_boost
|
|||
|
|
MC was trained at max_leverage 5.0–6.0 (champion region). At 8x+ it's outside training
|
|||
|
|
distribution → One-Class SVM envelope_score likely < -1.0 → RED → regime_dd_halt=True
|
|||
|
|
→ ZERO trades that day.
|
|||
|
|
|
|||
|
|
Two modes tested:
|
|||
|
|
mc_ref=5.0 ("decoupled"): MC sees original 5.0x reference → same verdicts as GOLD
|
|||
|
|
Pure effect of higher ceiling with MC behavior unchanged
|
|||
|
|
mc_ref=soft_cap ("coupled"): MC sees actual new cap → may flag RED/ORANGE on more days
|
|||
|
|
Tests "what would MC do if it knew about the new leverage"
|
|||
|
|
|
|||
|
|
ExtendedLeverageEngine.begin_day() temporarily swaps base_max_leverage to mc_leverage_ref
|
|||
|
|
before calling super().begin_day(), then restores extended caps for actual trading.
|
|||
|
|
This is the ONLY divergence from parent logic.
|
|||
|
|
|
|||
|
|
Test configs:
|
|||
|
|
A: 5.0/6.0 mc_ref=5.0 — GOLD reference (must reproduce 96.55% / 14.32%)
|
|||
|
|
B: 6.0/7.0 mc_ref=5.0 — +1x soft, MC decoupled
|
|||
|
|
C: 7.0/8.0 mc_ref=5.0 — +2x soft, MC decoupled
|
|||
|
|
D: 8.0/9.0 mc_ref=5.0 — +3x soft, MC decoupled
|
|||
|
|
E: 9.0/10.0 mc_ref=5.0 — +4x soft, MC decoupled
|
|||
|
|
F: 6.0/7.0 mc_ref=6.0 — +1x soft, MC coupled
|
|||
|
|
G: 7.0/8.0 mc_ref=7.0 — +2x soft, MC coupled
|
|||
|
|
H: 8.0/9.0 mc_ref=8.0 — +3x soft, MC coupled
|
|||
|
|
Total: 8 runs × ~250s ≈ 35 min
|
|||
|
|
|
|||
|
|
Key metrics:
|
|||
|
|
ROI%, DD%, PF, Trades, avg_leverage (mean realized leverage per trade)
|
|||
|
|
MC status breakdown: RED days / ORANGE days / OK days / halted days
|
|||
|
|
Δ vs GOLD: ΔROI, ΔDD
|
|||
|
|
|
|||
|
|
Results → exp9_leverage_ceiling_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.proxy_boost_engine import AdaptiveBoostEngine, DEFAULT_THRESHOLD, DEFAULT_ALPHA
|
|||
|
|
from nautilus_dolphin.nautilus.adaptive_circuit_breaker import AdaptiveCircuitBreaker
|
|||
|
|
|
|||
|
|
# ── GOLD reference for this experiment (adaptive_beta, not the old silver) ───
|
|||
|
|
_GOLD_EXP9 = dict(roi=96.55, dd=14.32, trades=2155) # adaptive_beta GOLD
|
|||
|
|
_SILVER = dict(roi=88.55, dd=15.05, trades=2155) # former gold = regression floor
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── ExtendedLeverageEngine — subclass only, ZERO parent modification ──────────
|
|||
|
|
|
|||
|
|
class ExtendedLeverageEngine(AdaptiveBoostEngine):
|
|||
|
|
"""
|
|||
|
|
Extended leverage ceiling fork of AdaptiveBoostEngine (adaptive_beta GOLD).
|
|||
|
|
|
|||
|
|
Three new constructor params vs parent:
|
|||
|
|
extended_soft_cap : replaces max_leverage (5.0) and base_max_leverage
|
|||
|
|
extended_abs_cap : replaces abs_max_leverage (6.0) — hard ceiling
|
|||
|
|
mc_leverage_ref : what to feed the MC-Forewarner (independent of actual caps)
|
|||
|
|
If None → MC sees extended_soft_cap (fully coupled)
|
|||
|
|
Set to 5.0 → MC always sees original 5x reference (decoupled)
|
|||
|
|
|
|||
|
|
ALL existing leverage logic (convex curve, ACB regime_size_mult, STALKER 2x cap,
|
|||
|
|
proxy_B scale_boost, etc.) is completely untouched. Only the three cap values change.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def __init__(
|
|||
|
|
self,
|
|||
|
|
*args,
|
|||
|
|
extended_soft_cap: float = 5.0,
|
|||
|
|
extended_abs_cap: float = 6.0,
|
|||
|
|
mc_leverage_ref: float = None,
|
|||
|
|
**kwargs,
|
|||
|
|
):
|
|||
|
|
# Inject extended caps as max_leverage / abs_max_leverage so parent stores them
|
|||
|
|
kwargs['max_leverage'] = extended_soft_cap
|
|||
|
|
kwargs['abs_max_leverage'] = extended_abs_cap
|
|||
|
|
super().__init__(*args, **kwargs)
|
|||
|
|
|
|||
|
|
# Explicitly ensure all three cap locations are consistent
|
|||
|
|
# (parent __init__ sets base_max_leverage = max_leverage and bet_sizer.max_leverage
|
|||
|
|
# from the kwargs, but we set explicitly for clarity)
|
|||
|
|
self.bet_sizer.max_leverage = extended_soft_cap
|
|||
|
|
self.base_max_leverage = extended_soft_cap
|
|||
|
|
self.abs_max_leverage = extended_abs_cap
|
|||
|
|
|
|||
|
|
self._extended_soft_cap = extended_soft_cap
|
|||
|
|
self._extended_abs_cap = extended_abs_cap
|
|||
|
|
# MC reference: 5.0 = decoupled (MC sees original gold reference)
|
|||
|
|
# extended_soft_cap = coupled (MC sees actual new cap)
|
|||
|
|
self._mc_leverage_ref = mc_leverage_ref if mc_leverage_ref is not None else extended_soft_cap
|
|||
|
|
|
|||
|
|
# Per-day MC verdict monitoring
|
|||
|
|
self.mc_monitor = dict(red=0, orange=0, ok=0, halted=0, total=0)
|
|||
|
|
|
|||
|
|
def begin_day(self, date_str: str, posture: str = 'APEX', direction=None) -> None:
|
|||
|
|
"""
|
|||
|
|
Temporarily expose mc_leverage_ref to the MC-Forewarner assessment,
|
|||
|
|
then restore the true extended caps for actual trading.
|
|||
|
|
|
|||
|
|
The parent begin_day() computes:
|
|||
|
|
mc_cfg['max_leverage'] = self.base_max_leverage * self._day_base_boost
|
|||
|
|
By setting base_max_leverage = mc_leverage_ref before the call, MC sees the
|
|||
|
|
reference leverage. After super().begin_day() returns, regime_dd_halt and
|
|||
|
|
_day_mc_scale are already set correctly for that reference. We then restore
|
|||
|
|
the true extended caps so _try_entry uses the full new ceiling.
|
|||
|
|
"""
|
|||
|
|
# Save true extended caps
|
|||
|
|
_true_base = self.base_max_leverage
|
|||
|
|
_true_abs = self.abs_max_leverage
|
|||
|
|
_true_sizer = self.bet_sizer.max_leverage
|
|||
|
|
|
|||
|
|
# Temporarily expose mc_leverage_ref to parent's MC assessment
|
|||
|
|
self.base_max_leverage = self._mc_leverage_ref
|
|||
|
|
self.bet_sizer.max_leverage = self._mc_leverage_ref
|
|||
|
|
self.abs_max_leverage = self._mc_leverage_ref # only affects _try_entry, safe to temp-set
|
|||
|
|
|
|||
|
|
super().begin_day(date_str, posture=posture, direction=direction)
|
|||
|
|
|
|||
|
|
# Restore true extended caps — all trading this day uses extended ceiling
|
|||
|
|
self.base_max_leverage = _true_base
|
|||
|
|
self.bet_sizer.max_leverage = _true_sizer
|
|||
|
|
self.abs_max_leverage = _true_abs
|
|||
|
|
|
|||
|
|
# Record MC verdict for monitoring
|
|||
|
|
self.mc_monitor['total'] += 1
|
|||
|
|
status = self._day_mc_status
|
|||
|
|
if status == 'RED':
|
|||
|
|
self.mc_monitor['red'] += 1
|
|||
|
|
elif status == 'ORANGE':
|
|||
|
|
self.mc_monitor['orange'] += 1
|
|||
|
|
else:
|
|||
|
|
self.mc_monitor['ok'] += 1
|
|||
|
|
if self.regime_dd_halt:
|
|||
|
|
self.mc_monitor['halted'] += 1
|
|||
|
|
|
|||
|
|
def reset(self):
|
|||
|
|
super().reset()
|
|||
|
|
# NDAlphaEngine.reset() rebuilds bet_sizer from self.bet_sizer.max_leverage
|
|||
|
|
# (which at reset time = _extended_soft_cap, so it rebuilds correctly).
|
|||
|
|
# But we re-apply explicitly to be safe.
|
|||
|
|
self.bet_sizer.max_leverage = self._extended_soft_cap
|
|||
|
|
self.base_max_leverage = self._extended_soft_cap
|
|||
|
|
self.abs_max_leverage = self._extended_abs_cap
|
|||
|
|
# Reset monitoring
|
|||
|
|
self.mc_monitor = dict(red=0, orange=0, ok=0, halted=0, total=0)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── Run harness ───────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def _run(engine_factory, name, d, fw):
|
|||
|
|
"""Full 55-day run + MC monitoring + avg_leverage tracking."""
|
|||
|
|
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:
|
|||
|
|
mc_mon = getattr(eng, 'mc_monitor', {})
|
|||
|
|
return dict(name=name, roi=roi, pf=0.0, dd=0.0, wr=0.0, sharpe=0.0,
|
|||
|
|
trades=0, avg_leverage=0.0, sizing_scale_mean=1.0,
|
|||
|
|
mc_monitor=mc_mon)
|
|||
|
|
|
|||
|
|
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
|
|||
|
|
|
|||
|
|
# Realized leverage stats
|
|||
|
|
lev_vals = [t.leverage for t in tr if hasattr(t, 'leverage') and t.leverage > 0]
|
|||
|
|
avg_lev = float(np.mean(lev_vals)) if lev_vals else 0.0
|
|||
|
|
max_lev = float(np.max(lev_vals)) if lev_vals else 0.0
|
|||
|
|
lev_p90 = float(np.percentile(lev_vals, 90)) if lev_vals else 0.0
|
|||
|
|
lev_at_cap = sum(1 for v in lev_vals if v >= (eng.abs_max_leverage - 0.05))
|
|||
|
|
pct_at_cap = lev_at_cap / len(lev_vals) * 100.0 if lev_vals else 0.0
|
|||
|
|
|
|||
|
|
# proxy_B scale stats
|
|||
|
|
scale_mean = getattr(eng, 'sizing_scale_mean', 1.0)
|
|||
|
|
|
|||
|
|
# MC monitoring
|
|||
|
|
mc_mon = getattr(eng, 'mc_monitor', {})
|
|||
|
|
|
|||
|
|
return dict(
|
|||
|
|
name=name,
|
|||
|
|
roi=roi, pf=pf_val, dd=max_dd, wr=wr, sharpe=sharpe, trades=n,
|
|||
|
|
avg_leverage=avg_lev, max_leverage_realized=max_lev,
|
|||
|
|
lev_p90=lev_p90,
|
|||
|
|
pct_at_hard_cap=pct_at_cap,
|
|||
|
|
sizing_scale_mean=scale_mean,
|
|||
|
|
mc_monitor=mc_mon,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ── Main ─────────────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
t_start = time.time()
|
|||
|
|
print("=" * 76)
|
|||
|
|
print("Exp 9 — Extended Leverage Ceiling Test (fork of GOLD adaptive_beta)")
|
|||
|
|
print("=" * 76)
|
|||
|
|
print(f" GOLD ref : ROI={_GOLD_EXP9['roi']:.2f}% DD={_GOLD_EXP9['dd']:.2f}% (adaptive_beta)")
|
|||
|
|
print(f" SILVER ref: ROI={_SILVER['roi']:.2f}% DD={_SILVER['dd']:.2f}% (former gold, regression floor)")
|
|||
|
|
|
|||
|
|
ensure_jit()
|
|||
|
|
d = load_data()
|
|||
|
|
fw = load_forewarner()
|
|||
|
|
|
|||
|
|
# Config: (label, extended_soft_cap, extended_abs_cap, mc_leverage_ref)
|
|||
|
|
configs = [
|
|||
|
|
# ── Decoupled (MC sees original 5.0x reference across all runs) ──────────
|
|||
|
|
("A_5.0/6.0_mc5.0_GOLD-ref", 5.0, 6.0, 5.0), # must reproduce GOLD 96.55%
|
|||
|
|
("B_6.0/7.0_mc5.0_decoupled", 6.0, 7.0, 5.0),
|
|||
|
|
("C_7.0/8.0_mc5.0_decoupled", 7.0, 8.0, 5.0),
|
|||
|
|
("D_8.0/9.0_mc5.0_decoupled", 8.0, 9.0, 5.0),
|
|||
|
|
("E_9.0/10.0_mc5.0_decoupled", 9.0, 10.0, 5.0),
|
|||
|
|
# ── Coupled (MC sees actual new cap → tests MC's response) ───────────────
|
|||
|
|
("F_6.0/7.0_mc6.0_coupled", 6.0, 7.0, 6.0),
|
|||
|
|
("G_7.0/8.0_mc7.0_coupled", 7.0, 8.0, 7.0),
|
|||
|
|
("H_8.0/9.0_mc8.0_coupled", 8.0, 9.0, 8.0),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
# adaptive_beta proxy_B params (match GOLD exactly)
|
|||
|
|
_PROXY_KWARGS = dict(threshold=DEFAULT_THRESHOLD, alpha=DEFAULT_ALPHA,
|
|||
|
|
adaptive_beta=True, adaptive_alpha=False, adaptive_thr=False)
|
|||
|
|
|
|||
|
|
results = []
|
|||
|
|
for i, (label, soft, hard, mc_ref) in enumerate(configs):
|
|||
|
|
t0 = time.time()
|
|||
|
|
print(f"\n[{i+1}/{len(configs)}] {label} ...")
|
|||
|
|
|
|||
|
|
def _factory(kw, s=soft, h=hard, r=mc_ref):
|
|||
|
|
return ExtendedLeverageEngine(
|
|||
|
|
extended_soft_cap=s,
|
|||
|
|
extended_abs_cap=h,
|
|||
|
|
mc_leverage_ref=r,
|
|||
|
|
**_PROXY_KWARGS,
|
|||
|
|
**kw,
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
res = _run(_factory, label, d, fw)
|
|||
|
|
elapsed = time.time() - t0
|
|||
|
|
|
|||
|
|
mc = res['mc_monitor']
|
|||
|
|
mc_str = (f" MC: ok={mc.get('ok',0)} orange={mc.get('orange',0)} "
|
|||
|
|
f"red={mc.get('red',0)} halted={mc.get('halted',0)}/{mc.get('total',0)}")
|
|||
|
|
dROI = res['roi'] - _GOLD_EXP9['roi']
|
|||
|
|
dDD = res['dd'] - _GOLD_EXP9['dd']
|
|||
|
|
print(f" ROI={res['roi']:>7.2f}% (Δ{dROI:+.2f}pp) DD={res['dd']:>6.2f}% (Δ{dDD:+.2f}pp) "
|
|||
|
|
f"PF={res['pf']:.4f} Trades={res['trades']}")
|
|||
|
|
print(f" avg_lev={res['avg_leverage']:.2f}x p90={res['lev_p90']:.2f}x "
|
|||
|
|
f"max={res['max_leverage_realized']:.2f}x at_hard_cap={res['pct_at_hard_cap']:.1f}% "
|
|||
|
|
f"scale_mean={res['sizing_scale_mean']:.4f}")
|
|||
|
|
print(f"{mc_str} ({elapsed:.0f}s)")
|
|||
|
|
|
|||
|
|
results.append(res)
|
|||
|
|
|
|||
|
|
# ── Verification ─────────────────────────────────────────────────────────
|
|||
|
|
gold_ref = results[0]
|
|||
|
|
gold_ok = (abs(gold_ref['roi'] - _GOLD_EXP9['roi']) < 0.5 and
|
|||
|
|
abs(gold_ref['dd'] - _GOLD_EXP9['dd']) < 0.5 and
|
|||
|
|
abs(gold_ref['trades'] - _GOLD_EXP9['trades']) < 10)
|
|||
|
|
print(f"\n{'='*76}")
|
|||
|
|
print(f"GOLD REFERENCE VERIFICATION: {'PASS ✓' if gold_ok else 'FAIL ✗'}")
|
|||
|
|
print(f" Expected: ROI={_GOLD_EXP9['roi']:.2f}% DD={_GOLD_EXP9['dd']:.2f}% "
|
|||
|
|
f"Got: ROI={gold_ref['roi']:.2f}% DD={gold_ref['dd']:.2f}%")
|
|||
|
|
|
|||
|
|
# ── Results table ─────────────────────────────────────────────────────────
|
|||
|
|
print(f"\n{'='*76}")
|
|||
|
|
print("FULL RESULTS (vs GOLD = adaptive_beta 96.55% / 14.32%)")
|
|||
|
|
print(f"{'Config':<38} {'ROI%':>7} {'ΔROI':>6} {'DD%':>6} {'ΔDD':>6} "
|
|||
|
|
f"{'Trades':>7} {'avgLev':>7} {'p90Lev':>7} {'@cap%':>6} {'OK?':>4}")
|
|||
|
|
print('-' * 100)
|
|||
|
|
for r in results:
|
|||
|
|
dROI = r['roi'] - _GOLD_EXP9['roi']
|
|||
|
|
dDD = r['dd'] - _GOLD_EXP9['dd']
|
|||
|
|
# Pass: better than GOLD on DD, not worse than SILVER on ROI
|
|||
|
|
ok = ('Y' if r['dd'] < _GOLD_EXP9['dd'] and r['roi'] >= _SILVER['roi'] else
|
|||
|
|
'G' if r['dd'] < _SILVER['dd'] and r['roi'] >= _SILVER['roi'] else
|
|||
|
|
'N')
|
|||
|
|
print(f"{r['name']:<38} {r['roi']:>7.2f} {dROI:>+6.2f} {r['dd']:>6.2f} {dDD:>+6.2f} "
|
|||
|
|
f"{r['trades']:>7} {r['avg_leverage']:>7.2f} {r['lev_p90']:>7.2f} "
|
|||
|
|
f"{r['pct_at_hard_cap']:>6.1f} {ok:>4}")
|
|||
|
|
print(" OK=Y: beats GOLD on DD + above SILVER ROI | OK=G: beats SILVER on both | OK=N: fail")
|
|||
|
|
|
|||
|
|
# ── MC interaction table ──────────────────────────────────────────────────
|
|||
|
|
print(f"\n{'='*76}")
|
|||
|
|
print("MC-FOREWARNER INTERACTION (per-day breakdown)")
|
|||
|
|
print(f"{'Config':<38} {'OK':>5} {'ORG':>5} {'RED':>5} {'HALT':>5} {'TOTAL':>6} {'halt%':>7}")
|
|||
|
|
print('-' * 75)
|
|||
|
|
for r in results:
|
|||
|
|
mc = r['mc_monitor']
|
|||
|
|
total = mc.get('total', 0)
|
|||
|
|
halt = mc.get('halted', 0)
|
|||
|
|
halt_pct = halt / total * 100.0 if total else 0.0
|
|||
|
|
print(f"{r['name']:<38} {mc.get('ok',0):>5} {mc.get('orange',0):>5} "
|
|||
|
|
f"{mc.get('red',0):>5} {halt:>5} {total:>6} {halt_pct:>7.1f}%")
|
|||
|
|
|
|||
|
|
# ── Decoupled vs Coupled delta at each leverage level ────────────────────
|
|||
|
|
print(f"\n{'='*76}")
|
|||
|
|
print("MC COUPLING EFFECT (decoupled vs coupled at same leverage level)")
|
|||
|
|
pairs = [
|
|||
|
|
("B_6.0/7.0", "F_6.0/7.0"),
|
|||
|
|
("C_7.0/8.0", "G_7.0/8.0"),
|
|||
|
|
("D_8.0/9.0", "H_8.0/9.0"),
|
|||
|
|
]
|
|||
|
|
rmap = {r['name'][:10]: r for r in results}
|
|||
|
|
for dec_label, coup_label in pairs:
|
|||
|
|
dec = next((r for r in results if dec_label in r['name']), None)
|
|||
|
|
cop = next((r for r in results if coup_label in r['name']), None)
|
|||
|
|
if dec and cop:
|
|||
|
|
halt_dec = dec['mc_monitor'].get('halted', 0)
|
|||
|
|
halt_cop = cop['mc_monitor'].get('halted', 0)
|
|||
|
|
print(f" {dec_label}: decoupled ROI={dec['roi']:.2f}% DD={dec['dd']:.2f}% halted={halt_dec}d")
|
|||
|
|
print(f" {coup_label}: coupled ROI={cop['roi']:.2f}% DD={cop['dd']:.2f}% halted={halt_cop}d")
|
|||
|
|
print(f" MC coupling cost: ΔROI={cop['roi']-dec['roi']:+.2f}pp ΔDD={cop['dd']-dec['dd']:+.2f}pp "
|
|||
|
|
f"Δhalted={halt_cop-halt_dec:+d}d")
|
|||
|
|
print()
|
|||
|
|
|
|||
|
|
# ── Best config summary ───────────────────────────────────────────────────
|
|||
|
|
print(f"{'='*76}")
|
|||
|
|
decoupled = [r for r in results if 'decoupled' in r['name'] or 'GOLD' in r['name']]
|
|||
|
|
best_roi = max(decoupled, key=lambda r: r['roi'])
|
|||
|
|
best_dd = min(decoupled, key=lambda r: r['dd'])
|
|||
|
|
best_combined = max(decoupled, key=lambda r: r['roi'] - r['dd']) # ROI−DD as proxy
|
|||
|
|
|
|||
|
|
print(f"BEST (decoupled only — pure leverage effect):")
|
|||
|
|
print(f" Best ROI: {best_roi['name']} ROI={best_roi['roi']:.2f}% DD={best_roi['dd']:.2f}%")
|
|||
|
|
print(f" Best DD: {best_dd['name']} ROI={best_dd['roi']:.2f}% DD={best_dd['dd']:.2f}%")
|
|||
|
|
print(f" Best R−D: {best_combined['name']} ROI={best_combined['roi']:.2f}% DD={best_combined['dd']:.2f}%")
|
|||
|
|
|
|||
|
|
# ── Log ──────────────────────────────────────────────────────────────────
|
|||
|
|
outfile = _HERE / "exp9_leverage_ceiling_results.json"
|
|||
|
|
log_results(results, outfile, gold=_GOLD_EXP9, meta={
|
|||
|
|
"exp": "exp9",
|
|||
|
|
"question": "Does adaptive_beta DD headroom permit higher leverage ceiling?",
|
|||
|
|
"silver": _SILVER,
|
|||
|
|
"total_elapsed_s": round(time.time() - t_start, 1),
|
|||
|
|
"gold_ref_ok": gold_ok,
|
|||
|
|
"note_mc": (
|
|||
|
|
"Decoupled runs (mc_ref=5.0): MC assesses at original 5x reference — "
|
|||
|
|
"pure leverage cap test. Coupled runs: MC sees actual new cap — tests MC response."
|
|||
|
|
),
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
total = time.time() - t_start
|
|||
|
|
print(f"\nTotal elapsed: {total / 60:.1f} min")
|
|||
|
|
print("Done.")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|