research(intraday): de-bias del lead ERM (filone B) — falso positivo + gate selection-on-holdout
L'analisi di robustezza affonda lo "earns_slot=True" di ERM: era prodotto da selezione-sull'hold-out + coda 2026 + multiple-testing non corretto. A) deflated-Sharpe FAIL: 0.00 (tutti 122 trial) / 0.16 (no-TOD) / 0.24 (solo-ERM) << 0.95 B) selezione in-sample-only -> ALTRA cella (long-flat, corr->TP01 0.53) = NEUTRAL, no slot C) ensemble del plateau (no cherry-pick) -> ADDS ma robust_oos=False -> no slot D) uplift FULL solo +0.10, negativo 2021/2022; uplift HOLD +0.30 concentrato nel 2026 => ERM SCARTATO come sleeve. Conferma ennesima del soffitto BTC/ETH-direzionale ~1.3. Lezione CODIFICATA in altlib (LESSON 4, test in tests/test_harness_realism.py): - deflated_sharpe() Bailey & Lopez de Prado, PASS >= 0.95 - select_cell_insample() scelta cella col solo Sharpe pre-HOLDOUT (no peeking) - study_family_honest() gate combinato: earns_slot[cella in-sample] AND DSR>=0.95 Regola: una strategia direzionale grid-searched si giudica con study_family_honest, non chiamando study_marginal sulla cella a max hold-out. Verificato end-to-end su ERM (earns_slot_honest=False). Chiude il punto cieco gemello di CC01. Diario aggiornato (verdetto downgrade), CLAUDE.md aggiornato. Test 119/119 verdi. Nessun impatto live (branch separato). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -29,12 +29,14 @@ from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from scipy.stats import norm
|
||||
|
||||
# --- make `from src...` work no matter where the agent's script lives -------
|
||||
_ROOT = Path(__file__).resolve().parents[3]
|
||||
@@ -670,6 +672,94 @@ def causality_ok(target_fn, tf: str = "1h", assets=CERTIFIED,
|
||||
reason=("length-mismatch on prefix" if bad else None))
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# SELECTION-ON-HOLDOUT GATE — codified 2026-06-29 from filone B (intraday ERM).
|
||||
#
|
||||
# LESSON 3: intraday_regime.py picked its "winner" cell by MAX hold-out Sharpe over a ~60-cell
|
||||
# grid, then ran study_marginal on THAT cell -> earns_slot=True. But the slot was an artifact of
|
||||
# SELECTING THE CELL ON THE HOLD-OUT: picking the cell IN-SAMPLE-ONLY (no peeking) lands on a
|
||||
# DIFFERENT, TP01-correlated cell that scores NEUTRAL, and the standalone Sharpe deflates to
|
||||
# DSR~0.0-0.24 over the trials searched. study_marginal alone can't catch this — it judges ONE
|
||||
# stream and never sees how the cell was chosen. The fix is two-fold and lives here:
|
||||
# (1) choose the cell IN-SAMPLE-ONLY (or walk-forward) BEFORE scoring the marginal, and
|
||||
# (2) DEFLATE the standalone Sharpe for the number of cells/families searched.
|
||||
# Twin of the CC01 ("implausible Sharpe -> hidden risk") and alt-sweep ("hold-out-fitting") blind
|
||||
# spots, in its "selection-on-holdout" form.
|
||||
# ===========================================================================
|
||||
def deflated_sharpe(sr_ann, all_sr_ann, daily_ret, dpy: float = 365.25):
|
||||
"""Deflated Sharpe Ratio (Bailey & Lopez de Prado): P(true Sharpe > the MAX Sharpe expected
|
||||
under the null of N independent trials). Penalizes multiple-testing — a standalone Sharpe ~1
|
||||
over a 100+ cell grid is routinely NOT significant once deflated. sr_ann = annualized Sharpe
|
||||
of the CHOSEN config; all_sr_ann = the Sharpe of EVERY cell searched; daily_ret = the chosen
|
||||
config's daily returns (for skew/kurt/T). Returns (DSR, expected_null_max_sharpe_ann);
|
||||
PASS if DSR >= 0.95."""
|
||||
r = np.asarray(pd.Series(daily_ret).dropna().values, float)
|
||||
T = len(r)
|
||||
if T < 30 or np.std(r) == 0:
|
||||
return float("nan"), float("nan")
|
||||
sr = sr_ann / math.sqrt(dpy)
|
||||
trials = np.asarray([s / math.sqrt(dpy) for s in all_sr_ann if np.isfinite(s)], float)
|
||||
N = max(len(trials), 2)
|
||||
var_tr = float(np.var(trials, ddof=1)) if N > 1 else 0.0
|
||||
emc = 0.5772156649
|
||||
z1 = norm.ppf(1 - 1.0 / N)
|
||||
z2 = norm.ppf(1 - 1.0 / (N * math.e))
|
||||
sr0 = math.sqrt(var_tr) * ((1 - emc) * z1 + emc * z2)
|
||||
sk = float(pd.Series(r).skew())
|
||||
ku = float(pd.Series(r).kurt()) + 3.0 # pandas kurt = excess
|
||||
den = math.sqrt(max(1e-9, 1 - sk * sr + (ku - 1) / 4.0 * sr ** 2))
|
||||
dsr = float(norm.cdf((sr - sr0) * math.sqrt(T - 1) / den))
|
||||
return dsr, sr0 * math.sqrt(dpy)
|
||||
|
||||
|
||||
def select_cell_insample(factory, grid, tfs, fee_side: float = FEE_SIDE) -> dict:
|
||||
"""Pick a config WITHOUT looking at the hold-out: rank grid cells by IN-SAMPLE (pre-HOLDOUT)
|
||||
standalone Sharpe of the 50/50 candidate. `factory(tf=..., **params)` -> target_fn; each grid
|
||||
item is a dict of factory kwargs (besides tf). Returns the in-sample-best cell, all rows
|
||||
(sorted), and EVERY cell's FULL Sharpe (for deflated_sharpe). This is the honest replacement
|
||||
for picking the max-hold-out cell."""
|
||||
rows = []
|
||||
for tf in tfs:
|
||||
for params in grid:
|
||||
try:
|
||||
daily = candidate_daily(factory(tf=tf, **params), tf=tf, fee_side=fee_side)
|
||||
except Exception:
|
||||
continue
|
||||
ins = daily[daily.index < HOLDOUT]
|
||||
is_sh = _sh(ins) if len(ins) > 60 else float("nan")
|
||||
rows.append(dict(tf=tf, params=params, insample_sharpe=round(is_sh, 3),
|
||||
full_sharpe=round(_sh(daily), 3)))
|
||||
valid = [r for r in rows if np.isfinite(r["insample_sharpe"])]
|
||||
chosen = max(valid, key=lambda r: r["insample_sharpe"]) if valid else None
|
||||
return dict(chosen=chosen,
|
||||
rows=sorted(valid, key=lambda r: r["insample_sharpe"], reverse=True),
|
||||
all_full_sharpe=[r["full_sharpe"] for r in rows])
|
||||
|
||||
|
||||
def study_family_honest(name: str, factory, grid, tfs, fee_side: float = FEE_SIDE,
|
||||
dsr_min: float = 0.95) -> dict:
|
||||
"""HARDENED family gate. A grid-searched directional candidate earns a slot ONLY if, picking
|
||||
the cell IN-SAMPLE-ONLY (no hold-out peeking), it STILL earns_slot via study_marginal AND its
|
||||
standalone Sharpe survives deflation for the WHOLE grid searched. Use this INSTEAD of
|
||||
cherry-picking the max-hold cell and calling study_marginal on it."""
|
||||
sel = select_cell_insample(factory, grid, tfs, fee_side=fee_side)
|
||||
ch = sel["chosen"]
|
||||
if ch is None:
|
||||
return dict(name=name, chosen=None, earns_slot_honest=False,
|
||||
reason="no valid in-sample cell")
|
||||
fn = factory(tf=ch["tf"], **ch["params"])
|
||||
sm = study_marginal(f"{name} ISpick {ch['params']}", fn, tf=ch["tf"], fee_side=fee_side)
|
||||
daily = candidate_daily(fn, tf=ch["tf"], fee_side=fee_side)
|
||||
dsr, sr0 = deflated_sharpe(_sh(daily), sel["all_full_sharpe"], daily)
|
||||
dsr_pass = bool(np.isfinite(dsr) and dsr >= dsr_min)
|
||||
return dict(name=name, n_cells=len(sel["all_full_sharpe"]), chosen=ch, rows=sel["rows"],
|
||||
marginal=sm, earns_slot_marginal=bool(sm["earns_slot"]),
|
||||
deflated_sharpe=round(dsr, 3) if np.isfinite(dsr) else None,
|
||||
expected_null_max=round(sr0, 3) if np.isfinite(sr0) else None,
|
||||
dsr_pass=dsr_pass,
|
||||
earns_slot_honest=bool(sm["earns_slot"] and dsr_pass))
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# DRIVERS — run a hypothesis across both assets, several TFs, with a fee sweep.
|
||||
# ===========================================================================
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
"""intraday_regime_analysis.py — ANALISI DI ROBUSTEZZA del LEAD ERM (filone B) — 2026-06-29.
|
||||
|
||||
Il lead di B (ERM 8h L=2.0 thr=0.35 L/S) fa earns_slot=True, ma con 2 caveat NON quantificati
|
||||
dallo script di scoperta `intraday_regime.py`:
|
||||
(1) il VINCITORE e' selezionato per min_hold MASSIMO su ~60 celle -> selezione-sull'hold-out;
|
||||
(2) il plateau hold-out e' a UNA SOLA RIGA (positivo solo a L~2.0; L>=2.5 va negativo sull'hold).
|
||||
Insieme = rischio multiple-testing / overfit della finestra recente, mai deflazionato (a
|
||||
differenza del filone C che ha il deflated-Sharpe).
|
||||
|
||||
Questo script attacca esattamente quei nodi, SENZA toccare il live (read-only, branch separato):
|
||||
|
||||
A) DEFLATED SHARPE (Bailey & Lopez de Prado) del vincitore vs TUTTI i trial realmente
|
||||
cercati (ERM+VEM+VBR+TOD, tutte le celle/TF). Se DSR << 0.95 lo Sharpe non e' significativo
|
||||
dopo la correzione per multiple-testing.
|
||||
|
||||
B) SELEZIONE IN-SAMPLE-ONLY: ri-scelgo la cella ERM usando SOLO lo Sharpe PRE-2025 (mai
|
||||
l'hold-out), poi ne valuto earns_slot sull'intera storia. Se una cella scelta SENZA vedere
|
||||
l'hold-out continua ad ADDS, l'edge non e' hold-out-mined.
|
||||
|
||||
C) ENSEMBLE DEL PLATEAU: invece della singola cella migliore, media i pesi su tutto il
|
||||
plateau ERM 8h (L x thr) -> un candidato unico "non-cherry-picked" -> earns_slot. Se la
|
||||
famiglia regge senza scegliere L, il caveat (1)+(2) si attenua.
|
||||
|
||||
D) DOVE VIVE L'EDGE: Sharpe per-anno standalone + uplift per-anno del blend 3-way
|
||||
(TP01+SKH+ERM) vs 2-way (TP01+SKH), e corr(ERM,SKH) per-anno (e' un hedge-di-SKH?).
|
||||
|
||||
Esecuzione: uv run python scripts/research/intraday_regime_analysis.py
|
||||
Idempotente, niente scritture su disco (solo report a stdout).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
sys.path.insert(0, "/opt/docker/PythagorasGoal/scripts/research/alt")
|
||||
import altlib as al # noqa: E402
|
||||
from intraday_regime import make_erm, make_vem, make_vbr, make_tod, _skh_daily # noqa: E402
|
||||
|
||||
DPY = 365.25
|
||||
HOLD = al.HOLDOUT
|
||||
ASSETS = ("BTC", "ETH")
|
||||
SCREEN_TFS = ("1h", "4h", "6h", "8h", "12h")
|
||||
|
||||
WIN = dict(tf="8h", L_days=2.0, thr=0.35, long_flat=False) # il lead di B
|
||||
|
||||
deflated_sharpe = al.deflated_sharpe # gate canonico (codificato in altlib da questo filone)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
def all_trials():
|
||||
"""Ricostruisce la griglia COMPLETA realmente cercata in intraday_regime.py, ritorna una
|
||||
lista di (tag, factory, tf, params) per pesare il multiple-testing onestamente."""
|
||||
out = []
|
||||
erm_grid = [dict(L_days=L, thr=t, long_flat=lf)
|
||||
for L in (1.0, 2.0, 3.0) for t in (0.35, 0.50) for lf in (False, True)]
|
||||
for tf in SCREEN_TFS:
|
||||
for p in erm_grid:
|
||||
out.append(("ERM", make_erm, tf, p))
|
||||
# + il plateau fine a 8h (L x thr, lf=False) — anche quelle sono celle testate
|
||||
for L in (1.5, 2.0, 2.5, 3.0):
|
||||
for t in (0.30, 0.35, 0.40, 0.45, 0.50):
|
||||
out.append(("ERM-plat", make_erm, "8h", dict(L_days=L, thr=t, long_flat=False)))
|
||||
vem_grid = [dict(Lmom_days=lm, Lshort_days=2.0, Llong_days=10.0, long_flat=lf)
|
||||
for lm in (1.0, 3.0) for lf in (False, True)]
|
||||
for tf in ("4h", "6h", "8h", "12h"):
|
||||
for p in vem_grid:
|
||||
out.append(("VEM", make_vem, tf, p))
|
||||
vbr_grid = [dict(k=k, atr_win=14, long_flat=lf) for k in (0.5, 1.0, 1.5) for lf in (False, True)]
|
||||
for tf in ("4h", "6h", "8h", "12h"):
|
||||
for p in vbr_grid:
|
||||
out.append(("VBR", make_vbr, tf, p))
|
||||
for lf in (False, True):
|
||||
out.append(("TOD", make_tod, "1h", dict(long_flat=lf)))
|
||||
return out
|
||||
|
||||
|
||||
def cand_full_is_sharpe(factory, tf, params):
|
||||
"""(daily, full Sharpe, in-sample<2025 Sharpe) del candidato 50/50 di quella cella."""
|
||||
fn = factory(tf=tf, **params)
|
||||
daily = al.candidate_daily(fn, tf=tf)
|
||||
full = al._sh(daily)
|
||||
ins = al._sh(daily[daily.index < HOLD]) if (daily.index < HOLD).sum() > 60 else float("nan")
|
||||
return daily, full, ins
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
def part_A_deflated():
|
||||
print("=" * 78)
|
||||
print("A) DEFLATED SHARPE del vincitore vs TUTTI i trial cercati (multiple-testing)")
|
||||
print("=" * 78)
|
||||
trials = all_trials()
|
||||
sr_all, sr_no_tod, sr_erm = [], [], []
|
||||
win_daily = win_full = None
|
||||
for tag, factory, tf, params in trials:
|
||||
try:
|
||||
daily, full, _ = cand_full_is_sharpe(factory, tf, params)
|
||||
except Exception:
|
||||
continue
|
||||
sr_all.append(full)
|
||||
if tag != "TOD":
|
||||
sr_no_tod.append(full)
|
||||
if tag.startswith("ERM"):
|
||||
sr_erm.append(full)
|
||||
if tag == "ERM" and tf == WIN["tf"] and params.get("L_days") == WIN["L_days"] \
|
||||
and params.get("thr") == WIN["thr"] and params.get("long_flat") is False:
|
||||
win_daily, win_full = daily, full
|
||||
sr_arr = np.array([s for s in sr_all if np.isfinite(s)])
|
||||
print(f" N trial finiti : {len(sr_arr)}")
|
||||
print(f" Sharpe winner (50/50) : {win_full:+.3f}")
|
||||
print(f" Sharpe trial: mean {sr_arr.mean():+.2f} std {sr_arr.std():.2f} "
|
||||
f"max {sr_arr.max():+.2f} >0: {int((sr_arr > 0).sum())}/{len(sr_arr)}")
|
||||
dsr = None
|
||||
for label, pool in (("TUTTI 122", sr_all), ("no-TOD", sr_no_tod), ("solo-ERM", sr_erm)):
|
||||
d, sr0 = deflated_sharpe(win_full, pool, win_daily)
|
||||
n = int(np.isfinite(np.array(pool)).sum())
|
||||
print(f" DSR [{label:>9} N={n:>3}]: {d:.3f} (Sh-max null {sr0:+.2f}) -> "
|
||||
f"{'PASS' if d >= 0.95 else 'FAIL'}")
|
||||
if label == "TUTTI 122":
|
||||
dsr = d
|
||||
return dsr, win_full, None
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
def part_B_insample_pick():
|
||||
print("\n" + "=" * 78)
|
||||
print("B) SELEZIONE IN-SAMPLE-ONLY (scelgo la cella ERM solo con Sharpe PRE-2025)")
|
||||
print("=" * 78)
|
||||
erm_grid = [dict(L_days=L, thr=t, long_flat=lf)
|
||||
for L in (1.0, 1.5, 2.0, 2.5, 3.0) for t in (0.30, 0.35, 0.40, 0.50)
|
||||
for lf in (False, True)]
|
||||
rows = []
|
||||
for tf in SCREEN_TFS:
|
||||
for p in erm_grid:
|
||||
try:
|
||||
_, full, ins = cand_full_is_sharpe(make_erm, tf, p)
|
||||
except Exception:
|
||||
continue
|
||||
rows.append((ins, full, tf, p))
|
||||
rows = [r for r in rows if np.isfinite(r[0])]
|
||||
rows.sort(key=lambda r: r[0], reverse=True) # ordina per Sharpe IN-SAMPLE (no hold-out)
|
||||
print(f" Top 5 celle per Sharpe IN-SAMPLE (<2025):")
|
||||
for ins, full, tf, p in rows[:5]:
|
||||
print(f" IS {ins:+.2f} FULL {full:+.2f} tf={tf:>3} {p}")
|
||||
ins, full, tf, p = rows[0]
|
||||
print(f"\n -> cella scelta SENZA vedere l'hold-out: tf={tf} {p}")
|
||||
sm = al.study_marginal(f"ERM-ISpick {p}", make_erm(tf=tf, **p), tf=tf)
|
||||
m = sm["marginal"]
|
||||
print(f" earns_slot={sm['earns_slot']} marginal={m['marginal_verdict']} "
|
||||
f"abs={sm['absolute']['verdict']['grade']}")
|
||||
print(f" corr->TP01 {m['corr_full']} has_insample_edge={m['has_insample_edge']} "
|
||||
f"is_hedge={m['is_hedge']} robust_oos={m['robust_oos']}")
|
||||
print(f" blend w25: full uplift {m['blends']['w25']['uplift_full']:+.3f} "
|
||||
f"hold uplift {m['blends']['w25']['uplift_hold']:+.3f}")
|
||||
same = (tf == WIN["tf"] and abs(p["L_days"] - WIN["L_days"]) < 1e-9
|
||||
and abs(p["thr"] - WIN["thr"]) < 1e-9 and p["long_flat"] is False)
|
||||
print(f" coincide col vincitore max-hold? {same}")
|
||||
return sm["earns_slot"], (tf, p)
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
def ensemble_target(tf, cells):
|
||||
"""Media (equal-weight) dei pesi vol-targeted su piu' celle ERM -> un unico stream per asset.
|
||||
Ritorna un target_fn(df) che ricostruisce l'ensemble per quel df."""
|
||||
def fn(df):
|
||||
ws = [make_erm(tf=tf, **c)(df) for c in cells]
|
||||
return np.nanmean(np.vstack(ws), axis=0)
|
||||
return fn
|
||||
|
||||
|
||||
def part_C_plateau_ensemble():
|
||||
print("\n" + "=" * 78)
|
||||
print("C) ENSEMBLE DEL PLATEAU ERM 8h (media celle L x thr, NIENTE cherry-pick)")
|
||||
print("=" * 78)
|
||||
cells = [dict(L_days=L, thr=t, long_flat=False)
|
||||
for L in (1.5, 2.0, 2.5, 3.0) for t in (0.30, 0.35, 0.40, 0.45, 0.50)]
|
||||
fn = ensemble_target("8h", cells)
|
||||
print(f" celle nell'ensemble: {len(cells)} (L 1.5-3.0 x thr 0.30-0.50, lf=False)")
|
||||
caus = al.causality_ok(fn, tf="8h")
|
||||
print(f" causale: ok={caus['ok']} max_tail_diff={caus['max_tail_diff']}")
|
||||
sm = al.study_marginal("ERM-plateau-ens", fn, tf="8h")
|
||||
m = sm["marginal"]
|
||||
print(f" earns_slot={sm['earns_slot']} marginal={m['marginal_verdict']} "
|
||||
f"abs={sm['absolute']['verdict']['grade']}")
|
||||
print(f" standalone full {m['cand_full_sharpe']} hold {m['cand_hold_sharpe']} "
|
||||
f"in-sample {m.get('cand_insample_sharpe')}")
|
||||
print(f" corr->TP01 {m['corr_full']} has_insample_edge={m['has_insample_edge']} "
|
||||
f"is_hedge={m['is_hedge']} robust_oos={m['robust_oos']}")
|
||||
print(f" blend w25: full {m['blends']['w25']['full']} (uplift "
|
||||
f"{m['blends']['w25']['uplift_full']:+.3f}) hold {m['blends']['w25']['hold']} "
|
||||
f"(uplift {m['blends']['w25']['uplift_hold']:+.3f})")
|
||||
print(f" multicut: {m['multicut_uplift']}")
|
||||
return sm["earns_slot"]
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
def part_D_where_edge():
|
||||
print("\n" + "=" * 78)
|
||||
print("D) DOVE VIVE L'EDGE — per-anno standalone + uplift 3-way vs 2-way + corr(ERM,SKH)")
|
||||
print("=" * 78)
|
||||
fn = make_erm(**WIN)
|
||||
cand = al.candidate_daily(fn, tf=WIN["tf"])
|
||||
tp = al.tp01_baseline_daily()
|
||||
skh = _skh_daily()
|
||||
J = pd.concat({"T": tp, "S": skh, "C": cand}, axis=1, join="inner").dropna()
|
||||
two = 0.75 * J["T"] + 0.25 * J["S"]
|
||||
three = 0.60 * J["T"] + 0.25 * J["S"] + 0.15 * J["C"]
|
||||
print(f" {'anno':>5} {'ERM Sh':>7} {'TP+SKH':>7} {'+ERM':>7} {'Δuplift':>8} "
|
||||
f"{'corr(ERM,SKH)':>14} {'corr(ERM,TP)':>13}")
|
||||
for y in sorted(set(J.index.year)):
|
||||
sub = J[J.index.year == y]
|
||||
if len(sub) < 40:
|
||||
continue
|
||||
s2 = 0.75 * sub["T"] + 0.25 * sub["S"]
|
||||
s3 = 0.60 * sub["T"] + 0.25 * sub["S"] + 0.15 * sub["C"]
|
||||
print(f" {y:>5} {al._sh(sub['C']):>+7.2f} {al._sh(s2):>+7.2f} {al._sh(s3):>+7.2f} "
|
||||
f"{al._sh(s3) - al._sh(s2):>+8.2f} {sub['C'].corr(sub['S']):>+14.2f} "
|
||||
f"{sub['C'].corr(sub['T']):>+13.2f}")
|
||||
print(f" {'FULL':>5} {al._sh(J['C']):>+7.2f} {al._sh(two):>+7.2f} {al._sh(three):>+7.2f} "
|
||||
f"{al._sh(three) - al._sh(two):>+8.2f} {J['C'].corr(J['S']):>+14.2f} "
|
||||
f"{J['C'].corr(J['T']):>+13.2f}")
|
||||
JH = J[J.index >= HOLD]
|
||||
h2 = 0.75 * JH["T"] + 0.25 * JH["S"]
|
||||
h3 = 0.60 * JH["T"] + 0.25 * JH["S"] + 0.15 * JH["C"]
|
||||
print(f" {'HOLD':>5} {al._sh(JH['C']):>+7.2f} {al._sh(h2):>+7.2f} {al._sh(h3):>+7.2f} "
|
||||
f"{al._sh(h3) - al._sh(h2):>+8.2f} {JH['C'].corr(JH['S']):>+14.2f} "
|
||||
f"{JH['C'].corr(JH['T']):>+13.2f}")
|
||||
|
||||
|
||||
def part_E_codified_gate():
|
||||
"""Validazione END-TO-END del gate appena codificato in altlib: study_family_honest sulla
|
||||
famiglia ERM deve dare earns_slot_honest=False (sceglie in-sample-only + deflaziona)."""
|
||||
print("\n" + "=" * 78)
|
||||
print("E) GATE CODIFICATO (al.study_family_honest) sulla famiglia ERM — deve bocciare")
|
||||
print("=" * 78)
|
||||
grid = [dict(L_days=L, thr=t, long_flat=lf)
|
||||
for L in (1.0, 1.5, 2.0, 2.5, 3.0) for t in (0.30, 0.35, 0.40, 0.50)
|
||||
for lf in (False, True)]
|
||||
rep = al.study_family_honest("ERM", make_erm, grid, SCREEN_TFS)
|
||||
ch = rep["chosen"]
|
||||
print(f" n_celle={rep['n_cells']} cella in-sample-best: tf={ch['tf']} {ch['params']}")
|
||||
print(f" earns_slot_marginal={rep['earns_slot_marginal']} "
|
||||
f"deflated_sharpe={rep['deflated_sharpe']} (dsr_pass={rep['dsr_pass']})")
|
||||
print(f" => earns_slot_HONEST = {rep['earns_slot_honest']} "
|
||||
f"(atteso False: slot bocciato dal gate)")
|
||||
return rep["earns_slot_honest"]
|
||||
|
||||
|
||||
def main():
|
||||
print("ANALISI ROBUSTEZZA LEAD ERM (filone B) — read-only, nessun impatto live\n")
|
||||
dsr, win_full, sr0 = part_A_deflated()
|
||||
es_is, is_cell = part_B_insample_pick()
|
||||
es_ens = part_C_plateau_ensemble()
|
||||
part_D_where_edge()
|
||||
es_honest = part_E_codified_gate()
|
||||
print("\n" + "=" * 78)
|
||||
print("SINTESI ANALISI B")
|
||||
print("=" * 78)
|
||||
print(f" A) deflated-Sharpe winner = {dsr:.3f} ({'PASS' if dsr >= 0.95 else 'FAIL'} vs 0.95)")
|
||||
print(f" B) cella scelta in-sample-only earns_slot = {es_is} (cella {is_cell})")
|
||||
print(f" C) ensemble del plateau earns_slot = {es_ens}")
|
||||
print(f" E) gate codificato earns_slot_honest = {es_honest}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user