research: ondata 26/07-bis — TP01 su barra parziale + i 2 gate mai costruiti

0 sleeve nuovi. Book, pesi, cron, config INVARIATI.

T1 — anche TP01 legge una barra giornaliera PARZIALE nel live, ma non conta.
Stesso fatto strutturale di SKH01: resample_tf non scarta il giorno in corso e
current_target prende [-1]; il feed si ricostruisce alle 00:30 UTC, quindi per
tutta la giornata il book vede oggi come 1 barra oraria su 24 (verificato).
Il docstring "ultima barra CHIUSA" era falso: corretto.

Tre path a un grado di liberta' per volta, 24 ancore, differenze appaiate:
  barra parziale   ΔFULL -0.031 (pos 7/24)  ΔHOLD +0.118 (pos 19/24)
  ritardo 1h       ΔFULL -0.004 (pos 11/24 = moneta)
  leva LIVE/MODEL  1.004
-> trascurabile, nessun cambio al live.

All'ancora canonica sembra peggio del vero: a offset 0 la parziale costa -0.230
di hold-out, che e' il MINIMO della banda (mediana +0.118). Speculare alla
lezione del 26/07: li' l'ancora canonica nascondeva un vantaggio, qui inventa
un danno.

REGOLA: la parzialita' dell'ultima barra conta in proporzione a quanto il
segnale pesa la barra piu' recente. Donchian breakout su 230m (la barra corrente
E' il segnale) -> +0.38; TSMOM 30/90/180g -> ±0.03. Non si trasferisce.

T2 — implausible_sharpe e anchor_luck_band codificati in altlib (debito
raccomandato 3 volte e mai scritto), piu' anchor_luck_delta che codifica
l'errore di stamattina (mediana delle differenze appaiate, non differenza
delle mediane).

Il gate ha segnalato VRP01 e il difetto era MIO: perdite contate su tutte le
barre, ma VRP01 e' settimanale su griglia giornaliera (94.2% di zeri) -> "0.96%,
coda assente" su uno sleeve in produzione. Sulle barre ATTIVE e' 16.5%, la forma
giusta di un credit spread a rischio definito. La lezione era gia' cablata il
giorno prima nel monitor DVOLSPREAD ("barre attive, non giorni di calendario").

Applicazione retroattiva 7/7 tutti ok, con controlli positivi obbligatori
superati (firme CC01 e deep-OTM segnalate, rumore Sh 0.62 no).

Replica indipendente del finding d'ancora del 02/07: il gate applicato alla
cieca a TP01 ritrova canonica +0.237 = 92 pctl delle 24, mediana onesta +0.056,
fortuna +0.182 — contro mediana 0.04 misurata il 02/07 con implementazione
separata. L'hold-out onesto di TP01 e' ~+0.05, non 0.31.

