Files
Adriano Dal Pastro 842420ce7b research: follow-up SKH01 chiuso — il blocco non esisteva, il de-luck x0.6 era troppo severo
Il follow-up "book sul path live" era fermo da tre sessioni su questa premessa:
"il simulatore compone per-trade a nozionale unitario, LO SLEEVE E' VOL-TARGETED".

LA PREMESSA ERA FALSA. Verificato in tre modi: _skyhook_returns chiama backtest_signals
con leverage=1.0/position_size=1.0; nessun target_vol/vol_target/realized_vol nel
sorgente; sim_equity(canonical) riproduce backtest_signals a max|diff| = 0.0. Il 20.7%
di vol realizzata dello sleeve e' un PRODOTTO della strategia (uscite % asimmetriche +
poco tempo a mercato), non un parametro: SKH01 e' l'unica delle 5 a NON essere
vol-targeted, il contrario di quanto si credeva. Test permanente cablato.

MISURA (ingressi live vs backtest, de-luckata su 8 offset a priori; sanity 120/120 bin
con ingresso ricostruiti):
- il numero del 26/07 era ~4x troppo grande: like-with-like (mediana PER-ASSET) +0.38 su
  3 offset -> +0.097 su 8, e "6/6 non negativi" -> 13/16. Sleeve 50/50: +0.112, 8/8.
- a livello di BOOK lo Sharpe e' una monetina (FULL +0.048, HOLD +0.051) ma il DRIFT e'
  +0.73pp positivo nel 100% delle estrazioni: l'ingresso intra-bin prende un prezzo
  migliore, i falsi ingressi aggiungono churn, vol e ritorno salgono insieme.

IL FATTORE x0.6 DECOMPOSTO E MISURATO:
  (a) fortuna d'ancora sul DRIFT: x0.874 (5-sleeve) / x0.890 (book live). La vol e'
      invariata fra le ancore (7.80->7.76%): la fortuna sta tutta nel drift.
  (b) path live: NON-NEGATIVO ovunque (uscite +0.081 Sh, ingressi +0.73pp, TP01 ~0).
Il x0.6 implicava un residuo x0.687 attribuito al live oltre l'ancora, che nessuna misura
sostiene -> FATTORE ONESTO x0.87-0.91, troppo severo del 31-34%. Il sospetto registrato
il 25/07 ("conta due volte la degradazione SKH01") e' confermato quantitativamente.

MURI DI CAPITALE ricalcolati con la stessa macchineria del 25/07: perpetua 6.00% ->
10.91%, muro per 50 EUR/g $494.758 -> $272.061 (-45%) a leva 1.0. La conclusione
STRUTTURALE non cambia: $272k restano ~453x il conto di oggi.

IPOTESI MIA REFUTATA nella stessa sessione: che la grid timing-luck di SKH01 fosse un
artefatto della lente a chiusura-di-bin. Dispersione LIVE/CANONICO = 1.59x -> il live e'
PIU' disperso. L'audit del 02/07 resta valido com'e'. (Nata su 2 offset, chiusa a 8.)

Trovato per strada: simulate() in r0726_skh_partial_entry.py non era mai chiamata da
main() — i numeri headline di quel diario venivano da una corsa mai committata.

Book, pesi, cron, config INVARIATI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 16:55:01 +00:00

