Files
PythagorasGoal/scripts/research/r0822c_skh_anchor_prop.py
T

605 lines
33 KiB
Python

"""r0822c_skh_anchor_prop — LA GAMBA (b) DEL `GATE PROP-01`, GIRATA DAVVERO (2026-08-22, ondata 3).
COSA CHIUDE
-----------
`r0822b_prop_recal` (RESULTS-0822 §29) ha registrato un gate a tre gambe e ne ha DATATA una al
**2026-10-31**:
(b) BANDA D'ANCORA DI SKH01 SOTTO LA LENTE PROP — MISURA. Data: 2026-10-31.
Costo: rifare `pc._hourly_legs` ai 23 offset della griglia 230m/690m e rigirare §3.
Metrica: MEDIANA DELLE DIFFERENZE APPAIATE J(peso nella regione w_XS 25-38%) -
J(libro LIVE 75/25) sui 23 offset, finestra W-LONG. PASS se > +0.05.
Surrogato di oggi (taglio del drift SKH x0.50 / x0.33): +0.124 e +0.113.
Mi aspetto un PASS e lo dichiaro prima.
E' l'unica distorsione che il progetto ha dichiarato DUE VOLTE (PROP-ALLOC §1 e PROP-RECAL §29)
e mai rimossa: *l'ottimo gira sull'ancora canonica e si appoggia a SKH01, che il progetto misura
come lo sleeve con la fortuna d'ancora piu' grande da restituire*.
⚠️ **LA DATA ERA UN BUDGET, E IL BUDGET ERA ANCORATO ALLA COMPUTAZIONE SBAGLIATA.**
Il costo temuto ("~9 min per asset x offset") e' quello di `r0726_skh_sigcache`, che ricostruisce
il segnale intra-bin a OGNI osservazione oraria dentro ogni bin 230m (~82.000 righe per cella).
Questa misura non ne ha bisogno: serve `pc._hourly_legs` all'offset, cioe' resample + entries +
MTM per-trade, **misurato 0,6 s per offset** (entrambi gli asset). I 23 offset costano ~15 s.
La gamba (b) non aspettava un dato ne' una macchina: aspettava che qualcuno misurasse il costo.
IL PROBLEMA VERO: LE ANCORE SONO DUE
------------------------------------
SKH01 ha 23 offset di griglia (230m/690m, uno ogni 30m su [0,690)); XS01 ha 10 fasi del ciclo
H=10. PROP-RECAL ha girato la banda di FASE (a offset SKH canonico); PROP-ALLOC nessuna delle due.
La lezione registrata di `r0726_loo_deluck` e' che **la somma delle fortune marginali NON e' la
mediana congiunta** (sbagliava di +0,82 di Sharpe hold-out).
**SCELTA DICHIARATA: griglia congiunta PIENA 23 x 10 = 230 configurazioni**, non un campionamento
e non una somma di marginali. E' possibile perche' il costo e' stato MISURATO prima di scegliere
(~0,06 s per coppia config-leva): con 23 offset x 10 fasi x 8 leve la spesa e' ~110 s.
**Perche' questa scelta NON e' quella che mi conviene:** la scelta comoda sarebbe fissare la fase
di XS01 a 0, che PROP-RECAL misura fortunata di +0,019 di J; il collasso per MEDIANA sulle fasi la
restituisce. Entrambe sono riportate, cosi' si vede da che parte taglia.
CONVENZIONI (tutte ereditate, nessuna nuova)
--------------------------------------------
* Statistica = **MEDIANA DELLE DIFFERENZE APPAIATE**, mai differenza delle mediane (26/07: con
quella correzione un verdetto si ribalto'). Ogni offset e' valutato con ENTRAMBE le
configurazioni (candidato e LIVE) alla STESSA ancora e sugli STESSI indici di bootstrap.
* Lente **ACCOPPIATA** ovunque (su una regola di DD a UN GIORNO la close-only e' cieca).
* Pesi FISSI, mai ri-ottimizzati per offset: ri-cercare l'argmax a ogni offset sarebbe un
max-of-23, cioe' esattamente il difetto sotto audit. La leva si', ma su ENTRAMBI i lati.
* de-luck sul drift x0,89 tenuto **per comparabilita' con PROP-RECAL**. ⚠️ Su questa dimensione
e' un DOPPIO CONTEGGIO conservativo: lo 0,89 fu misurato (26/07) proprio come rapporto
mediana-ancore / ancora-canonica del drift. §6 rigira la misura a fattore 1,00.
* Universo XS01 = **U13 Binance** (la versione piu' DEBOLE, conservativa), finestra **W-LONG
2021+**, meccanismo congelato. Identici a PROP-RECAL.
COSA QUESTA MISURA NON TOGLIE (dichiarato, non nascosto)
--------------------------------------------------------
L'ancora giornaliera di **TP01** (24 ore possibili) resta canonica 00:00 UTC, che il progetto
misura come la migliore delle 24 per l'hold-out di TP01. E' una TERZA dimensione, fuori dal
perimetro della gamba (b), e la sua fortuna resta dentro questi numeri.
ESITO (2026-08-22, corsa completa 391 s, 5.127 celle dichiarate)
----------------------------------------------------------------
* RIPRODUZIONE all'ancora canonica **7/7 esatta** (J LIVE 0,281 · fase 0 0,472 · mediana 0,453 ·
banda [0,431 0,488] · delta +0,175 · 10/10 fasi) e **3 controlli bit-exact a 0,0** (gambe orarie
a off=0 contro `pc._hourly_legs()` su 4 colonne; costruttore di libro contro `PR.rg` a due pesi).
* **GAMBA (b): PASS.** Mediana delle differenze appaiate sui 23 offset = **+0,1162** contro la
soglia pre-registrata +0,05; banda p10-p90 **[+0,087, +0,152]**; **23/23 offset positivi**,
**230/230 celle** dello spazio congiunto positive. La regione tiene tutta: 38/38/25 +0,116 ·
38/25/38 +0,120 · 25/38/38 +0,109.
* **La fortuna d'ancora c'e' ed e' quella attesa, ma non e' dove si temeva.** L'offset canonico 0
sta al **91° pctl** dei 23 su tutte e tre le grandezze e regala **+0,059 sul delta**, +0,112 sul
livello J del candidato, +0,050 su quello del LIVE. Il delta e' MENO ancorato dei livelli perche'
il LIVE contiene SKH01 al 25% e la fortuna si cancella in parte nella differenza appaiata.
* 📌 **IL RISULTATO INTERPRETATIVO: il vantaggio NON e' un tilt su SKH01.** A `38/25/38` SKH01 sta
al **medesimo peso del LIVE (25%)**, quindi la sua ancora si cancella quasi esattamente — e li'
il delta e' il **piu' alto dei tre (+0,120)** con la banda **piu' stretta** (ampiezza 0,032
contro 0,065 di W_A e 0,057 di W_C, che portano SKH a 38%). **L'ampiezza della banda scala col
DIFFERENZIALE di peso su SKH01**, esattamente come previsto prima di misurare. Cio' che paga e'
XS01, non un sovrappeso dello sleeve fragile.
* ⚠️ **Il numero OPERATIVO invece si dimezza, e per una ragione diversa dal gate.** P(>=50 EUR/g)
da EUR 600 in 36 mesi: mediana della banda d'ancora **4,4% [p10-p90 2,5%-8,2%]** contro il
**7,8%** pubblicato; P(zero) **35,3%** contro 25,5%. All'ancora canonica la macchina ridice
**7,7%** (= replica di PROP-RECAL): il crollo e' la fortuna d'ancora, non un disaccordo.
**Il gate e' su una DIFFERENZA (dove l'ancora si cancella in parte), il numero operativo e' un
LIVELLO (dove non si cancella affatto) — passano insieme e si muovono in modo diverso.**
* ⚠️ **Il surrogato del 22/08 e' finito vicino al vero per la CANCELLAZIONE DI DUE ASIMMETRIE.**
Ricalcolato a peso FISSO il taglio del drift da' **+0,076 (x0,50)** e **+0,045 (x0,33)**, cioe'
SOTTOSTIMA il vero (+0,116); ma PROP-RECAL lo calcolava ri-cercando l'argmax sui 10 pesi
migliori mentre il LIVE resta inchiodato a 75/25 — sotto stress il candidato puo' scappare da
SKH01 e il LIVE no -> il surrogato si gonfia a +0,124/+0,113. **Due errori in versi opposti che
quasi si annullano: il numero era giusto, il metodo no.**
* Sensibilita' dichiarata: a de-luck **x1,00** (senza il doppio conteggio dell'ancora) il delta
sale a **+0,148**, 23/23 positivi -> tenere lo 0,89 e' conservativo su questa dimensione.
* Banda del maxDD del candidato sui 23 offset: **-8,5% ... -13,0%**, mediana **-10,8%** (il
pubblicato a peso 25% era -9,6%): su un conto a barriera la coda peggiora quanto il livello.
* **RESTA FUORI, dichiarato:** l'ancora giornaliera di **TP01** (24 ore) e' una TERZA dimensione,
fuori dal perimetro della gamba (b), e la sua fortuna e' ancora dentro questi numeri.
Uso: `nice -n 19 timeout 900 uv run python scripts/research/r0822c_skh_anchor_prop.py`
Rete: NESSUNA (parquet certificati + cache Binance di r0822b_xs01_oos).
"""
from __future__ import annotations
import sys
import time
from pathlib import Path
import numpy as np
import pandas as pd
ROOT = Path(__file__).resolve().parents[2]
for _p in (ROOT, ROOT / "scripts" / "research", ROOT / "scripts" / "research" / "alt"):
sys.path.insert(0, str(_p))
import r0702_anchor_skh01 as AN # noqa: E402 resample con fase spostata (02/07)
import r0724_goal50_intraday_mc as mc # noqa: E402 recon orario per-sleeve (24/07)
import r0725_prop_coupled as pc # noqa: E402 lente accoppiata (25/07)
import r0725_prop_ladder as pl # noqa: E402 regole firm + scala di conti
import r0822_prop_alloc as PA # noqa: E402 J, bootstrap, simulatore (22/08)
import r0822b_prop_recal as PR # noqa: E402 IL file di cui giro la gamba (b)
import r0822b_xs01_oos as X # noqa: E402 motore XS01 + cache Binance
from src.data.downloader import load_data # noqa: E402
from src.strategies.skyhook import HTF_MIN, LTF_MIN # noqa: E402
ASSETS = ("BTC", "ETH")
OFFSETS = tuple(range(0, 690, 30)) # 23 offset a priori, identici a r0702 (0 = canonico)
PHASES = tuple(range(10)) # 10 fasi del ciclo H=10 di XS01
PHASES_SUB = (0, 3, 7) # sottocampione a priori per la scala di conti
LEVS = PR.LEVS
N_PATHS = PA.N_PATHS
EV_H = FU_H = 365
SEED_EV, SEED_FU = 8220, 8221
HYRO = "HYRO"
GATE_B = 0.05 # soglia PRE-REGISTRATA da PROP-RECAL
# pesi FISSI che coprono la regione robusta w_XS in [25%, 38%] (griglia simplesso a passo 1/8)
W_LIVE = {"TP01": 0.75, "SKH01": 0.25}
W_A = {"TP01": 0.38, "SKH01": 0.38, "XS01": 0.25} # centro dichiarato da PROP-RECAL (headline)
W_B = {"TP01": 0.38, "SKH01": 0.25, "XS01": 0.38} # w_XS all'estremo alto, TP-pesante
W_C = {"TP01": 0.25, "SKH01": 0.38, "XS01": 0.38} # w_XS all'estremo alto, SKH-pesante
# numeri PUBBLICATI da riprodurre prima di produrne di nuovi (RESULTS-0822 §29)
PUB = dict(j_med=0.453, j_ph0=0.472, j_p10=0.431, j_p90=0.488, j_live=0.281,
dmed=0.175, npos=10, surr_50=0.124, surr_33=0.113, p50g=0.078)
TRIALS = 0
def bump(n: int = 1) -> None:
global TRIALS
TRIALS += n
def hr(t: str = "") -> None:
print("\n" + "-" * 104)
if t:
print(f" {t}")
print("-" * 104)
# ==================================================================== 1. GAMBE ORARIE ALL'OFFSET
_LEGS: dict[int, pd.DataFrame] = {}
def hourly_legs_off(off: int) -> pd.DataFrame:
"""`pc._hourly_legs()` con la griglia 230m/690m di SKH01 spostata di `off` minuti.
NON riscrive la macchineria: rimpiazza temporaneamente `mc.build_frames` con il resample
a fase spostata di `r0702_anchor_skh01` e richiama `mc.skh_hourly_contrib` — cosi' la
logica trade-per-trade (entry a close del bin, exit AL LIVELLO, SL prioritario, fee
nell'ora d'uscita) resta letteralmente quella del 24/07. La fedelta' della sostituzione e'
provata dal controllo bit-exact a off=0 (§1).
La gamba TP01 NON viene toccata: l'ancora di TP01 e' un'ALTRA dimensione (24 ore giornaliere)
e la gamba (b) del gate riguarda i 23 offset di SKH01.
"""
if off in _LEGS:
return _LEGS[off]
tp_r = tp_w = sk_r = sk_w = None
orig = mc.build_frames
def patched(df5: pd.DataFrame, _off: int = off):
ltf = AN.resample_off(df5, LTF_MIN, _off)
htf = AN.resample_off(df5, HTF_MIN, _off)
# confini HTF sottoinsieme dei LTF (690 = 3 x 230): la stessa guardia di r0702.
assert np.isin(htf["timestamp"].values[1:], ltf["timestamp"].values).all(), \
f"confini HTF non sottoinsieme dei LTF (off={_off})"
return ltf, htf
mc.build_frames = patched
try:
for a in ASSETS:
df = load_data(a, "1h")
idx = pd.DatetimeIndex(pd.to_datetime(df["datetime"], utc=True))
close = df["close"].values.astype(float)
low = df["low"].values.astype(float)
high = df["high"].values.astype(float)
prev = np.concatenate(([np.nan], close[:-1]))
r_close = close / prev - 1.0
tpf = mc.tp01_daily_frame(a)
day_of = idx.floor("D")
w_tp = np.nan_to_num(tpf["w"].reindex(day_of).values)
fee_tp = np.nan_to_num(tpf["fee"].reindex(day_of).values)
first = np.concatenate(([True], day_of[1:] != day_of[:-1]))
r_wick = np.where(w_tp >= 0, low / prev - 1.0, high / prev - 1.0)
t_r = pd.Series(w_tp * r_close - np.where(first, fee_tp, 0.0), index=idx)
t_w = pd.Series(w_tp * r_wick - np.where(first, fee_tp, 0.0), index=idx)
sa, sb = mc.skh_hourly_contrib(a, idx, close, low, high)
s_r = pd.Series(sa / mc.W_SKH, index=idx)
s_w = pd.Series(sb / mc.W_SKH, index=idx)
tp_r = t_r if tp_r is None else tp_r.add(t_r, fill_value=np.nan)
tp_w = t_w if tp_w is None else tp_w.add(t_w, fill_value=np.nan)
sk_r = s_r if sk_r is None else sk_r.add(s_r, fill_value=np.nan)
sk_w = s_w if sk_w is None else sk_w.add(s_w, fill_value=np.nan)
finally:
mc.build_frames = orig
J = pd.concat({"tp_r": tp_r, "tp_w": tp_w, "skh_r": sk_r, "skh_w": sk_w}, axis=1).dropna()
_LEGS[off] = J
return J
_CRY: dict = {}
def crypto_off(off: int, w_tp: float, w_skh: float) -> pd.DataFrame:
"""`pc.crypto_daily_tuples` con le gambe orarie all'offset `off` (minimo ESATTO sul path
orario condiviso: si compone l'ora per ora e si guarda il minimo, wick incluso)."""
key = (off, round(w_tp, 6), round(w_skh, 6))
if key in _CRY:
return _CRY[key]
J = hourly_legs_off(off)
r_h = (w_tp * J["tp_r"] + w_skh * J["skh_r"]).values
w_h = (w_tp * J["tp_w"] + w_skh * J["skh_w"]).values
idx = J.index
days = idx.floor("D")
rows = []
for day, pos in pd.Series(np.arange(len(idx)), index=days).groupby(level=0):
sl = pos.values
r = r_h[sl]
wk = w_h[sl]
cum = np.cumprod(1.0 + r)
cum_prev = np.concatenate(([1.0], cum[:-1]))
m = min(cum.min(), (cum_prev * (1.0 + wk)).min()) - 1.0
rows.append((day, cum[-1] - 1.0, min(m, cum[-1] - 1.0)))
out = pd.DataFrame(rows, columns=["day", "R", "m"]).set_index("day")
_CRY[key] = out
return out
def rg_off(w: dict, idx: pd.DatetimeIndex, xs: pd.DataFrame, off: int) -> tuple:
"""`PR.rg` con il sub-book crypto all'offset `off`. Validato bit-exact a off=0 in §1."""
w_tp, w_sk = float(w.get("TP01", 0.0)), float(w.get("SKH01", 0.0))
w_xs = float(w.get("XS01", 0.0))
R = np.zeros(len(idx))
M = np.zeros(len(idx))
if w_tp > 0 or w_sk > 0:
c = crypto_off(off, w_tp, w_sk).reindex(idx)
R += np.nan_to_num(c["R"].values)
M += np.nan_to_num(c["m"].values)
if w_xs > 0:
f = xs.reindex(idx)
R += w_xs * np.nan_to_num(f["R"].values)
M += w_xs * np.nan_to_num(f["m"].values)
return R, np.minimum(M, R) - R
def best_over_levers(R, G, ev, fu, factor: float = PA.DELUCK) -> tuple[float, float, np.ndarray]:
"""(J, leva, vettore per-percorso) alla leva che massimizza J. La ricerca sulla leva e'
applicata SIMMETRICAMENTE al candidato e al LIVE (convenzione di PROP-RECAL)."""
jb, lb, vb = -1.0, None, None
for lv in LEVS:
o = PR.obj_rg(R, G, lv, ev, fu, factor=factor)
if o["J"] > jb:
jb, lb, vb = o["J"], lv, o["jvec"]
bump(len(LEVS))
return jb, lb, vb
# ==================================================================== main
def main() -> None:
try:
sys.stdout.reconfigure(line_buffering=True)
except Exception: # pragma: no cover
pass
t0 = time.time()
def tick(lab: str) -> None:
print(f" [{time.time()-t0:6.0f}s] {lab}")
print("=" * 104)
print(" SKH-ANCHOR-PROP — la gamba (b) del `GATE PROP-01`, girata invece che aspettata")
print("=" * 104)
print(" DOMANDA: la mediana delle differenze appaiate J(regione w_XS 25-38%) - J(LIVE 75/25)")
print(f" sui 23 offset della griglia 230m/690m di SKH01, W-LONG, supera +{GATE_B:.2f}?")
print(" ASPETTATIVA REGISTRATA PRIMA DI MISURARE (mia, non ereditata): mi aspetto un PASS ma")
print(" con una taglia SENSIBILMENTE minore del surrogato (+0.124/+0.113), per un motivo")
print(" strutturale: il LIVE 75/25 contiene SKH01 al 25% e il candidato al 38%, quindi la")
print(" fortuna d'ancora si cancella in PARTE nella differenza appaiata — il residuo pesa")
print(" ~13 punti di peso, non 38. Se il delta uscisse >= al surrogato, il surrogato non era")
print(" conservativo e lo dico.")
# ============================================ §0. RIPRODUZIONE
hr("§0. RIPRODUZIONE — la macchina deve dare i numeri di PROP-RECAL prima di darne di nuovi")
Cb, Ob, Hb, Lb = PR.ohlc_panels(PR.bin_loader, PR.XS_UNIVERSE)
p13 = Cb[X.U13].dropna()
o13 = Ob.reindex(p13.index)[X.U13]
h13 = Hb.reindex(p13.index)[X.U13]
l13 = Lb.reindex(p13.index)[X.U13]
XSP = {ph: PR.xs_tuples(p13, o13, h13, l13, phase=ph) for ph in PHASES}
bump(len(PHASES))
cry_idx = pc.crypto_daily_tuples(1.0, 0.0).index
base_idx = cry_idx.intersection(XSP[0].index)
first13 = XSP[0].index[np.flatnonzero(XSP[0]["R"].values != 0.0)[0]]
I_LONG = base_idx[base_idx >= max(first13, pd.Timestamp("2021-01-01", tz="UTC"))]
ev = PA.boot_idx(len(I_LONG), EV_H, N_PATHS, seed=SEED_EV)
fu = PA.boot_idx(len(I_LONG), FU_H, N_PATHS, seed=SEED_FU)
print(f" W-LONG: {len(I_LONG)} giorni {I_LONG[0].date()} -> {I_LONG[-1].date()} | "
f"de-luck x{PA.DELUCK} | {N_PATHS} percorsi | semi {SEED_EV}/{SEED_FU} | firm {HYRO}")
RL0, GL0 = PR.rg(W_LIVE, I_LONG, XSP[0])
jL0, lvL0, vL0 = best_over_levers(RL0, GL0, ev, fu)
js0, ds0 = [], []
for ph in PHASES:
Rp, Gp = PR.rg(W_A, I_LONG, XSP[ph])
jb, _, vb = best_over_levers(Rp, Gp, ev, fu)
js0.append(jb)
ds0.append(float((vb - vL0).mean()))
print(f" {'grandezza':>42} {'mio':>9} {'PROP-RECAL':>11} esito")
checks = [("J libro LIVE 75/25 (ancora canonica)", jL0, PUB["j_live"]),
("J candidato 38/38/25 fase 0", js0[0], PUB["j_ph0"]),
("J candidato MEDIANA su 10 fasi", float(np.median(js0)), PUB["j_med"]),
("banda di fase p10", float(np.percentile(js0, 10)), PUB["j_p10"]),
("banda di fase p90", float(np.percentile(js0, 90)), PUB["j_p90"]),
("delta appaiato MEDIANO (10 fasi)", float(np.median(ds0)), PUB["dmed"])]
ok = True
for lab, mine, pub in checks:
good = abs(mine - pub) <= 0.005
ok &= good
print(f" {lab:>42} {mine:>9.3f} {pub:>11.3f} {'OK' if good else '<-- DIVERGE'}")
npos0 = sum(1 for d in ds0 if d > 0)
print(f" {'fasi con delta > 0':>42} {npos0:>9d} {PUB['npos']:>11d} "
f"{'OK' if npos0 == PUB['npos'] else '<-- DIVERGE'}")
ok &= npos0 == PUB["npos"]
print(f"\n >>> RIPRODUZIONE ALL'ANCORA CANONICA: "
f"{'OK — si procede' if ok else 'FALLITA — questo E il risultato'}")
if not ok:
print(" La macchina non riproduce PROP-RECAL: qualunque numero sugli offset sarebbe di")
print(" un'altra macchina. Mi fermo qui, come impone la disciplina di riproduzione.")
return
tick("§0 riproduzione")
# ============================================ §1. LA MACCHINERIA ALL'OFFSET
hr("§1. LA MACCHINERIA ALL'OFFSET — e il controllo che prova che la sostituzione e' fedele")
t = time.time()
J0 = hourly_legs_off(0)
(Jref,) = pc._hourly_legs()
same_idx = J0.index.equals(Jref.index)
dmax = max(float(np.abs(J0[c].values - Jref[c].values).max()) for c in J0.columns)
print(f" gambe orarie a off=0 contro `pc._hourly_legs()`: n={len(J0)} indice identico="
f"{same_idx} max|dif| su 4 colonne = {dmax:.2e} -> "
f"{'BIT-EXACT' if (same_idx and dmax == 0.0) else 'DIVERGE — STOP'}")
if not (same_idx and dmax == 0.0):
print(" La sostituzione di `build_frames` non riproduce il canonico: mi fermo.")
return
for wt in (W_A, W_LIVE):
Ra, Ga = rg_off(wt, I_LONG, XSP[0], 0)
Rb, Gb = PR.rg(wt, I_LONG, XSP[0])
d1 = float(np.abs(Ra - Rb).max())
d2 = float(np.abs(Ga - Gb).max())
print(f" costruttore di libro a off=0 contro `PR.rg` ({PA.wkey(wt)}): "
f"max|dR| {d1:.2e} max|dgap| {d2:.2e} -> "
f"{'BIT-EXACT' if max(d1, d2) == 0.0 else 'DIVERGE'}")
if max(d1, d2) != 0.0:
return
print(f" costo misurato: {time.time()-t:.1f}s per la prima cella (import+cache inclusi)")
t = time.time()
for off in OFFSETS:
hourly_legs_off(off)
assert hourly_legs_off(off).index.equals(J0.index), \
f"indice orario diverso a off={off}: il bootstrap non sarebbe appaiato"
print(f" 23 offset costruiti in {time.time()-t:.0f}s — l'indice giornaliero e' lo STESSO a")
print(" ogni offset, quindi gli indici di bootstrap sono condivisi e il confronto e' appaiato.")
print(" ⚠️ La data 2026-10-31 era un budget di calcolo ancorato al costo di `skh_sigcache`")
print(" (segnale intra-bin, ~9 min/cella), che questa misura non usa.")
n_tr = {}
for off in (0, 330, 660):
J = hourly_legs_off(off)
n_tr[off] = int((J["skh_r"].values != 0.0).sum())
print(f" ore con contributo SKH01 non nullo: " +
" · ".join(f"off{o}: {v}" for o, v in n_tr.items()) +
" (griglie diverse, non una traslazione)")
tick("§1 macchineria")
# ============================================ §2. LA GRIGLIA CONGIUNTA
hr("§2. LA GRIGLIA CONGIUNTA 23 OFFSET x 10 FASI — la gamba (b), headline W_A = 38/38/25")
print(" Per ogni offset: J del LIVE (che contiene SKH01 al 25% e quindi si muove anch'esso)")
print(" e J del candidato a ciascuna delle 10 fasi di XS01, alla leva che massimizza J.")
print(" Il delta e' appaiato sui MEDESIMI indici di bootstrap; il collasso sulle fasi e' la")
print(" MEDIANA (la stima onesta di PROP-RECAL), non la fase 0 (fortunata di +0.019).")
D = np.zeros((len(OFFSETS), len(PHASES)))
JC = np.zeros((len(OFFSETS), len(PHASES)))
JL = np.zeros(len(OFFSETS))
LVL = np.zeros(len(OFFSETS))
DDC = np.zeros((len(OFFSETS), len(PHASES)))
DDL = np.zeros(len(OFFSETS))
t = time.time()
print(f"\n {'off':>4} {'J LIVE':>7} {'levL':>5} {'J cand fase0':>12} {'MEDIANA':>8} "
f"{'p10-p90':>15} {'delta MEDIANO':>13} {'fasi>0':>7} {'maxDD cand':>10}")
for i, off in enumerate(OFFSETS):
RL, GL = rg_off(W_LIVE, I_LONG, XSP[0], off)
jL, lvL, vL = best_over_levers(RL, GL, ev, fu)
JL[i], LVL[i] = jL, lvL
DDL[i] = PR.maxdd(PA.deluck(RL))
for k, ph in enumerate(PHASES):
Rc, Gc = rg_off(W_A, I_LONG, XSP[ph], off)
jc, _, vc = best_over_levers(Rc, Gc, ev, fu)
JC[i, k] = jc
DDC[i, k] = PR.maxdd(PA.deluck(Rc))
D[i, k] = float((vc - vL).mean())
print(f" {off:>4} {jL:>7.3f} {lvL:>5.2f} {JC[i,0]:>12.3f} {np.median(JC[i]):>8.3f} "
f"[{np.percentile(JC[i],10):>6.3f},{np.percentile(JC[i],90):>6.3f}] "
f"{np.median(D[i]):>+13.3f} {int((D[i] > 0).sum()):>5}/10 "
f"{np.median(DDC[i])*100:>9.1f}%")
print(f" ({time.time()-t:.0f}s per 23 x (1 LIVE + 10 fasi) x 8 leve)")
d_per_off = np.median(D, axis=1) # collasso onesto sulle fasi
d_gate = float(np.median(d_per_off)) # <-- IL NUMERO DELLA GAMBA (b)
d_ph0 = float(np.median(D[:, 0])) # la scelta comoda, per confronto
print(f"\n {'':>52}{'mediana':>9}{'p10':>9}{'p90':>9}{'>0':>8}")
print(f" {'delta appaiato per OFFSET (fasi collassate a mediana)':>52}"
f"{d_gate:>+9.3f}{np.percentile(d_per_off,10):>+9.3f}"
f"{np.percentile(d_per_off,90):>+9.3f}{int((d_per_off>0).sum()):>5}/23")
print(f" {'delta appaiato, spazio CONGIUNTO 230 celle':>52}"
f"{np.median(D):>+9.3f}{np.percentile(D,10):>+9.3f}"
f"{np.percentile(D,90):>+9.3f}{int((D>0).sum()):>4}/230")
print(f" {'delta a fase 0 fissa (la scelta COMODA)':>52}"
f"{d_ph0:>+9.3f}{np.percentile(D[:,0],10):>+9.3f}"
f"{np.percentile(D[:,0],90):>+9.3f}{int((D[:,0]>0).sum()):>5}/23")
print(f" {'ancora canonica (off 0, fase mediana) = PROP-RECAL':>52}"
f"{np.median(D[0]):>+9.3f}{'':>9}{'':>9}{int((D[0]>0).sum()):>5}/10")
print(f"\n Fortuna d'ancora di SKH01 SOTTO LA LENTE PROP (canonico - mediana):")
print(f" sul DELTA : {np.median(D[0]) - d_gate:+.3f} "
f"(l'offset 0 sta al {(d_per_off < d_per_off[0]).mean()*100:.0f}° pctl dei 23)")
print(f" sul LIVELLO J del candidato : {np.median(JC[0]) - np.median(np.median(JC,axis=1)):+.3f} "
f"(off 0 al {(np.median(JC,axis=1) < np.median(JC[0])).mean()*100:.0f}° pctl)")
print(f" sul LIVELLO J del LIVE : {JL[0] - np.median(JL):+.3f} "
f"(off 0 al {(JL < JL[0]).mean()*100:.0f}° pctl)")
print(" ⚠️ La fortuna si cancella in PARTE perche' il LIVE contiene SKH01 al 25%: il delta")
print(" e' meno ancorato dei livelli. E' la ragione per cui la statistica giusta e' il")
print(" DELTA APPAIATO e non il livello.")
tick("§2 griglia congiunta")
# ============================================ §3. LA REGIONE, NON IL PUNTO
hr("§3. LA REGIONE w_XS 25-38% — il gate parla di una REGIONE, non di un punto")
print(" Tre pesi FISSI dichiarati che coprono la regione (nessuna ri-ottimizzazione per")
print(" offset: sarebbe un max-of-23, cioe' il difetto sotto audit). W_A gira sulle 10 fasi")
print(f" piene; i due pesi di sensibilita' sul sottocampione a priori {PHASES_SUB} (budget).")
print(f"\n {'peso':>12} {'w_XS':>5} {'J mediano':>10} {'delta MEDIANO':>13} "
f"{'p10':>8} {'p90':>8} {'offset>0':>9} {'celle>0':>9} {'gate':>6}")
reg = {}
for lab, wt, phs in (("38/38/25", W_A, PHASES), ("38/25/38", W_B, PHASES_SUB),
("25/38/38", W_C, PHASES_SUB)):
if wt is W_A:
Dw = D
Jw = JC
else:
Dw = np.zeros((len(OFFSETS), len(phs)))
Jw = np.zeros((len(OFFSETS), len(phs)))
for i, off in enumerate(OFFSETS):
RL, GL = rg_off(W_LIVE, I_LONG, XSP[0], off)
_, _, vL = best_over_levers(RL, GL, ev, fu)
for k, ph in enumerate(phs):
Rc, Gc = rg_off(wt, I_LONG, XSP[ph], off)
jc, _, vc = best_over_levers(Rc, Gc, ev, fu)
Jw[i, k] = jc
Dw[i, k] = float((vc - vL).mean())
dpo = np.median(Dw, axis=1)
dm = float(np.median(dpo))
reg[lab] = dm
print(f" {lab:>12} {int(round(100*wt['XS01'])):>4}% {np.median(Jw):>10.3f} "
f"{dm:>+13.3f} {np.percentile(dpo,10):>+8.3f} {np.percentile(dpo,90):>+8.3f} "
f"{int((dpo>0).sum()):>7}/23 {int((Dw>0).sum()):>6}/{Dw.size} "
f"{'PASS' if dm > GATE_B else 'FAIL':>6}")
worst = min(reg.values())
print(f"\n Peggiore della regione: {min(reg, key=reg.get)} con {worst:+.3f}; "
f"mediana dei tre {np.median(list(reg.values())):+.3f}")
tick("§3 regione")
# ============================================ §4. SURROGATO vs MISURA
hr("§4. IL SURROGATO CONTRO LA MISURA — quanto valeva il taglio del drift")
print(" PROP-RECAL surrogava la banda d'ancora con un taglio del DRIFT di SKH01 (x0.50 e")
print(" x0.33) e leggeva +0.124 / +0.113. Qui lo stesso surrogato e' ricalcolato al peso")
print(" FISSO W_A e con lo stesso collasso di fase, cosi' il confronto e' appaiato.")
print(f"\n {'lente':>44} {'delta MEDIANO':>13}")
for lab, dmt in (("misura vera: 23 offset x 10 fasi", None),
("surrogato SKH x0.50 (ancora canonica)", (("SKH01", 0.50),)),
("surrogato SKH x0.33 (ancora canonica)", (("SKH01", 0.33),))):
if dmt is None:
print(f" {lab:>44} {d_gate:>+13.3f}")
continue
RLs, GLs = PR.rg(W_LIVE, I_LONG, XSP[0], dm=dmt)
_, _, vLs = best_over_levers(RLs, GLs, ev, fu)
dsx = []
for ph in PHASES:
Rs, Gs = PR.rg(W_A, I_LONG, XSP[ph], dm=dmt)
_, _, vs = best_over_levers(Rs, Gs, ev, fu)
dsx.append(float((vs - vLs).mean()))
print(f" {lab:>44} {np.median(dsx):>+13.3f}")
print("\n Un surrogato che taglia il DRIFT non e' la stessa cosa di una griglia diversa:")
print(" cambiare l'ancora cambia QUALI trade esistono, non solo quanto rendono.")
tick("§4 surrogato")
# ============================================ §5. IL NUMERO OPERATIVO
hr("§5. IL NUMERO OPERATIVO — P(>=50 EUR/g) da EUR 600 in 36 mesi, sulla banda d'ancora")
print(f" Sottocampione DICHIARATO: 23 offset x {len(PHASES_SUB)} fasi {PHASES_SUB} (uniformi a")
print(" priori sul ciclo H=10) = 69 simulazioni da 1500 percorsi. La scala di conti costa")
print(" ~1.7 s a simulazione: la griglia piena 23x10 costerebbe ~7 min in piu' e la mediana")
print(" di 69 estrazioni basta per una banda al punto percentuale.")
pc.DELUCK = PA.DELUCK
W3 = PA.CRYPTO_PROP
pt, pz, med_eur = [], [], []
t = time.time()
if time.time() - t0 < 620:
for i, off in enumerate(OFFSETS):
for ph in PHASES_SUB:
Rc, Gc = rg_off(W_A, I_LONG, XSP[ph], off)
jc, lvc, _ = best_over_levers(Rc, Gc, ev, fu)
cfg = tuple(W_A.get(nm, 0.0) for nm in W3)
key = (cfg[0], cfg[1], cfg[2], "shared")
saved = pc._TUP_CACHE.pop(key, None)
pc._TUP_CACHE[key] = (Rc, Gc, I_LONG)
r = pc.simulate([cfg], [0] * pl.MAX_CONCURRENT, n_paths=1500, lev=lvc,
seed=20260822, lens="coupled", start=str(I_LONG[0].date()))
pc._TUP_CACHE.pop(key, None)
if saved is not None:
pc._TUP_CACHE[key] = saved
pt.append(r["p_target"]); pz.append(r["p_zero"]); med_eur.append(r["med"])
bump(len(OFFSETS) * len(PHASES_SUB))
print(f"\n P(>=50 EUR/g) su {len(pt)} celle (offset x fase): mediana "
f"{np.median(pt):.1%} banda p10-p90 [{np.percentile(pt,10):.1%}, "
f"{np.percentile(pt,90):.1%}] min {min(pt):.1%} max {max(pt):.1%}")
print(f" P(zero) mediana {np.median(pz):.1%} | EUR/g mediana {np.median(med_eur):.2f}")
print(f" All'ancora CANONICA (off 0, fasi {PHASES_SUB}): "
f"{np.median(pt[:len(PHASES_SUB)]):.1%}")
print(f" PROP-RECAL pubblicava {PUB['p50g']:.1%} [7.2-9.1%] (ancora canonica, 10 fasi)")
print(f" ({time.time()-t:.0f}s)")
else:
print("\n NON GIRATO: budget di tempo esaurito prima di §5. Si dichiara, non si stima.")
pt = []
tick("§5 numero operativo")
# ============================================ §6. SENSIBILITA' AL DE-LUCK
hr("§6. SENSIBILITA' — il de-luck x0.89 su questa dimensione e' un doppio conteggio")
print(" Lo 0.89 fu MISURATO (26/07) come rapporto mediana-ancore / ancora-canonica del")
print(" drift: applicarlo mentre si gira la banda d'ancora conta due volte la stessa")
print(" correzione. E' tenuto per comparabilita' con PROP-RECAL; qui la stessa misura a")
print(" fattore 1.00 (nessun de-luck), sulle stesse 23 x 3 celle.")
d_nd = []
for i, off in enumerate(OFFSETS):
RL, GL = rg_off(W_LIVE, I_LONG, XSP[0], off)
_, _, vL = best_over_levers(RL, GL, ev, fu, factor=1.0)
row = []
for ph in PHASES_SUB:
Rc, Gc = rg_off(W_A, I_LONG, XSP[ph], off)
_, _, vc = best_over_levers(Rc, Gc, ev, fu, factor=1.0)
row.append(float((vc - vL).mean()))
d_nd.append(float(np.median(row)))
print(f"\n delta appaiato mediano de-luck x0.89 (headline) : {d_gate:+.3f}")
print(f" de-luck x1.00 (no double) : {np.median(d_nd):+.3f}"
f" ({int(sum(1 for d in d_nd if d > 0))}/23 > 0)")
print(" Il verso di questa differenza dice se la scelta conservativa lo era davvero.")
tick("§6 sensibilita'")
# ============================================ §7. VERDETTO
hr("§7. VERDETTO SULLA GAMBA (b)")
passed = d_gate > GATE_B
print(f" Metrica pre-registrata: mediana delle differenze appaiate J(w_XS 25-38%) - J(LIVE)")
print(f" sui 23 offset, W-LONG. Soglia: > +{GATE_B:.2f}")
print(f"\n MISURA (headline W_A 38/38/25, fasi collassate a mediana): {d_gate:+.4f}")
print(f" banda p10-p90 sui 23 offset: [{np.percentile(d_per_off,10):+.3f}, "
f"{np.percentile(d_per_off,90):+.3f}] offset positivi: "
f"{int((d_per_off>0).sum())}/23 celle positive: {int((D>0).sum())}/230")
print(f" regione: peggiore {worst:+.3f} ({min(reg, key=reg.get)}), "
f"mediana dei tre pesi {np.median(list(reg.values())):+.3f}")
print(f"\n >>> GAMBA (b): {'PASS' if passed else 'FAIL'} "
f"({d_gate:+.4f} {'>' if passed else '<='} +{GATE_B:.2f})")
if passed:
print(" L'aspettativa registrata da PROP-RECAL (un PASS) e' confermata.")
else:
print(" L'aspettativa registrata da PROP-RECAL (un PASS) e' SMENTITA: il vantaggio")
print(" era in parte calendario.")
if pt:
print(f"\n COSA RESTA del risultato funded, ri-ancorato: P(>=50 EUR/g) mediana "
f"{np.median(pt):.1%} [banda d'ancora {np.percentile(pt,10):.1%}-"
f"{np.percentile(pt,90):.1%}] contro il {PUB['p50g']:.1%} pubblicato.")
print(f"\n RESTA FUORI (dichiarato): l'ancora giornaliera di TP01 (24 ore) — terza dimensione,")
print(" non nel perimetro della gamba (b), e la sua fortuna e' ancora dentro questi numeri.")
print(f"\n TRIAL/CELLE DICHIARATI (al rialzo): {TRIALS}")
print(f" fatto in {time.time()-t0:.0f}s")
print("=" * 104)
if __name__ == "__main__":
main()