NON fatto: il book ricalcolato sul path live, bloccato da incompatibilita' di
lenti (simulatore per-trade vs sleeve vol-targeted). Follow-up dichiarato.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-07-25 22:36:40 +00:00
parent 6d0813a399
commit 37be1df838
7 changed files with 947 additions and 2 deletions
+142
View File
@@ -782,6 +782,148 @@ def _verdict(per_cell: list[dict]) -> dict:
n_positive_cells=len(ok), n_cells=len(per_cell))
def implausible_sharpe(daily, n_trades: int | None = None, n_losing_trades: int | None = None,
sharpe_max: float = 3.0, min_loss_frac: float = 0.02,
calmar_max: float = 20.0) -> dict:
"""GATE: a Sharpe/track too good to be true means the RISK IS OUTSIDE THE DATASET.
Codifies a blind spot the project hit THREE times and each time diagnosed by hand:
- CC01 cash-and-carry: modelled Sharpe 11-13, maxDD 0.3% — it passed EVERY gate of the
marginal scorer. The premium was real; the model simply had no 2022, no liquidation, no
basis blow-out. Real basis trades run Sharpe ~1-3 with abrupt tails.
- Albimarini deep-OTM cells: 0 losses in 142 trades.
- half of the 288-structure VRP grid: same 0-loss signature.
The point is NOT that a high Sharpe is impossible. It is that a track with no left tail is
evidence about the SAMPLE, not about the strategy: the loss mode exists and did not occur.
A flagged candidate is at best STAT-MODE — never a deploy — until the missing tail is priced.
`n_trades` / `n_losing_trades` enable the RULE OF THREE: observing 0 losses in N trades is
consistent with a true loss rate up to ~3/N. On a payoff that loses many multiples of the
premium (deep-OTM short options) that residual rate is enough to flip expectancy — which is
exactly why "0/142" must not read as "riskless".
Returns dict(implausible: bool, reasons: [...], ...). ANY trigger sets implausible=True.
"""
s = pd.Series(daily)
if not isinstance(s.index, pd.DatetimeIndex):
raise TypeError("implausible_sharpe vuole una serie a indice temporale "
"(altrimenti l'annualizzazione e' arbitraria)")
s = _to_daily(s) # normalizza a giornaliero: _sh annualizza a 365.25
r = np.asarray(s.dropna().values, float)
out = dict(implausible=False, reasons=[], n_obs=int(len(r)))
if len(r) < 30:
out["reasons"].append(f"campione troppo corto per giudicare (n={len(r)})")
return out
sh, dd = _sh(s), _dd_ret(s)
ann = float(np.mean(r) * 365.25)
# ⚠️ la frazione di perdite si misura sulle barre ATTIVE, non su tutte. Uno sleeve che sta
# flat la maggior parte del tempo (VRP01 e' settimanale su griglia giornaliera: 94% di zeri;
# SKH01 e' flat l'88% dei giorni) farebbe scattare "coda sinistra assente" per COSTRUZIONE.
# E' lo stesso errore che il monitor DVOLSPREAD evita contando in barre attive: qui l'avevo
# ripetuto, e il gate segnalava VRP01 (perdite 0.96% su tutte -> 16.5% sulle attive).
act = r[r != 0.0]
loss_frac = float((act < 0).mean()) if len(act) else float("nan")
calmar = ann / dd if dd > 1e-9 else float("inf")
out.update(sharpe=sh, maxdd=dd, ann_return=ann, loss_frac=loss_frac, calmar=calmar,
n_active=int(len(act)), active_frac=float(len(act) / len(r)))
if len(act) < 30:
out["reasons"].append(
f"solo {len(act)} barre ATTIVE: la coda non e' misurabile, non 'assente' "
f"(giudizio sospeso, non promozione)")
out["implausible"] = True
return out
if sh > sharpe_max:
out["reasons"].append(
f"Sharpe {sh:.2f} > {sharpe_max} — nessuna strategia eseguibile lo sostiene su anni; "
f"il candidato piu' probabile e' un rischio non nel campione")
if loss_frac < min_loss_frac:
out["reasons"].append(
f"solo il {loss_frac*100:.2f}% delle {len(act)} barre ATTIVE in perdita "
f"(<{min_loss_frac*100:.0f}%) — coda sinistra assente: e' una proprieta' del "
f"CAMPIONE, non della strategia")
if np.isfinite(calmar) and calmar > calmar_max:
out["reasons"].append(
f"Calmar {calmar:.1f} > {calmar_max} (ret {ann*100:.1f}%/a su maxDD {dd*100:.2f}%) — "
f"il drawdown misurato non contiene il modo di perdita")
elif not np.isfinite(calmar):
out["reasons"].append("maxDD ~0: il modo di perdita non si e' MAI manifestato nel campione")
if n_trades and n_trades > 0 and n_losing_trades is not None and n_losing_trades == 0:
ub = 3.0 / n_trades # regola del tre
out["loss_rate_ub95"] = float(ub)
out["reasons"].append(
f"0 perdite su {n_trades} trade: la regola del tre lascia un tasso di perdita VERO "
f"fino a {ub*100:.2f}% — su un payoff asimmetrico basta a ribaltare l'expectancy")
out["implausible"] = bool(out["reasons"])
return out
def anchor_luck_band(fn_by_offset, offsets, canonical=0, metric=_sh,
min_frac_positive: float = 0.5) -> dict:
"""GATE: quanta parte del titolo e' FORTUNA DELL'ANCORA (candidato dichiarato il 02/07).
Il timing-luck d'ancora e' multiple-testing che il deflated-Sharpe NON conta: la griglia di
ancore non e' una griglia di PARAMETRI, quindi nessuno la dichiara come trial, ma scegliere
(anche implicitamente) l'ancora 00:00 e' un max-of-k. Misurato su 4/4 sleeve ancorati:
TP01 hold-out 0.31 = migliore delle 24; SKH01 minHold +1.26 = 93-98° pctl dei 23 offset;
XS01 fase 0 al 15° pctl di DD; VRP01 e' l'unico SENZA firma (la sua fase e' la PEGGIORE).
`fn_by_offset(off)` -> serie di rendimenti (giornalieri o piu' fini). `metric` -> scalare.
La stima ONESTA e' la **mediana della banda**, non il valore canonico.
gate_pass = la metrica e' positiva alla mediana E in almeno `min_frac_positive` delle ancore
(necessario, non sufficiente: dice che il segno non dipende dall'ancora).
"""
vals = {}
for o in offsets:
try:
vals[o] = float(metric(fn_by_offset(o)))
except Exception: # un'ancora rotta non deve uccidere
continue
if len(vals) < 3:
return dict(gate_pass=False, reason=f"solo {len(vals)} ancore valutabili")
arr = np.array(sorted(vals.values()), float)
can = vals.get(canonical, float("nan"))
pctl = float((arr < can).mean()) if np.isfinite(can) else float("nan")
med = float(np.median(arr))
frac_pos = float((arr > 0).mean())
return dict(gate_pass=bool(med > 0 and frac_pos >= min_frac_positive),
canonical=can, canonical_pctl=pctl, median=med,
lo=float(arr.min()), hi=float(arr.max()),
frac_positive=frac_pos, n_anchors=len(vals),
luck=float(can - med) if np.isfinite(can) else float("nan"),
per_offset=vals)
def anchor_luck_delta(fn_a_by_offset, fn_b_by_offset, offsets, metric=_sh) -> dict:
"""Confronto di DUE varianti sulla banda d'ancora: **mediana delle DIFFERENZE APPAIATE**.
Errore catturato in sessione il 26/07 e qui codificato: confrontare `mediana(A) - mediana(B)`
mette a confronto ancore DIVERSE (la mediana di A e quella di B cadono su offset diversi) e
puo' RIBALTARE il verdetto — successe misurando il recupero on-book di SKH01. Gli offset sono
coppie: la statistica corretta e' la mediana di (A_o - B_o).
Corollario della lezione del 26/07: *se si de-lucka una strategia va de-luckato anche il suo
DEGRADO* — ogni Δ fra due varianti misurato a un'ancora sola eredita la fortuna di quell'ancora.
"""
d = {}
for o in offsets:
try:
d[o] = float(metric(fn_a_by_offset(o))) - float(metric(fn_b_by_offset(o)))
except Exception:
continue
if len(d) < 3:
return dict(gate_pass=False, reason=f"solo {len(d)} ancore appaiate")
arr = np.array(list(d.values()), float)
return dict(gate_pass=bool(np.median(arr) > 0 and (arr > 0).mean() >= 0.5),
median_paired=float(np.median(arr)), mean_paired=float(np.mean(arr)),
lo=float(arr.min()), hi=float(arr.max()),
n_positive=int((arr > 0).sum()), n_anchors=len(d), per_offset=d)
def study_weights(name: str, target_fn, tfs=("1d", "12h"),
assets=CERTIFIED, fee_sweep=FEE_SWEEP) -> dict:
"""Run a CONTINUOUS-position hypothesis on each (asset,tf), report robustness.