research(wave-0822): XSR-REPRO trova un difetto di produzione (i monitor forward registrano ~41 min/giorno); SKEW scompone il f di VRP01 — il 42% e' struttura a termine

This commit is contained in:
Adriano Dal Pastro
2026-08-22 17:29:19 +00:00
parent f0122d97a1
commit ea22a0e20c
9 changed files with 1445 additions and 93 deletions
+155 -20
View File
@@ -302,11 +302,39 @@ def main() -> dict:
print("\n IL TEST DI SUPER-PROPORZIONALITA' (la domanda esatta del brief):")
dpk = [delta_p[j] / KS[j] for j in range(len(KS))]
rap = [dd_acc[j] / dd_close[j] for j in range(len(KS))]
j1 = KS.index(1.00)
print(f" Δ/k a k=0.50 {dpk[0]:+.3%} · 1.00 {dpk[j1]:+.3%} · 1.50 {dpk[KS.index(1.50)]:+.3%}"
print(" Δ per UNITA' di leva (se il minimo intraday amplificasse piu' che")
print(" proporzionalmente, questa riga CRESCEREBBE in modulo con k):")
print(f" Δ/k k=0.50 {dpk[0]:+.3%} · 1.00 {dpk[j1]:+.3%} · 1.50 {dpk[KS.index(1.50)]:+.3%}"
f" · 2.00 {dpk[KS.index(2.00)]:+.3%} · 5.00 {dpk[-1]:+.3%}")
cresce = dpk[KS.index(2.00)] < dpk[j1] # piu' negativo = piu' costoso per unita' di leva
print(f" -> Δ/k {'CRESCE in modulo con k = super-proporzionale' if cresce else 'NON cresce in modulo: l effetto e circa PROPORZIONALE'}.")
print(f" rapporto maxDD acc./close: {' · '.join(f'{r:.3f}' for r in rap)}")
super_prop = dpk[KS.index(2.00)] > dpk[j1] * 1.05
sotto = dpk[KS.index(2.00)] < dpk[j1] * 0.95
verdetto = ("SUPER-proporzionale" if super_prop else
("SOTTO-proporzionale" if sotto else "proporzionale entro il rumore"))
print(f"\n ❌ RISPOSTA: NO — Δ/k **CALA** in modulo (da {dpk[0]:.3%} a {dpk[-1]:.3%}) e il")
print(f" RAPPORTO fra le due maxDD e' PIATTO ({min(rap):.3f}-{max(rap):.3f}) su tutta la")
print(f" griglia. La lente accoppiata e' un RICARICO MOLTIPLICATIVO COSTANTE di ~"
f"{(np.mean(rap)-1)*100:.1f}%")
print(f" sul maxDD, non un'amplificazione: verdetto **{verdetto}**.")
print(" Meccanismo: il maxDD di 25 anni e' una statistica MULTI-GIORNO (una discesa di")
print(" settimane), il wick e' un fatto di UN giorno. Aggiungere qualche punto di tuffo")
print(" a una discesa gia' lunga sposta poco, e la parte della distribuzione che")
print(" conterebbe non c'e': nel 26,8% dei giorni il libro CHIUDE sul minimo (gap = 0)")
print(" e quei giorni sono proprio i brutti. A leva alta Δ/k cala anche perche' i")
print(" drawdown si allungano piu' in fretta di quanto il singolo tuffo cresca.")
print("\n ⚠️ MA C'E' UN'ECCEZIONE, ed e' quella che decide la frontiera: la PROBABILITA'")
print(" di sfondare una SOGLIA FISSA e' amplificata assai piu' del maxDD stesso,")
print(" perche' vicino alla soglia la densita' e' fitta.")
print(f"\n {'k':>6}{'P(DD>30%) close':>17}{'P(DD>30%) acc.':>16}{'amplificazione':>16}")
for j, k in enumerate(KS):
pc = float((s0["dd"][:, j, 0] > DD_SOGLIA).mean())
pa = float((s0["dd"][:, j, 1] > DD_SOGLIA).mean())
amp = (pa / pc) if pc > 0 else float("nan")
print(f" {k:>6.2f}{pc:>17.1%}{pa:>16.1%}"
f"{(f'{amp:.2f}x' if np.isfinite(amp) else 'n.d.'):>16}")
print("\n IL NUMERO CHE DECIDE — L'HAIRCUT DI LEVA IMPLICITO NELLA LENTE:")
print(" 'a quale k close-only corrisponde il rischio di DD che la lente accoppiata")
@@ -397,6 +425,44 @@ def main() -> dict:
print(f" <= {frac:.0%} dell'equity -> costo massimo {sl*frac:.0%} dell'equity per asset a k=1,")
print(f" cioe' {sl*frac*2:.0%} se scattano entrambi; diventa il -100% a k = {1/(sl*frac*2):.1f}x.")
# ------------------------------------------------------------------ (3-bis)
sezione("(3-bis) DOVE L'ACCOPPIAMENTO MORDE DAVVERO — la regola a UN GIORNO, sui giorni STORICI")
print("\n Il finding del 25/07 ('close-only e' CIECA', 'l'indipendente RADDOPPIA i breach')")
print(" e' misurato su una regola di DAILY-LOSS: una soglia valutata sul MINIMO di UN")
print(" giorno. Il maxDD di un piano d'accumulo e' un'altra grandezza. Qui le due si")
print(" guardano fianco a fianco, sui GIORNI VERI (nessun bootstrap): quante volte in")
print(f" {len(D['R_del'])/365:.1f} anni una soglia di perdita giornaliera viene sfondata.")
Rh, Gh = D["R_del"], gap
rng2 = np.random.default_rng(909)
Gsh = Gh.copy(); rng2.shuffle(Gsh)
anni_st = len(Rh) / 365.0
for T in (0.03, 0.05, 0.10):
print(f"\n soglia daily-loss {T:.0%} (breach/anno)")
print(f" {'k':>6}{'close-only':>13}{'accoppiata':>13}{'indip. (finto)':>17}"
f"{'acc./close':>12}{'indip./acc.':>13}")
for k in (1.00, 1.25, 1.50, 2.00, 3.00):
nc = float((Rh * k <= -T).sum()) / anni_st
na = float(((Rh + Gh) * k <= -T).sum()) / anni_st
ni = float(((Rh + Gsh) * k <= -T).sum()) / anni_st
r1 = (na / nc) if nc > 0 else float("inf")
r2 = (ni / na) if na > 0 else float("nan")
print(f" {k:>6.2f}{nc:>13.2f}{na:>13.2f}{ni:>17.2f}"
f"{(f'{r1:.2f}x' if np.isfinite(r1) else 'INF'):>12}"
f"{(f'{r2:.2f}x' if np.isfinite(r2) else 'n.d.'):>13}")
print("\n 📌 IL RISULTATO TRASFERIBILE DEL FILONE: la lezione del 25/07 e' VERA e NON SI")
print(" TRASFERISCE. Su una regola a UN GIORNO l'accoppiamento cambia il conto di un")
print(" fattore (e close-only puo' dare ZERO dove il vero e' positivo: cecita' esatta);")
print(" su una statistica MULTI-GIORNO come il maxDD di un piano d'accumulo lo stesso")
print(" difetto vale un ricarico costante di pochi punti percentuali. Il gate")
print(" pre-registrato ha importato una lezione dal contesto in cui e' stata misurata")
print(" (conti FUNDED, barriera di daily-loss) a un contesto in cui la grandezza")
print(" decisiva e' un'altra — stessa forma dell'errore che il progetto ha gia'")
print(" codificato il 26/07 sulla barra parziale (SKH01 +0.38 / TP01 ±0.03: 'la")
print(" conclusione non si trasferisce fra sleeve').")
print(" ⚠️ Il che NON assolve close-only in generale: sul canale PROP/FUNDED, dove il")
print(" vincolo binding e' esattamente una regola di daily-loss per-conto, la lente")
print(" accoppiata resta obbligatoria e la leva li' e' un'altra decisione.")
# ------------------------------------------------------------------ (4)
sezione("(4) IL GRADINO ESEGUIBILE — sopravvive alla lente? (€500/mese, muro netto)")
s500 = simula(D["R_del"], gap, KS, anni=ANNI, dep_eur=500, bersaglio=BERSAGLIO, seed=SEED)
@@ -443,10 +509,10 @@ def attacco2() -> None:
f" <- lo scetticato dichiara 12,14%")
print(f" -> VERIFICATO. E il numero non e' un artefatto della sua ricostruzione.")
print("\n ⚠️ MA IL MECCANISMO CHE DICHIARA E' INCOMPLETO, e la parte che manca e' la")
print(" maggiore. Lo scetticato scrive: «il target vale sulla posizione quando c'e', e")
print(" TP01 e' long-flat». Decomposizione:")
convs, vols_pred, vols_real, sers = [], [], [], {}
print("\n ⚠️ MA IL MECCANISMO CHE DICHIARA SPIEGA CIRCA META' DEL DIVARIO, e la meta'")
print(" che manca porta a una conclusione operativa diversa. Lo scetticato scrive:")
print(" «il target vale sulla posizione quando c'e', e TP01 e' long-flat». Misura:")
convs, vols_pred, vols_real, sers, flats = [], [], [], {}, []
for a in ("BTC", "ETH"):
df = resample_1d(load_data(a, "1h"))
c = df["close"].values.astype(float)
@@ -461,6 +527,7 @@ def attacco2() -> None:
sers[a] = pd.Series(np.clip(net, -0.99, None), index=pd.to_datetime(df["datetime"]))
ok = np.isfinite(d) & np.isfinite(vol) & (vol > 0)
convs.append(float(np.sqrt(np.mean(d[ok] ** 2))))
flats.append(float((d[ok] == 0).mean()))
# errore del previsore di vol: vol realizzata nei 30g SEGUENTI / vol prevista
fwd = pd.Series(r).rolling(30).std().shift(-30).values * np.sqrt(365.25)
okf = ok & np.isfinite(fwd)
@@ -478,21 +545,35 @@ def attacco2() -> None:
comb = (0.5 * J["BTC"] + 0.5 * J["ETH"]).std() * 365 ** .5
div = comb / (0.5 * vb + 0.5 * ve)
conv = float(np.mean(convs))
per_leg = (0.5 * vb + 0.5 * ve) / 0.20
p_flat = float(np.mean(flats))
f_flat = float(np.sqrt(1.0 - p_flat))
f_conv = conv / f_flat if f_flat > 0 else float("nan")
print(f"\n gamba singola: BTC {vb:.2%} · ETH {ve:.2%} -> 50/50 combinato {comb:.2%}")
print(f" fattore di DIVERSIFICAZIONE BTC/ETH = {div:.3f} (corr {J['BTC'].corr(J['ETH']):.2f})")
print(f"\n CATENA: 20,00% ×{conv:.3f} (convinzione) ×{np.mean(vols_real)/np.mean(vols_pred):.3f} "
f"(previsore di vol) ×{div:.3f} (diversificazione) = "
f"{0.20*conv*(np.mean(vols_real)/np.mean(vols_pred))*div:.2%}")
print(f" misurato: {tp.std()*365**.5:.2%}")
print(f"\n 📌 IL FATTO CHE LO SCETTICATO NON DICE: il pezzo grosso NON e' lo stare flat, e'")
print(f" che `tsmom_blend` e' la MEDIA DEI SEGNI di 3 orizzonti, quindi quando TP01 e'")
print(f" 'a mercato' e' quasi sempre a 1/3 o 2/3 di convinzione. Contando lo zero, il")
print(f" fattore di convinzione da solo vale ×{conv:.2f}. Lo stare flat e' UNA PARTE di")
print(f" quel fattore, non il fattore.")
print(f" Conseguenza operativa opposta a quella suggerita: se il divario fosse solo")
print(f" 'flat', alzare target_vol non aumenterebbe il rischio nei giorni flat (dove non")
print(f" c'e' posizione). Essendo CONVINZIONE, alzare target_vol alza la posizione")
print(f" proprio nei giorni in cui il segnale e' PIU' DEBOLE (1 orizzonte su 3).")
print(f"\n SCOMPOSIZIONE ESATTA (i due fattori si moltiplicano e chiudono il conto):")
print(f" 20,00% ×{per_leg:.3f} (dentro la GAMBA) ×{div:.3f} (diversificazione BTC/ETH)"
f" = {0.20*per_leg*div:.2%}")
print(f" misurato sullo sleeve di produzione: {tp.std()*365**.5:.2%}")
print(f"\n E il ×{per_leg:.3f} della gamba si spacca a sua volta in DUE meta' quasi uguali:")
print(f" · sta FLAT nel {p_flat:.1%} delle barre (direzione 0) -> ×{f_flat:.3f}")
print(f" · quando NON e' flat sta a convinzione PARZIALE (rms {conv/f_flat:.2f}) -> ×{f_conv:.3f}")
print(f" prodotto sqrt(E[d²]) = ×{conv:.3f}; il residuo fino a ×{per_leg:.3f} e'")
print(f" la correlazione fra convinzione e vol realizzata/prevista"
f" (previsore di vol da solo: ×{np.mean(vols_real)/np.mean(vols_pred):.3f}).")
print(f"\n 📌 QUELLO CHE LO SCETTICATO NON DICE, e sono DUE cose.")
print(f" (i) «TP01 e' long-flat» spiega **circa META'** del divario (×{f_flat:.2f}), non")
print(f" il divario. L'altra meta' e' che `tsmom_blend` e' la MEDIA DEI SEGNI di 3")
print(f" orizzonti: quando TP01 e' a mercato sta quasi sempre a 1/3 o 2/3 di")
print(f" convinzione (×{f_conv:.2f}). E la differenza NON e' accademica: se il divario")
print(f" fosse solo 'flat', alzare target_vol non toccherebbe i giorni senza")
print(f" posizione; essendo CONVINZIONE, alzarlo aumenta la size proprio nei giorni")
print(f" in cui il segnale e' PIU' DEBOLE (1 orizzonte su 3 d'accordo).")
print(f" (ii) un terzo fattore non sta nemmeno nello sleeve: ×{div:.2f} viene dal")
print(f" COMBINARE BTC ed ETH a 50/50 (corr {J['BTC'].corr(J['ETH']):.2f}). Non e' un")
print(f" difetto d'etichetta: e' diversificazione, ed e' esattamente cio' che si")
print(f" vuole. Chiamare 'sovrastima del rischio' anche questo pezzo confonde una")
print(f" proprieta' desiderata con un errore di misura.")
print("\n E TP01 E' UN CASO PARTICOLARE, non la regola — gli altri sleeve vol-targeted:")
print(f"\n {'sleeve':>9}{'target dichiarato':>19}{'vol REALIZZATA':>16}{'su barre ATTIVE':>17}{'attive':>9}")
@@ -590,6 +671,59 @@ def attacco2() -> None:
print(" esattamente quella protetta dal test di guardia.")
def attacco2b() -> None:
"""Quali affermazioni pubblicate cambiano, e quali no — misurato, non argomentato."""
from src.portfolio import sleeves as SL
sezione("(6-bis) ATTACCO 2c — QUALI AFFERMAZIONI CAMBIANO (e la maggior parte NON cambia)")
tp, sk = SL._tp01_returns(), SL._skyhook_returns()
if tp.index.tz is None:
tp = tp.tz_localize("UTC")
if sk.index.tz is None:
sk = sk.tz_localize("UTC")
B = pd.concat({"TP01": tp, "SKH01": sk}, axis=1, join="inner").fillna(0.0)
w = np.array([W_TP, W_SKH])
X = B[["TP01", "SKH01"]].values
bk = X @ w
cov = np.cov(X.T)
var_b = float(bk.var())
rc = w * (cov @ w) / (cov @ w @ w)
print("\n NON cambia NIENTE di misurato. Ogni Sharpe, maxDD, muro, traiettoria e rendita")
print(" pubblicati sono calcolati sulla SERIE DI RITORNI REALIZZATA, mai sull'etichetta:")
print(" `target_vol` entra nel codice come parametro di sizing e non compare in nessuna")
print(" formula di rischio a valle. Un'etichetta che sovrastima non rende ottimista un")
print(" numero che non la usa.")
print("\n CAMBIA una classe di affermazioni sola: quelle che CONFRONTANO un parametro con")
print(" una misura. Nel progetto ce n'e' una, ed e' nel libro LIVE:")
print(f"\n {'sleeve':>8}{'peso NOZIONALE':>16}{'vol realizzata':>16}"
f"{'contributo al RISCHIO':>23}")
for i, nm in enumerate(("TP01", "SKH01")):
print(f" {nm:>8}{w[i]:>16.0%}{X[:, i].std()*365**.5:>16.2%}{rc[i]:>23.1%}")
print(f" {'libro':>8}{1.0:>16.0%}{bk.std()*365**.5:>16.2%}{sum(rc):>23.1%}")
print(f" corr TP01/SKH01 {np.corrcoef(X.T)[0,1]:+.3f} vol libro {bk.std()*365**.5:.2%}")
sig = X.std(axis=0) * 365 ** 0.5
cf = (W_TP * 0.20) ** 2 / ((W_TP * 0.20) ** 2 + (W_SKH * sig[1]) ** 2)
print("\n ❌ IPOTESI MIA, REFUTATA DAL MIO STESSO NUMERO. Mi aspettavo che l'etichetta")
print(" nascondesse una ripartizione del rischio molto diversa dai pesi di nozionale.")
print(f" Non e' cosi': {rc[0]*100:.0f}/{rc[1]*100:.0f} contro {W_TP*100:.0f}/{W_SKH*100:.0f}"
f" — coincidono entro un paio di punti, e per una")
print(" ragione che si legge in una riga: contributi di rischio ~ (w·σ)² con corr")
print(f" {np.corrcoef(X.T)[0,1]:+.2f} ~ 0, e {W_TP:.2f}×{sig[0]:.1%} = {W_TP*sig[0]:.2%} contro"
f" {W_SKH:.2f}×{sig[1]:.1%} = {W_SKH*sig[1]:.2%}")
print(" stanno gia' in rapporto ~75/25. **Coincidenza numerica, non struttura.**")
print("\n 📌 IL FATTO CHE RESTA, ed e' piu' utile dell'ipotesi che ho perso: l'errore non")
print(" lo commette chi legge i PESI, lo commette chi legge l'ETICHETTA. Un lettore che")
print(f" prendesse `target_vol=20%` per buono calcolerebbe TP01 al {cf*100:.0f}% del rischio")
print(f" di libro; il vero e' {rc[0]*100:.0f}%. L'etichetta non rende ottimista nessun numero")
print(" pubblicato (nessuno la usa) — sposta di ~16 punti l'ATTRIBUZIONE del rischio,")
print(" cioe' proprio la domanda 'quale gamba mi fa male' su cui il progetto ha gia'")
print(" tre misure indipendenti che indicano SKH01 come la fragile (LOO d'ancora,")
print(" sensibilita' alla fee 4x, vol realizzata piu' alta).")
print("\n E si precisa una frase: «SKH01 e' l'unica delle 5 a NON essere vol-targeted» e'")
print(" esatta sul CODICE e va letta insieme al fatto che e' anche quella che di vol ne")
print(" realizza di PIU' (21,0% contro 12,1%). Non e' una contraddizione: e' il motivo per")
print(" cui il peso 25% e' stato confermato tre volte all'estremo prudente.")
def guardia() -> None:
sezione("(7) IL VINCOLO DURO — cosa protegge davvero il test di guardia")
cfg = json.loads((ROOT / "config" / "live.json").read_text())
@@ -614,5 +748,6 @@ def guardia() -> None:
if __name__ == "__main__":
main()
attacco2()
attacco2b()
guardia()
print(f"\n▸ totale: consegna nel messaggio dell'agente.")