132 lines
6.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python
"""r0726_deluck_factor.py — DECOMPOSIZIONE del fattore de-luck ×0.6 del 25/07.
IL PROBLEMA. `r0725_capcurve.py` applica `b = b - 0.4*mean(b)` ("de-luck ×0.6": taglia il 40% del
DRIFT, lascia la vol) sopra un book gia' calcolato con SKH01 sulla lente `hourly`. Il ×0.6 fu
scelto **a occhio**, e CLAUDE.md registrava gia' il sospetto: *"il ×0.6 sopra il path live rischia
di contare DUE VOLTE la degradazione SKH01"*.
Il fattore mescola due cose che ora si possono separare e misurare:
(a) **fortuna d'ancora** — i numeri canonici sono un punto alto dello spazio d'ancora
(misurato il 26/07 sul Sharpe: FULL 97.0 pctl, HOLD 99.6 pctl). Qui serve sul **DRIFT**,
perche' e' il drift che il ×0.6 taglia.
(b) **degradazione del path live** — che per SKH01 e' misurata **NEGATIVA** (il live e' MEGLIO
del backtest su entrambi i lati: uscite +0.081 di Sharpe FULL di book su 23/23 offset,
ingressi +0.38 di Sharpe di sleeve) e per TP01 e' ~zero (±0.03 su 24 ancore).
Questo script misura (a) e la riporta accanto a (b), per due book:
* il book ATTIVO 5-sleeve (33/15/12/20/20)
* il book LIVE Deribit (TP01/SKH01 75/25) — quello che gira davvero
METODO: identico a `r0726_loo_deluck.py` (stesse serie d'ancora, stesse estrazioni congiunte
uniformi indipendenti), ma la metrica e' il **drift annualizzato** (media giornaliera x 365.25) e
il CAGR, non il Sharpe. Il fattore onesto e' `mediana / canonico` sul drift.
NON decide da solo il valore da usare: la componente (b) sugli INGRESSI di SKH01 e' in corso di
misura de-luckata (`r0726_skh_sigcache.py` + `r0726_skh_live_book.py`). Qui si stabilisce quanto
del ×0.6 e' spiegato dall'ancora — cioe' quanto ne resta da attribuire al live.
uv run python scripts/research/r0726_deluck_factor.py [--draws N]
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
import numpy as np
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts" / "research"))
sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt"))
import r0726_loo_deluck as L # noqa: E402
from src.portfolio.portfolio import HOLDOUT, combine_outer, metrics # noqa: E402
DPY = 365.25
BOOKS = {
"book ATTIVO 5-sleeve (33/15/12/20/20)": L.WEIGHTS,
"book LIVE Deribit (TP01/SKH01 75/25)": {"TP01_trend_1d": 0.75, "SKH01_skyhook": 0.25},
}
def drift_and_cagr(cols: dict, weights: dict) -> tuple[float, float, float]:
"""(drift annualizzato, CAGR, vol annualizzata) del book a quei pesi."""
w = {k: weights[k] for k in cols}
s = combine_outer(cols, w)
r = np.asarray(s.dropna().values, float)
m = metrics(s)
return float(r.mean() * DPY), float(m["cagr"]), float(r.std() * np.sqrt(DPY))
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--draws", type=int, default=2000)
args = ap.parse_args()
print("=" * 100)
print(" r0726 — DECOMPOSIZIONE del fattore de-luck ×0.6 (25/07)")
print("=" * 100)
print(" Il ×0.6 taglia il DRIFT del book (b - 0.4*mean(b)). Quanto di quel taglio e'")
print(" spiegato dalla FORTUNA D'ANCORA, e quanto resterebbe da attribuire al path live?")
S = L.build_all()
rng = np.random.default_rng(L.SEED)
cfgs = [{n: int(rng.choice(L.SPEC[n][1])) for n in L.SPEC} for _ in range(args.draws)]
canon_cfg = {n: L.SPEC[n][2] for n in L.SPEC}
for label, W in BOOKS.items():
names = list(W)
print("\n" + "=" * 100)
print(f" {label}")
print("=" * 100)
def cols_at(cfg):
return {n: S[n][cfg[n]] for n in names}
d_can, c_can, v_can = drift_and_cagr(cols_at(canon_cfg), W)
rows = np.array([drift_and_cagr(cols_at(c), W) for c in cfgs], float)
d_med, c_med, v_med = np.median(rows, axis=0)
d_lo, d_hi = np.percentile(rows[:, 0], [10, 90])
c_lo, c_hi = np.percentile(rows[:, 1], [10, 90])
n_anch = int(np.prod([len(L.SPEC[n][1]) for n in names]))
print(f" spazio d'ancora di questo book: {n_anch:,} configurazioni "
f"({' x '.join(str(len(L.SPEC[n][1])) for n in names)})")
print(f"\n {'':<24}{'canonico':>11}{'pctl':>8}{'MEDIANA':>11}{'p10':>10}{'p90':>10}")
print(f" {'drift annualizzato':<24}{d_can*100:10.2f}%"
f"{float((rows[:, 0] < d_can).mean()*100):7.1f}%{d_med*100:10.2f}%"
f"{d_lo*100:9.2f}%{d_hi*100:9.2f}%")
print(f" {'CAGR':<24}{c_can*100:10.2f}%"
f"{float((rows[:, 1] < c_can).mean()*100):7.1f}%{c_med*100:10.2f}%"
f"{c_lo*100:9.2f}%{c_hi*100:9.2f}%")
print(f" {'vol annualizzata':<24}{v_can*100:10.2f}%{'':>8}{v_med*100:10.2f}%")
f_drift = d_med / d_can if d_can else float("nan")
f_cagr = c_med / c_can if c_can else float("nan")
print(f"\n >>> FATTORE D'ANCORA sul drift : ×{f_drift:.3f} (il ×0.6 ne assume ×0.600)")
print(f" >>> FATTORE D'ANCORA sul CAGR : ×{f_cagr:.3f}")
residuo = 0.6 / f_drift if f_drift else float("nan")
print(f" >>> residuo implicito da attribuire al PATH LIVE: ×{residuo:.3f}")
if residuo < 1.0:
print(f" cioe' il ×0.6 assume che il live tolga un altro "
f"{(1-residuo)*100:.0f}% di drift OLTRE l'ancora.")
else:
print(f" cioe' il ×0.6 e' CONSERVATIVO anche prima di contare il live.")
print("\n" + "=" * 100)
print(" COMPONENTE (b) — degradazione del path live, gia' misurata")
print("=" * 100)
print(" SKH01 uscite : +0.081 Sharpe FULL di book, 23/23 offset (26/07, banda appaiata)")
print(" SKH01 ingressi: +0.38 Sharpe di sleeve, mediana su 3 offset x 2 asset (26/07)")
print(" -> in corso la versione de-luckata a 8 offset, a livello di BOOK")
print(" TP01 barra parziale: -0.031 FULL / +0.118 HOLD su 24 ancore = trascurabile")
print(" => il segno di (b) e' NON-NEGATIVO su tutto cio' che e' stato misurato.")
print(" Un fattore <1 per il live non e' sostenuto da nessuna misura del progetto.")
if __name__ == "__main__":
main()