research(sweep): 5 thread paralleli — 0 nuovi sleeve, STATARB-RESID LEAD ortogonale+eseguibile
Ricerca onesta su aree inesplorate (harness altlib+xsec_v2_nonmom, tutti i gate incl. study_family_honest anti-selection-on-holdout). Branch main, nessun impatto live, test 143/143. 1 XSEC low-risk cousins (MAX/idio-vol/Amihud) -> 1 LEAD (IVOL), STAT-MODE, DSR 0.37<0.95 2 XSEC momentum-structure vs XS01 -> tutto REDUNDANT (sostituire XS01 distrugge hold) 3 Meta-allocazione dinamica (4 sleeve) -> pesi fissi vincono (gia quasi risk-parity) 4 Segnali ortogonali ETH/BTC (2 gambe) -> STATARB-RESID + DVOLSPREAD LEAD 5 1-gamba a segnale (MACD/RSI/Supertrend/...) -> 0/12 earns_slot (trend=TP01, MR morta, hedge) LEAD principale STATARB-RESID (mean-rev residuo ETH-b*BTC, OLS rolling, 2 gambe): primo stream INSIEME ortogonale (corr->book 0.027, beta-mkt 0.013) ED eseguibile a $600 (haircut ~0, NON STAT-MODE) -> cadono i 2 muri di XS01/opzioni. Resta solo il muro dell'edge (Sharpe 0.84, DSR 0.929 same-sign <0.95). Causalita+fee verificate dal coordinatore. Forward-monitor, non sleeve. Soffitto direzionale ~1.3 riconfermato. Diario 2026-06-29-strategy-search-5threads.md, CLAUDE.md agg. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,387 @@
|
||||
"""XSEC v3 — fattori cross-sectional "low-risk cousins" su ~51 alt Hyperliquid (1d, STAT-MODE).
|
||||
|
||||
TESI (filone C, terza ondata). I primi due filoni cross-sectional hanno coperto: momentum (XS01,
|
||||
sleeve attivo), reversal/idio-reversal, TOTAL-low-vol (LOWVOL) e betting-against-beta (BAB).
|
||||
Restano TRE anomalie "cugine del low-risk", documentate in equity ma MAI provate qui, che POTREBBERO
|
||||
diversificare il portafoglio essendo strutturalmente diverse dal momentum e dal total-vol:
|
||||
|
||||
1. MAX (lottery-demand, Bali-Cakici-Whitelaw 2011). Gli asset col MASSIMO rendimento giornaliero
|
||||
piu' alto nelle ultime B sedute attraggono domanda "da lotteria" e poi sottoperformano.
|
||||
SHORT high-max / LONG low-max -> score = -max(daily_ret over B).
|
||||
Diverso dal momentum (e' la coda destra recente, non il trend) e dal total-vol (un singolo
|
||||
estremo, non la dispersione).
|
||||
|
||||
2. IVOL (idiosyncratic vol, Ang-Hodrick-Xing-Zhang 2006). SHORT alta vol del RESIDUO
|
||||
(dopo aver tolto beta*mercato su finestra B) / LONG bassa. score = -ivol_residuo.
|
||||
DIVERSO da LOWVOL (gia' provato in v2) che usa la vol TOTALE: qui si toglie prima il fattore
|
||||
di mercato, isolando il rischio idiosincratico (ortogonale a BAB, che e' il beta sistematico).
|
||||
|
||||
3. AMIHUD (illiquidity, Amihud 2002). Ranking su |ret|/dollar_volume medio su B (dollar_volume =
|
||||
volume_coin * close, perche' il volume HL e' in coin -> va dollarizzato per confrontare asset).
|
||||
Tesi standard: premio di illiquidita' -> LONG illiquido / SHORT liquido. In crypto il segno e'
|
||||
incerto (flight-to-quality verso i major liquidi), quindi si provano ENTRAMBI i segni e si tiene
|
||||
quello con tesi economica + edge: AMIHUD_ILLIQ (score=+amihud) vs AMIHUD_LIQ (score=-amihud).
|
||||
|
||||
GATE OBBLIGATORI (CLAUDE.md + parita' con xsec_v2_nonmom):
|
||||
- Griglia B in {20,30,60} x H in {5,10} x k in {5,8}, su ENTRAMBI gli universi (51-all, 19-major).
|
||||
- CAUSALE: score a close[i], peso tenuto in i+1 (engine shifta W[i-1]*dret[i]); vol=0 gata.
|
||||
Verifica prefix-consistency (xv.causality_prefix_check) sul best: ok=True, max_tail_diff~0.
|
||||
- NETTO fee 0.10% RT su ogni gamba a ogni ribilancio (engine) + turnover/anno riportato.
|
||||
- DEFLATED Sharpe (Bailey-Lopez de Prado) sul best, con TUTTI gli Sharpe FULL testati come trial
|
||||
(multiple-testing): serve DSR>0.95 per un claim forte.
|
||||
- corr vs XS01 e vs TP01 (vogliamo |corrXS|<0.6 per diversificare).
|
||||
- Uplift del portafoglio 4->5 sleeve a 10% e 15% (active_sleeves, non modificati).
|
||||
- Per-anno (breadth) + HOLD-OUT (2025-01-01+).
|
||||
- ANTI-selection-on-holdout: il best e' scelto per HOLD massimo; si riporta ANCHE il best scelto
|
||||
per Sharpe IN-SAMPLE (<2025) e si verifica che il deflated-Sharpe (che usa il FULL, in-sample
|
||||
incluso) regga comunque.
|
||||
|
||||
CAVEAT immutabili: storia ~2.5 anni (deflated-Sharpe + multiple-testing), book a molte gambe NON
|
||||
eseguibile a $600 -> STAT-MODE / forward-monitor, MAI deploy. Nessuno sleeve registrato.
|
||||
|
||||
uv run python scripts/research/xsec_v3_lowrisk.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
sys.path.insert(0, str(PROJECT_ROOT / "scripts" / "research"))
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
import xsec_v2_nonmom as xv # HARNESS collaudato (engine, metriche, statistica, portafoglio)
|
||||
|
||||
HOLDOUT = xv.HOLDOUT
|
||||
metrics = xv.metrics
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# SCORE BUILDERS — "low-risk cousins". Tutti CAUSALI (dati <= i). Convenzione
|
||||
# engine: long ALTO score / short BASSO score (vol=0 gata automaticamente).
|
||||
# ===========================================================================
|
||||
def make_max(PX, B):
|
||||
"""MAX / lottery-demand: massimo rendimento giornaliero nelle ultime B sedute.
|
||||
score = -max -> long low-max / short high-max (la 'lotteria' sottoperforma)."""
|
||||
px, n, A, DR, m = xv._precompute(PX)
|
||||
ROLLMAX = DR.rolling(B, min_periods=int(0.6 * B)).max().values
|
||||
def score_at(i):
|
||||
mx = ROLLMAX[i]
|
||||
valid = np.isfinite(mx) & np.isfinite(px[i])
|
||||
return -mx, valid
|
||||
return score_at, B + 1
|
||||
|
||||
|
||||
def make_ivol(PX, B):
|
||||
"""IVOL: vol del RESIDUO dopo beta*mercato su finestra B (OLS in-window, esatto:
|
||||
var_resid = var(y) - beta^2*var(m) con beta = cov/var, >= 0 per costruzione).
|
||||
score = -ivol -> long bassa idio-vol / short alta (anomalia Ang et al.)."""
|
||||
px, n, A, DR, m = xv._precompute(PX)
|
||||
beta, varm = xv._rolling_beta(DR, m, B) # beta (n,A), varm (n,)
|
||||
mp = int(0.6 * B)
|
||||
ExDR = DR.rolling(B, min_periods=mp).mean()
|
||||
ExDR2 = (DR * DR).rolling(B, min_periods=mp).mean()
|
||||
varDR = (ExDR2 - ExDR ** 2).values # var population (coerente con _rolling_beta)
|
||||
resid_var = varDR - (beta ** 2) * varm[:, None]
|
||||
ivol = np.sqrt(np.clip(resid_var, 0.0, None))
|
||||
def score_at(i):
|
||||
iv = ivol[i]
|
||||
valid = np.isfinite(iv) & np.isfinite(beta[i]) & np.isfinite(px[i])
|
||||
return -iv, valid
|
||||
return score_at, B + 1
|
||||
|
||||
|
||||
def make_amihud(PX, VOL, B, sign):
|
||||
"""AMIHUD illiquidity: media su B di |ret| / dollar_volume (volume_coin*close).
|
||||
sign=+1 -> LONG illiquido (premio di illiquidita'); sign=-1 -> LONG liquido."""
|
||||
px, n, A, DR, m = xv._precompute(PX)
|
||||
dvol = (VOL * PX).replace(0, np.nan) # dollarizza il volume in coin
|
||||
illiq = (DR.abs() / dvol).replace([np.inf, -np.inf], np.nan)
|
||||
AMI = illiq.rolling(B, min_periods=int(0.6 * B)).mean().values
|
||||
def score_at(i):
|
||||
a = AMI[i]
|
||||
valid = np.isfinite(a) & np.isfinite(px[i])
|
||||
return sign * a, valid
|
||||
return score_at, B + 1
|
||||
|
||||
|
||||
def amihud_builder(VOL_full, sign):
|
||||
"""Builder (PX,cfg) per AMIHUD che richiede il volume: chiude su VOL_full e lo RIALLINEA a
|
||||
PX.index. Cosi' la causality_prefix_check (che tronca PX a PXc=PX[:cut] e chiama builder(PXc))
|
||||
riceve automaticamente VOLc = VOL_full.reindex(PXc.index) -> nessun look-ahead dal volume."""
|
||||
def builder(PX, p):
|
||||
VOL = VOL_full.reindex(PX.index)
|
||||
return make_amihud(PX, VOL, p["B"], sign)
|
||||
return builder
|
||||
|
||||
|
||||
# Griglia condivisa (parita' con i gate): B x H x k.
|
||||
BHK = [dict(B=B, H=H, k=k) for B in (20, 30, 60) for H in (5, 10) for k in (5, 8)]
|
||||
|
||||
|
||||
def build_mechanisms(VOL):
|
||||
"""Catalogo per-universo: AMIHUD va legato al VOL dell'universo corrente."""
|
||||
return {
|
||||
"MAX": (lambda PX, p: make_max(PX, p["B"]), BHK),
|
||||
"IVOL": (lambda PX, p: make_ivol(PX, p["B"]), BHK),
|
||||
"AMIHUD_ILLIQ": (amihud_builder(VOL, +1), BHK), # long illiquido / short liquido
|
||||
"AMIHUD_LIQ": (amihud_builder(VOL, -1), BHK), # long liquido / short illiquido
|
||||
}
|
||||
|
||||
|
||||
# Tesi economica per il verdetto AMIHUD (quale segno ha senso se mostra edge).
|
||||
AMIHUD_THESIS = {
|
||||
"AMIHUD_ILLIQ": "premio di illiquidita' (long illiquido / short liquido)",
|
||||
"AMIHUD_LIQ": "flight-to-quality verso i major liquidi (long liquido / short illiquido)",
|
||||
}
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# Helper
|
||||
# ===========================================================================
|
||||
def insample_sharpe(daily):
|
||||
pre = daily[daily.index < HOLDOUT]
|
||||
return metrics(pre)["sharpe"] if len(pre) > 30 else float("nan")
|
||||
|
||||
|
||||
def per_year(daily):
|
||||
_, yrs = xv.yr_breadth(daily)
|
||||
years = [int(y) for y, _ in daily.groupby(daily.index.year)]
|
||||
return [(y, round(v, 3)) for y, v in zip(years, yrs)]
|
||||
|
||||
|
||||
def uplift_for(cand_daily, base, bf, bh, fractions=(0.10, 0.15)):
|
||||
"""Uplift portafoglio 4->5 sleeve riusando le CACHE di `base` (Sleeve cached). Ritorna
|
||||
{fr: (cf, ch, wgt)} e il best combinato (dFULL+dHOLD)."""
|
||||
cand_fn = lambda: cand_daily
|
||||
out, best = {}, None
|
||||
for fr in fractions:
|
||||
wraw = fr / (1.0 - fr) # cand_frac ~ fr (sum_base=1)
|
||||
cand = xv.Sleeve("XSV3_cand", wraw, cand_fn)
|
||||
pf1 = xv.StrategyPortfolio(base + [cand])
|
||||
cf = metrics(pf1.combined_daily())
|
||||
ch = metrics(pf1.combined_daily(lo=HOLDOUT))
|
||||
wgt = pf1.weights().get("XSV3_cand", 0.0)
|
||||
out[fr] = (cf, ch, wgt)
|
||||
d = (cf["sharpe"] - bf["sharpe"]) + (ch["sharpe"] - bh["sharpe"])
|
||||
best = d if best is None else max(best, d)
|
||||
return out, best
|
||||
|
||||
|
||||
INSAMPLE_EDGE = 0.5 # gate del progetto (scorer indurito): edge standalone PRE-holdout >=0.5
|
||||
|
||||
|
||||
def robust_candidate(rows):
|
||||
"""Candidato GIUDICATO: NON il best-by-HOLD nudo (che premia il holdout-fitting: una config
|
||||
negativa in-sample con HOLD alto e' overfit alla finestra OOS, lezione dello scorer indurito),
|
||||
ma il best fra le config con EDGE IN-SAMPLE (>=0.5) E HOLD>0, ordinate per Sharpe BILANCIATO
|
||||
(insample+hold)/2. Se nessuna ha in-sample edge -> None (il meccanismo non ha edge reale,
|
||||
qualunque HOLD alto e' artefatto di selezione)."""
|
||||
elig = [r for r in rows if np.isfinite(r["insample"]) and r["insample"] >= INSAMPLE_EDGE and r["hold"] > 0]
|
||||
if not elig:
|
||||
return None
|
||||
return max(elig, key=lambda r: 0.5 * (r["insample"] + r["hold"]))
|
||||
|
||||
|
||||
def verdict(cand, dsr, caus_ok, uplift_best, has_isedge):
|
||||
full, hold, corrXS = cand["full"], cand["hold"], cand["corrXS"]
|
||||
diversifies = abs(corrXS) < 0.6
|
||||
helps = (uplift_best is not None) and uplift_best > 0.10
|
||||
if not has_isedge:
|
||||
return "SCARTATO", "nessuna config con edge in-sample>=0.5 + HOLD>0 (qualunque HOLD alto e' selezione-su-holdout)"
|
||||
strong = (dsr > 0.95) and (hold > 0.30) and (full > 0.70) and caus_ok and diversifies
|
||||
if strong and helps:
|
||||
return "SLEEVE-CANDIDATE", "edge robusto (DSR>0.95, in-sample+OOS, causale, diversifica, alza il portafoglio)"
|
||||
if (full > 0.5 and hold > 0.0 and diversifies and caus_ok) and (helps or dsr > 0.50):
|
||||
return "LEAD-forward-monitor", "edge in-sample E OOS coerente + diversifica, ma DSR<0.95 (96 trial, storia ~2.5y)"
|
||||
if full > 0.3 and hold > 0.0:
|
||||
return "DEBOLE", "segno giusto ma Sharpe/robustezza insufficienti"
|
||||
return "SCARTATO", "no edge (full/hold non positivi o non diversifica)"
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
# MAIN
|
||||
# ===========================================================================
|
||||
def main():
|
||||
print("=" * 104)
|
||||
print(" XSEC v3 — LOW-RISK COUSINS cross-sectional su Hyperliquid (MAX / IVOL / AMIHUD) — STAT-MODE")
|
||||
print("=" * 104)
|
||||
|
||||
tp_daily = xv.tp01_sleeve().daily()
|
||||
xs_daily = xv.xsec_sleeve().daily()
|
||||
print(" riferimenti corr: TP01 (trend, deployable) e XS01 (momentum cross-sec, sleeve attivo).")
|
||||
|
||||
universes = {"51-all": None, "19-major": xv.XS_UNIVERSE}
|
||||
mats = {}
|
||||
for uname, u in universes.items():
|
||||
PX, VOL = xv.load_matrix(u)
|
||||
mats[uname] = (PX, VOL)
|
||||
print(f" universo {uname:<9}: {PX.shape[1]:>2} asset, {PX.shape[0]} giorni "
|
||||
f"[{PX.index[0].date()} -> {PX.index[-1].date()}]")
|
||||
|
||||
# ---- griglia completa: raccoglie tutte le righe + tutti gli Sharpe FULL (trial DSR) ----
|
||||
MECHS = ("MAX", "IVOL", "AMIHUD_ILLIQ", "AMIHUD_LIQ")
|
||||
rows_by_mech = {mn: [] for mn in MECHS}
|
||||
all_sr = []
|
||||
builders = {} # (uname, mech) -> builder (per causality)
|
||||
for uname, (PX, VOL) in mats.items():
|
||||
mechs = build_mechanisms(VOL)
|
||||
print("\n" + "#" * 104)
|
||||
print(f"# UNIVERSO {uname}")
|
||||
print("#" * 104)
|
||||
for mn in MECHS:
|
||||
builder, cfgs = mechs[mn]
|
||||
builders[(uname, mn)] = builder
|
||||
rows = xv.run_grid(PX, VOL, mn, builder, cfgs, xs_daily, tp_daily, uname)
|
||||
for r in rows:
|
||||
r["uni"] = uname
|
||||
r["mech"] = mn
|
||||
r["insample"] = insample_sharpe(r["daily"])
|
||||
rows_by_mech[mn].extend(rows)
|
||||
all_sr.extend([r["full"] for r in rows])
|
||||
if not rows:
|
||||
print(f"\n [{mn}] nessuna config valida")
|
||||
continue
|
||||
pos_full = sum(r["full"] > 0 for r in rows)
|
||||
pos_hold = sum(r["hold"] > 0 for r in rows)
|
||||
print(f"\n [{mn}] {len(rows)} config | plateau FULL>0: {pos_full}/{len(rows)}"
|
||||
f" | HOLD>0: {pos_hold}/{len(rows)}")
|
||||
print(f" {'cfg':<18}{'FULL':>7}{'inS':>7}{'HOLD':>7}{'DD%':>6}{'ret%':>7}"
|
||||
f"{'anni+':>7}{'corrXS':>8}{'corrTP':>8}{'turn/y':>8}")
|
||||
for r in sorted(rows, key=lambda r: -r["hold"])[:3]:
|
||||
print(f" {xv.tag(r['cfg']):<18}{r['full']:>7.2f}{r['insample']:>7.2f}{r['hold']:>7.2f}"
|
||||
f"{r['dd']*100:>6.0f}{r['ret']*100:>+7.0f}{r['pct']*100:>6.0f}%"
|
||||
f"{r['corrXS']:>+8.2f}{r['corrTP']:>+8.2f}{r['turn']:>8.0f}")
|
||||
|
||||
print(f"\n TRIAL TOTALI testati (per deflated-Sharpe): {len([s for s in all_sr if np.isfinite(s)])}")
|
||||
|
||||
# ---- base portafoglio una sola volta (Sleeve cached, riusati per ogni candidato) ----
|
||||
base = xv.active_sleeves()
|
||||
pf0 = xv.StrategyPortfolio(base); pf0.backtest()
|
||||
bf = metrics(pf0.combined_daily()); bh = metrics(pf0.combined_daily(lo=HOLDOUT))
|
||||
|
||||
# ---- analisi per meccanismo ----
|
||||
# CANDIDATO GIUDICATO = robust_candidate (edge in-sample>=0.5 E HOLD>0, best bilanciato): evita la
|
||||
# trappola del best-by-HOLD nudo (che premia config negative in-sample = overfit alla finestra OOS).
|
||||
# Si riportano comunque, per trasparenza/anti-cherry, anche il naive best-HOLD e il best-inSAMPLE.
|
||||
summary = []
|
||||
chosen_daily = {} # mech -> serie del candidato giudicato (per corr-matrix)
|
||||
for mn in MECHS:
|
||||
rows = rows_by_mech[mn]
|
||||
print("\n" + "=" * 104)
|
||||
print(f" MECCANISMO {mn}")
|
||||
print("=" * 104)
|
||||
if not rows:
|
||||
print(" nessuna config valida -> SCARTATO")
|
||||
summary.append((mn, "SCARTATO", "nessuna config valida", None))
|
||||
continue
|
||||
|
||||
naive_hold = max(rows, key=lambda r: r["hold"])
|
||||
best_is = max(rows, key=lambda r: (r["insample"] if np.isfinite(r["insample"]) else -9))
|
||||
cand = robust_candidate(rows)
|
||||
has_isedge = cand is not None
|
||||
print(f" naive best-HOLD : [{naive_hold['uni']}] {xv.tag(naive_hold['cfg']):<16} "
|
||||
f"FULL {naive_hold['full']:+.2f} inS {naive_hold['insample']:+.2f} HOLD {naive_hold['hold']:+.2f}"
|
||||
f" {'(in-sample NEGATIVO -> holdout-fit)' if naive_hold['insample'] < INSAMPLE_EDGE else ''}")
|
||||
print(f" best-inSAMPLE : [{best_is['uni']}] {xv.tag(best_is['cfg']):<16} "
|
||||
f"FULL {best_is['full']:+.2f} inS {best_is['insample']:+.2f} HOLD {best_is['hold']:+.2f}"
|
||||
f" (anti-selection-on-holdout)")
|
||||
if not has_isedge:
|
||||
print(f" CANDIDATO GIUDICATO: NESSUNA config con in-sample>=0.5 E HOLD>0 "
|
||||
f"-> il meccanismo NON ha edge reale (ogni HOLD alto e' selezione-su-holdout).")
|
||||
verd, why = verdict(naive_hold, float("nan"), True, None, False)
|
||||
print(f"\n >>> VERDETTO {mn}: {verd} — {why}." +
|
||||
(f" tesi: {AMIHUD_THESIS[mn]}" if mn in AMIHUD_THESIS else ""))
|
||||
summary.append((mn, verd, why, dict(uni=naive_hold["uni"], cfg=xv.tag(naive_hold["cfg"]),
|
||||
full=naive_hold["full"], hold=naive_hold["hold"], dd=naive_hold["dd"],
|
||||
dsr=float("nan"), corrXS=naive_hold["corrXS"], up=None, isedge=False)))
|
||||
continue
|
||||
|
||||
daily = cand["daily"]
|
||||
chosen_daily[mn] = daily
|
||||
f, h, pct = xv.evalcfg(daily)
|
||||
dsr, sr0 = xv.deflated_sharpe(f["sharpe"], all_sr, daily)
|
||||
caus = xv.causality_prefix_check(*mats[cand["uni"]], builders[(cand["uni"], mn)], cand["cfg"])
|
||||
ups, up_best = uplift_for(daily, base, bf, bh)
|
||||
|
||||
print(f" CANDIDATO GIUDICATO (in-sample>=0.5 & HOLD>0, best bilanciato):")
|
||||
print(f" [{cand['uni']}] {xv.tag(cand['cfg']):<16} FULL {cand['full']:+.2f} "
|
||||
f"inSAMPLE {cand['insample']:+.2f} HOLD {cand['hold']:+.2f} (in-sample EDGE = OK)")
|
||||
print(f" standalone: DD {f['maxdd']*100:.0f}% ret {f['ret']*100:+.0f}% "
|
||||
f"anni+ {pct*100:.0f}% turnover/y {cand['turn']:.0f}")
|
||||
print(f" corr vs XS01 {cand['corrXS']:+.2f} | corr vs TP01 {cand['corrTP']:+.2f}")
|
||||
print(f" CAUSALITA' prefix-check: ok={caus['ok']} max_tail_diff={caus['max_tail_diff']:.2e}")
|
||||
print(f" DEFLATED Sharpe (N={len([s for s in all_sr if np.isfinite(s)])} trial): {dsr:.3f}"
|
||||
f" | soglia Sharpe-max-null annualizz. {sr0:.2f} (serve DSR>0.95)")
|
||||
print(f" per-anno: {per_year(daily)}")
|
||||
print(f" UPLIFT portafoglio (base FULL {bf['sharpe']:.2f} / HOLD {bh['sharpe']:.2f}):")
|
||||
for fr, (cf, ch, wgt) in ups.items():
|
||||
print(f" +cand @{wgt*100:>4.1f}% FULL {cf['sharpe']:.2f} ({cf['sharpe']-bf['sharpe']:+.2f})"
|
||||
f" DD {cf['maxdd']*100:.0f}% | HOLD {ch['sharpe']:.2f} ({ch['sharpe']-bh['sharpe']:+.2f})")
|
||||
|
||||
verd, why = verdict(cand, dsr, caus["ok"], up_best, has_isedge)
|
||||
extra = f" tesi: {AMIHUD_THESIS[mn]}" if mn in AMIHUD_THESIS else ""
|
||||
print(f"\n >>> VERDETTO {mn}: {verd} — {why}.{extra}")
|
||||
summary.append((mn, verd, why, dict(uni=cand["uni"], cfg=xv.tag(cand["cfg"]), full=cand["full"],
|
||||
hold=cand["hold"], dd=f["maxdd"], dsr=dsr, corrXS=cand["corrXS"],
|
||||
up=up_best, isedge=True)))
|
||||
|
||||
# ---- redundancy check: i 3 'low-risk cousins' sono UNA scommessa o TRE? ----
|
||||
if len(chosen_daily) >= 2:
|
||||
print("\n" + "=" * 104)
|
||||
print(" RIDONDANZA — correlazione tra i candidati giudicati (sono lo stesso bet 'evita-speculativo'?)")
|
||||
print("=" * 104)
|
||||
names = list(chosen_daily.keys())
|
||||
print(" " + "".join(f"{n[:8]:>10}" for n in names))
|
||||
for a in names:
|
||||
line = f" {a[:8]:<10}"
|
||||
for b in names:
|
||||
line += f"{xv._corr(chosen_daily[a], chosen_daily[b]):>10.2f}"
|
||||
print(line)
|
||||
print(" NB: corr alta tra i candidati = sono la stessa anomalia low-risk in tre vesti, non tre edge.")
|
||||
|
||||
# ---- AMIHUD: scegli il segno con tesi economica + edge ----
|
||||
print("\n" + "=" * 104)
|
||||
print(" AMIHUD — scelta del segno (tesi economica + edge)")
|
||||
print("=" * 104)
|
||||
a_ill = next((s for s in summary if s[0] == "AMIHUD_ILLIQ"), None)
|
||||
a_liq = next((s for s in summary if s[0] == "AMIHUD_LIQ"), None)
|
||||
for s in (a_ill, a_liq):
|
||||
if s and s[3]:
|
||||
print(f" {s[0]:<14} FULL {s[3]['full']:+.2f} HOLD {s[3]['hold']:+.2f} "
|
||||
f"DSR {s[3]['dsr']:.2f} corrXS {s[3]['corrXS']:+.2f} -> {s[1]} ({AMIHUD_THESIS[s[0]]})")
|
||||
cand_signs = [s for s in (a_ill, a_liq) if s and s[3] and s[3]["full"] > 0 and s[3]["hold"] > 0]
|
||||
if cand_signs:
|
||||
win = max(cand_signs, key=lambda s: 0.5 * (s[3]["full"] + s[3]["hold"]))
|
||||
print(f" -> segno con edge+tesi: {win[0]} ({AMIHUD_THESIS[win[0]]})")
|
||||
else:
|
||||
print(" -> NESSUN segno mostra edge positivo (full>0 e hold>0): AMIHUD SCARTATO in entrambi i versi.")
|
||||
|
||||
# ---- sintesi finale ----
|
||||
print("\n" + "=" * 104)
|
||||
print(" SINTESI FINALE — c'e' un sopravvissuto reale?")
|
||||
print("=" * 104)
|
||||
for mn, verd, why, info in summary:
|
||||
if info:
|
||||
print(f" {mn:<14} {verd:<22} FULL {info['full']:+.2f} HOLD {info['hold']:+.2f} "
|
||||
f"DSR {info['dsr']:.2f} corrXS {info['corrXS']:+.2f} upliftBest {info['up'] if info['up'] is not None else float('nan'):+.2f}")
|
||||
else:
|
||||
print(f" {mn:<14} {verd}")
|
||||
survivors = [s for s in summary if s[1] == "SLEEVE-CANDIDATE"]
|
||||
leads = [s for s in summary if s[1] == "LEAD-forward-monitor"]
|
||||
if survivors:
|
||||
print(f"\n SOPRAVVISSUTO: {', '.join(s[0] for s in survivors)} (sleeve-candidate, comunque STAT-MODE).")
|
||||
elif leads:
|
||||
print(f"\n Nessuno sleeve-candidate. LEAD da forward-monitor: {', '.join(s[0] for s in leads)}.")
|
||||
else:
|
||||
print("\n NESSUN sopravvissuto: tutti DEBOLE/SCARTATO. Risultato valido (la maggior parte muore).")
|
||||
|
||||
print("\n CAVEAT immutabili: storia ~2.5 anni (deflated-Sharpe + multiple-testing), book a molte")
|
||||
print(" gambe NON eseguibile a $600 -> STAT-MODE / forward-monitor, MAI deploy. Nessuno sleeve")
|
||||
print(" registrato: e' solo lavoro statistico (vincoli del filone C).")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user