"""GTAA01 sulla versione REALMENTE ACQUISTABILE (UCITS) — quanto dello sleeve resta. CONTESTO. Il 2026-07-26 il conto reale ha rifiutato l'ordine sui 6 ETF USA di GTAA01 (PRIIPs: domiciliati USA, nessun KID, vietati al retail UE). Lo sleeve non e' deployabile cosi' com'e'. Questo script misura la sola via d'uscita: negoziare gli equivalenti UCITS. ⚠️ IL FATTO CHE RIDIMENSIONA IL PROBLEMA, E CHE LA NOTA IN gtaa.py NON AVEVA VISTO. La nota diceva: "storia piu' CORTA -> si perde la validazione a 30 anni che e' cio' che rendeva credibile questo sleeve". E' FALSO, per un motivo strutturale: **il PRIIPs vieta di COMPRARE, non di GUARDARE**. I prezzi di SPY/QQQ/IWM/TLT/GLD/HYG restano leggibili (l'operatore li ha visti sul terminale mentre l'ordine veniva rifiutato). Il SEGNALE di GTAA01 puo' continuare a girare sui 30 anni di serie USA per sempre; cambia solo il VEICOLO su cui si incassa il rendimento. La storia corta degli UCITS non serve a validare la strategia — serve a misurare la DEVIAZIONE DEL VEICOLO, che e' un drift lento e si stima bene anche su pochi anni. METODO — tre lenti, UN GRADO DI LIBERTA' PER VOLTA (come per il path live di SKH01, 26/07): L0 segnale USA + rendimento USA = GTAA01 come pubblicato (non deployabile) L1 segnale USA + rendimento UCITS = isola il VEICOLO L2 segnale UCITS + rendimento UCITS = aggiunge l'OROLOGIO (LSE chiude alle 11:30 di New York) Le tre lenti girano sullo STESSO calendario e sulla STESSA finestra: le differenze sono appaiate. ⚠️ DUE INSIEMI DI VEICOLI, e la ragione e' il BIGLIETTO MINIMO. Il prezzo di UNA azione e' una scelta dell'emittente, non una proprieta' dell'indice: CSPX costa $802 e VUAA $144 sullo STESSO S&P 500. Ma i veicoli economici sono i piu' recenti. Quindi: STORIA (CSPX/EQQQ/XRSU/IDTL/IGLN/IHYU) -> finestra lunga, serve a MISURARE; DEPLOY (VUAA/XNAS/R2US/IDTL/IGLN/IHYU) -> prezzo unitario basso, serve a ESEGUIRE. uv run python scripts/research/r0726_gtaa_ucits.py """ from __future__ import annotations import sys from pathlib import Path import numpy as np import pandas as pd ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) from src.data.eq_crosscheck import repair_twin_outliers # noqa: E402 from src.data.eq_splits import repair_splits # noqa: E402 from src.portfolio import gtaa as G # noqa: E402 RAW = ROOT / "data" / "raw" ANN = np.sqrt(252.0) US = ("SPY", "QQQ", "IWM", "TLT", "GLD", "HYG") # Scartati di proposito: RTWO (Russell 2000 *quality* = fattore), CSUSS (MSCI USA Small Cap *ESG*), # IDP6 (S&P 600: small cap ma indice diverso da Russell 2000), CNDX (market data non sottoscritto). SET_STORIA = {"SPY": "CSPX", "QQQ": "EQQQ", "IWM": "XRSU", "TLT": "IDTL", "GLD": "IGLN", "HYG": "IHYU"} SET_DEPLOY = {"SPY": "VUAA", "QQQ": "XNAS", "IWM": "R2US", "TLT": "IDTL", "GLD": "IGLN", "HYG": "IHYU"} # TER indicativi (%/anno). ⚠️ FONTE SECONDARIA, non letta dai KID: servono solo come CONTROLLO di # plausibilita' del drag misurato, non entrano in nessun calcolo. Regola 26/07: dichiarare la # qualita' della fonte. TER = {"SPY": 0.09, "QQQ": 0.20, "IWM": 0.19, "TLT": 0.15, "GLD": 0.40, "HYG": 0.49, "CSPX": 0.07, "EQQQ": 0.30, "XRSU": 0.30, "IDTL": 0.07, "IGLN": 0.12, "IHYU": 0.50, "VUAA": 0.07, "XNAS": 0.20, "R2US": 0.30} # Ritenuta USA sui dividendi (15% da trattato) x rendimento da dividendo indicativo -> punti base # che un residente italiano paga comprando il veicolo USA e NON paga comprando l'UCITS irlandese. RITENUTA_BPS = {"SPY": 18, "QQQ": 7, "IWM": 18, "TLT": 60, "GLD": 0, "HYG": 90} # =========================================================================== # dato # =========================================================================== def _norm(s: pd.Series) -> pd.Series: s = s[~s.index.duplicated()].copy() idx = pd.to_datetime(s.index) if getattr(idx, "tz", None) is not None: idx = idx.tz_localize(None) s.index = idx.normalize() return s.sort_index() def _load(prefix: str, sym: str) -> pd.DataFrame: d = pd.read_parquet(RAW / f"{prefix}_{sym.lower()}_1d.parquet") d.index = pd.to_datetime(d["timestamp"], unit="ms", utc=True) d, _ = repair_splits(d.sort_index()) return d def close_us(sym: str) -> pd.Series: d = _load("eq", sym) return pd.Series(d["close"].astype(float).values, index=d.index) def close_uc(sym: str, twin: pd.Series) -> tuple[pd.Series, list[dict]]: """Chiusure UCITS RIPARATE col cross-check sul gemello USA (src/data/eq_crosscheck.py).""" d, bad = repair_twin_outliers(_load("eqx", sym), twin) return pd.Series(d["close"].astype(float).values, index=d.index), bad # =========================================================================== # motore — generalizzazione di gtaa._gated_returns con segnale e veicolo SEPARATI # =========================================================================== def leg_returns(sig_close: pd.Series, exe_close: pd.Series, cal, cap_leg: float, band_usd: float, every: int, cost, *, integer_shares: bool = False): """Rendimenti netti di UNA gamba sul calendario `cal`, piu' la posizione tenuta. `sig_close` decide l'esposizione, `exe_close` la incassa: separarli isola il veicolo dall'orologio. Identico a gtaa._gated_returns quando le due coincidono e cal e' l'indice nativo. `integer_shares`: l'ordine si arrotonda a un numero INTERO di azioni. """ sig = _norm(sig_close).reindex(cal).ffill() exe = _norm(exe_close).reindex(cal).ffill() ex = np.nan_to_num(np.asarray(G._exposure(sig).values, float)) px = exe.values.astype(float) ret = np.zeros(len(px)) ret[1:] = px[1:] / px[:-1] - 1.0 held = np.empty(len(ex)) comm = np.zeros(len(ex)) cur = 0.0 for i in range(len(ex)): if i % every == 0: tgt = ex[i] if integer_shares: tgt = np.floor(tgt * cap_leg / px[i]) * px[i] / cap_leg notional = abs(tgt - cur) * cap_leg if notional >= max(band_usd, G.IB_MIN_TRADE_USD): comm[i] = cost(notional, px[i]) / cap_leg cur = tgt held[i] = cur pos = np.zeros(len(held)) pos[1:] = held[:-1] net = pos * ret - comm net[0] = 0.0 return pd.Series(net, index=cal), pd.Series(pos, index=cal) def sleeve(sig: dict, exe: dict, cal, capital: float, band: float, every: int, cost, integer_shares: bool = False, want_pos: bool = False): cap_leg = float(capital) / len(sig) out = {k: leg_returns(sig[k], exe[k], cal, cap_leg, band, every, cost, integer_shares=integer_shares) for k in sig} r = pd.concat({k: v[0] for k, v in out.items()}, axis=1).mean(axis=1, skipna=True) if want_pos: return r, pd.concat({k: v[1] for k, v in out.items()}, axis=1) return r def sleeve_native(sig: dict, exe: dict, capital: float, band: float, every: int, cost) -> pd.Series: """Replica ESATTA di gtaa.gtaa_returns: ogni gamba sul PROPRIO indice, outer-join, media. Serve solo al sanity check — le lenti girano su calendario comune, che e' cio' che rende appaiate le differenze.""" cap_leg = float(capital) / len(sig) cols = {} for k in sig: cal = _norm(exe[k]).index cols[k] = leg_returns(sig[k], exe[k], cal, cap_leg, band, every, cost)[0] return pd.concat(cols, axis=1, sort=True).sort_index().mean(axis=1, skipna=True).dropna() def met(r: pd.Series) -> dict: r = r.dropna() if len(r) < 50 or r.std() == 0: return dict(sharpe=np.nan, cagr=np.nan, dd=np.nan, vol=np.nan) eq = (1 + r).cumprod() return dict(sharpe=float(r.mean() / r.std() * ANN), cagr=float(eq.iloc[-1] ** (252.0 / len(r)) - 1), dd=float((eq / eq.cummax() - 1).min()), vol=float(r.std() * ANN)) def cost_ib_us(notional, price): return G.ib_commission(notional, price) def cost_form(fixed=0.0, prop_bps=0.0, min_fee=0.0, max_frac=1.0): def f(notional, price): return min(max(fixed + notional * prop_bps / 1e4, min_fee), max_frac * notional) return f def common_cal(series_list) -> pd.DatetimeIndex: cal = None for s in series_list: i = _norm(s).index cal = i if cal is None else cal.intersection(i) return cal def drag_cumulato(a: pd.Series, b: pd.Series, cal) -> float: """Deviazione annualizzata del veicolo b rispetto al gemello a, misurata sul RAPPORTO. ⚠️ NON si usa la media delle differenze giornaliere. Le due serie seguono lo STESSO indice ma sono campionate a orari diversi (LSE chiude 4h30 prima di New York): la differenza giornaliera e' dominata da uno sfasamento che si INVERTE il giorno dopo, quindi ha una volatilita' enorme (17-22% annualizzata) e un errore standard di ~7%/anno sulla stima del drag — cioe' 15 volte piu' grande del drag stesso. Il rapporto b/a invece e' stazionario: il suo drift E' il drag. """ x, y = _norm(a).reindex(cal).ffill(), _norm(b).reindex(cal).ffill() r = (y / x).dropna() return float((r.iloc[-1] / r.iloc[0]) ** (252.0 / len(r)) - 1) # =========================================================================== def main() -> None: print("=" * 108) print(" GTAA01 SUGLI EQUIVALENTI UCITS — quanto dello sleeve resta quando lo si puo' comprare") print("=" * 108) us = {s: close_us(s) for s in US} uc, difetti = {}, {} for s, u in SET_STORIA.items(): uc[s], bad = close_uc(u, us[s]) if bad: difetti[u] = bad dep = {} for s, u in SET_DEPLOY.items(): dep[s], bad = close_uc(u, us[s]) if bad: difetti[u] = bad # ---------- (0) DATO ---------- print("\n" + "=" * 108) print(" (0) IL DATO — cross-check di ogni veicolo contro il suo gemello USA") print("=" * 108) print(" Il feed equity aveva SOLO controlli locali (integrita', gap, spike, split): nessun") print(" secondo parere, mentre nel crypto la certificazione incrocia sempre piu' venue.") print(f"\n {'gamba':7s}{'STORIA':9s}{'barre':>7s}{'dal':>12s} {'DEPLOY':9s}{'barre':>7s}{'dal':>12s}") for s in US: a, b = SET_STORIA[s], SET_DEPLOY[s] print(f" {s:7s}{a:9s}{len(uc[s]):>7d}{str(_norm(uc[s]).index[0].date()):>12s} " f"{b:9s}{len(dep[s]):>7d}{str(_norm(dep[s]).index[0].date()):>12s}") print("\n BARRE SCARTATE DAL CROSS-CHECK:") if not difetti: print(" nessuna") for u, bad in difetti.items(): for b in bad: print(f" {u} {pd.Timestamp(b['date']).date()}: deviazione dal gemello {b['dev']:+.1%} " f"mentre il gemello faceva {b['twin_move']:+.2%} " f"-> non spiegato x{b['unexplained']:.0f}") print("\n ⚠️ CSPX 2012-01-13: open/high 112.740 in USD, low/close 88.010 in EUR (fattore dai") print(" close adiacenti 1.2797 = EURUSD di quel giorno). La certificazione ESISTENTE non lo") print(" vedeva: la guardia era 'maxret > 50% -> SPIKE?' e una contaminazione EUR/USD vale") print(" ~22-28%. Stesso schema dello split 2:1 del 25/07 (che valeva ESATTAMENTE -50%).") # ---------- SANITY ---------- print("\n" + "=" * 108) print(" SANITY — il motore generalizzato riproduce gtaa.gtaa_returns?") print("=" * 108) CAP = G.GTAA_DEFAULT_CAPITAL mine = sleeve_native(us, us, CAP, G.REBAL_BAND_USD, G.REBAL_EVERY, cost_ib_us) prod = _norm(G.gtaa_returns(capital=CAP)) both = pd.concat([mine, prod], axis=1).dropna() dmax = float((both.iloc[:, 0] - both.iloc[:, 1]).abs().max()) print(f" max|differenza| su {len(both)} barre = {dmax:.3e} " f"{'OK (bit-exact)' if dmax < 1e-12 else '❌ IL MOTORE NON E LA PRODUZIONE'}") # ---------- (1) DEVIAZIONE DEL VEICOLO ---------- cal = common_cal(list(us.values()) + list(uc.values())) start_uc = max(_norm(x).index[0] for x in uc.values()) cal = cal[cal >= start_uc + pd.Timedelta(days=380)] print("\n" + "=" * 108) print(" (1) LA DEVIAZIONE DEL VEICOLO — misurata sul RAPPORTO, non sulle differenze") print("=" * 108) print(f" finestra: {cal[0].date()} -> {cal[-1].date()} ({len(cal)} barre, {len(cal)/252:.1f} anni)") print(" (+1 anno sul veicolo piu' corto: in L2 il segnale nasce dagli UCITS e HORIZONS arriva") print(" a 252 barre — senza il margine le tre lenti non sarebbero appaiate)") print(f"\n {'gamba':7s}{'UCITS':7s}{'drag misurato':>15s}{'atteso da TER':>15s}" f"{'+ ritenuta USA':>16s}{'stabilita per anno':>20s}") tot_mis, tot_att = [], [] for s in US: u = SET_STORIA[s] d = drag_cumulato(us[s], uc[s], cal) att = (TER[s] - TER[u]) / 100.0 anni = [] for y in sorted(set(cal.year))[1:-1]: c = cal[cal.year == y] if len(c) > 100: anni.append(drag_cumulato(us[s], uc[s], c)) tot_mis.append(d) tot_att.append(att) print(f" {s:7s}{u:7s}{d:>14.2%}{att:>14.2%}{att + RITENUTA_BPS[s]/1e4:>15.2%}" f" [{min(anni):+.2%},{max(anni):+.2%}]" if anni else "") print(f"\n MEDIA EQUAL-WEIGHT: misurato {np.mean(tot_mis):+.2%}/anno · " f"atteso da TER {np.mean(tot_att):+.2%} · " f"con ritenuta {np.mean(tot_att) + np.mean(list(RITENUTA_BPS.values()))/1e4:+.2%}") # Il grado d'accordo si CONTA, non si dichiara: scriverlo a mano nel print e' il modo piu' # facile di pubblicare una conclusione che i propri dati smentiscono. conc = sum(1 for a, b in zip(tot_mis, tot_att) if np.sign(a) == np.sign(b)) vicini = sum(1 for a, b in zip(tot_mis, tot_att) if abs(a - b) < 0.003) print(f"\n CONTROLLO DI PLAUSIBILITA': segno concorde in {conc}/6 gambe, " f"scarto < 30bps in {vicini}/6.") print(" Il caso piu' netto e' l'oro: GLD 0.40% vs IGLN 0.12% -> atteso +0.28%, misurato") print(" +0.36%. Le due gambe discordi (CSPX, IHYU) restano entro 30bps: su una banda annua") print(" larga [-0.9%,+1.9%] il segno di uno scarto cosi' piccolo non e' informativo.") print(" Cio' che il controllo stabilisce e' la SCALA — decine di bps, non punti.") print("\n ⚠️ La colonna 'ritenuta' e' A FAVORE dell'UCITS e NON e' nel drag misurato:") print(" ADJUSTED_LAST sulle serie USA e' al LORDO della ritenuta del 15% che un residente") print(" italiano paga davvero sui dividendi USA. (Ipotesi fiscale dichiarata, fonte") print(" secondaria, da confermare col commercialista — non e' un parere fiscale.)") # ---------- (2) TRE LENTI ---------- print("\n" + "=" * 108) print(" (2) TRE LENTI, UN GRADO DI LIBERTA' PER VOLTA") print("=" * 108) L0 = sleeve(us, us, cal, CAP, G.REBAL_BAND_USD, G.REBAL_EVERY, cost_ib_us) L1 = sleeve(us, uc, cal, CAP, G.REBAL_BAND_USD, G.REBAL_EVERY, cost_ib_us) L2 = sleeve(uc, uc, cal, CAP, G.REBAL_BAND_USD, G.REBAL_EVERY, cost_ib_us) print(f"\n {'lente':48s}{'Sharpe':>9s}{'CAGR':>9s}{'maxDD':>9s}{'vol':>8s}") for nome, r in [("L0 segnale USA + rend. USA (pubblicato)", L0), ("L1 segnale USA + rend. UCITS (veicolo)", L1), ("L2 segnale UCITS + rend. UCITS (+orologio)", L2)]: m = met(r) print(f" {nome:48s}{m['sharpe']:>9.2f}{m['cagr']:>8.2%}{m['dd']:>9.1%}{m['vol']:>8.1%}") m0, m2 = met(L0), met(L2) print(f"\n TOTALE (L2-L0): dSharpe {m2['sharpe']-m0['sharpe']:+.2f} · " f"dCAGR {m2['cagr']-m0['cagr']:+.2%}/anno · dDD {m2['dd']-m0['dd']:+.1%}") print(f"\n ⚠️ corr(L0,L2) = {L0.corr(L2):.2f}, bassa per due serie sullo STESSO indice — ed e'") print(" l'orologio, non un difetto: la chiusura di Londra e quella di New York condividono") print(" la notte e la mattina americana ma non il pomeriggio. Verificato: la correlazione") print(" fra veicolo e gemello e' MASSIMA a lag 0 su 6/6 coppie (0.46-0.78), quindi le date") print(" non sono sfasate. Conseguenza: le statistiche GIORNALIERE delle due lenti non sono") print(" confrontabili una a una, mentre il DRIFT (sezione 1) lo e'.") # ---------- (3) COSTI PER FORMA ---------- print("\n" + "=" * 108) print(" (3) IL COSTO DEL VENUE — per FORMA, non per livello") print("=" * 108) print(" Regola 25/07: due venue a pari 'costo medio' danno esiti OPPOSTI al variare del") print(" capitale, perche' un pavimento FISSO e' una tassa regressiva. Qui non si indovina il") print(" listino di un broker: si misura la curva e si ricava la soglia da verificare.") caps = [3_000, 10_000, 50_000] # Il numero che rende ogni listino calcolabile a mano, senza doverne indovinare nessuno. print("\n ORDINI L'ANNO (dipendono da banda e cadenza, NON dal listino ne' dal capitale):") for c in [3_000, 10_000, 50_000]: n = 0 for k in uc: _, pos = leg_returns(uc[k], uc[k], cal, c / 6.0, G.REBAL_BAND_USD, G.REBAL_EVERY, cost_form(fixed=1.0)) n += int((pos.diff().abs() > 1e-12).sum()) print(f" a ${c:,d} di capitale allocato: {n/(len(cal)/252):.0f} ordini/anno " f"-> costo annuo = ordini x tariffa (es. $1/ordine = ${n/(len(cal)/252):.0f}/anno " f"= {n/(len(cal)/252)/c:.2%} del capitale)") print(f"\n COSTO FISSO per ordine: " + "".join(f"{'$'+format(c,',d'):>22s}" for c in caps)) print(f" {'':>29s}" + "".join(f"{'Sharpe':>11s}{'CAGR':>11s}" for _ in caps)) for f in [0.0, 0.35, 1.0, 2.0, 3.0, 5.0, 10.0]: row = f" {'$'+format(f,'.2f'):>29s}" for c in caps: m = met(sleeve(uc, uc, cal, c, G.REBAL_BAND_USD, G.REBAL_EVERY, cost_form(fixed=f))) row += f"{m['sharpe']:>11.2f}{m['cagr']:>10.2%}" print(row) print(f"\n COSTO PROPORZIONALE: " + "".join(f"{'$'+format(c,',d'):>22s}" for c in caps)) print(f" {'':>29s}" + "".join(f"{'Sharpe':>11s}{'CAGR':>11s}" for _ in caps)) for p in [0.0, 5.0, 10.0, 25.0, 50.0]: row = f" {format(p,'.0f')+' bps/ordine':>29s}" for c in caps: m = met(sleeve(uc, uc, cal, c, G.REBAL_BAND_USD, G.REBAL_EVERY, cost_form(prop_bps=p))) row += f"{m['sharpe']:>11.2f}{m['cagr']:>10.2%}" print(row) print("\n SOGLIE — costo fisso per ordine oltre il quale lo sleeve non vale la pena:") print(f" {'capitale':>10s}{'CAGR = 0':>13s}{'Sharpe = 0.45':>16s} (0.45 = base del plateau dichiarato)") for c in caps: def cagr_at(f): return met(sleeve(uc, uc, cal, c, G.REBAL_BAND_USD, G.REBAL_EVERY, cost_form(fixed=f))) res = [] for target, key in [(0.0, "cagr"), (0.45, "sharpe")]: lo, hi = 0.0, 40.0 if cagr_at(hi)[key] > target: res.append(">$40") continue for _ in range(9): mid = (lo + hi) / 2 if cagr_at(mid)[key] > target: lo = mid else: hi = mid res.append(f"${(lo+hi)/2:.2f}") print(f" {'$'+format(c,',d'):>10s}{res[0]:>13s}{res[1]:>16s}") # ---------- (4) BIGLIETTO MINIMO ---------- print("\n" + "=" * 108) print(" (4) IL BIGLIETTO MINIMO — l'analogo azionario dell'haircut small-cap") print("=" * 108) print(" Senza frazionamento un ordine e' almeno UNA azione. Se una azione vale quanto l'intera") print(" gamba, il vol-target non e' rappresentabile e lo sleeve non e' quello che il backtest") print(" modella. Ma il prezzo unitario e' una SCELTA: sullo stesso indice esistono linee 5-10x") print(" piu' economiche.") print(f"\n {'gamba':7s}{'STORIA':8s}{'prezzo':>9s}{'DEPLOY':>9s}{'prezzo':>9s}" + "".join(f"{'a $'+format(c,',d'):>13s}" for c in caps)) for s in US: pa = float(_norm(uc[s]).iloc[-1]) pb = float(_norm(dep[s]).iloc[-1]) row = f" {s:7s}{SET_STORIA[s]:8s}{pa:>9.2f}{SET_DEPLOY[s]:>9s}{pb:>9.2f}" for c in caps: row += f"{pb/(c/6.0):>12.0%} " print(row) print(" (percentuale = quanto pesa UNA azione DEPLOY sull'allocazione di quella gamba;") print(" sopra ~20% il target continuo non e' rappresentabile)") print("\n DANNO MISURATO dell'esecuzione a numero INTERO di azioni — i DUE insiemi a confronto,") print(" sulla STESSA finestra (quella del veicolo piu' corto), per isolare il prezzo unitario:") cal_d = common_cal(list(us.values()) + list(dep.values()) + list(uc.values())) start_d = max(_norm(x).index[0] for x in list(dep.values()) + list(uc.values())) cal_d = cal_d[cal_d >= start_d + pd.Timedelta(days=380)] print(f" finestra {cal_d[0].date()} -> {cal_d[-1].date()} ({len(cal_d)/252:.1f} anni, " f"vincolata da {SET_DEPLOY['QQQ']})") print(f"\n {'insieme':9s}{'capitale':>10s}{'Sh fraz.':>10s}{'Sh intero':>11s}{'delta':>8s}" f"{'vol fraz.':>11s}{'vol intero':>12s}{'gambe vive':>12s}{'esposta':>10s}") for nome, vv in [("STORIA", uc), ("DEPLOY", dep)]: for c in [3_000, 10_000, 50_000]: a = met(sleeve(vv, vv, cal_d, c, G.REBAL_BAND_USD, G.REBAL_EVERY, cost_form(fixed=1.0))) rb, pb = sleeve(vv, vv, cal_d, c, G.REBAL_BAND_USD, G.REBAL_EVERY, cost_form(fixed=1.0), integer_shares=True, want_pos=True) b = met(rb) vive = int((pb.abs().sum(axis=0) > 0).sum()) print(f" {nome:9s}{'$'+format(c,',d'):>10s}{a['sharpe']:>10.2f}{b['sharpe']:>11.2f}" f"{b['sharpe']-a['sharpe']:>+8.2f}{a['vol']:>11.1%}{b['vol']:>12.1%}" f"{str(vive)+'/6':>12s}{(pb > 0).mean().mean():>10.0%}") print("\n ⚠️ SI LEGGE LA COLONNA 'gambe vive' E 'vol', NON 'Sharpe'. Un vincolo che ALZA lo") print(" Sharpe non e' un miglioramento: arrotondando in giu' riduce l'esposizione e paga") print(" meno commissioni. E' il NULL DE-LEVERING (VRP-DD, TP01xDVOL, MAT01: 3 occorrenze") print(" nel progetto) — il primo test da fare su ogni 'va meglio'. Qui il controllo e' stato") print(" eseguito e il verdetto e' che con l'insieme DEPLOY il de-levering NON c'e'.") # ---------- (5) limite del rilevatore, chiuso sul DANNO ---------- print("\n" + "=" * 108) print(" (5) IL LIMITE DEL CROSS-CHECK, CHIUSO SUL DANNO E NON SULLA RILEVABILITA'") print("=" * 108) print(" Il cross-check non vede una contaminazione a cambio basso (EURUSD ~1.09 -> 8% di") print(" deviazione, dentro il rumore dei giorni violenti: misurato 9.9% il 2025-04-09).") print(" Abbassare la soglia genererebbe falsi positivi, quindi si misura il DANNO di una") print(" contaminazione NON VISTA invece di inseguirne la rilevabilita'.") base = met(sleeve(uc, uc, cal, CAP, G.REBAL_BAND_USD, G.REBAL_EVERY, cost_form(fixed=1.0))) rng = np.random.default_rng(20260726) for fx, nome in [(1.09, "EURUSD 1.09 — il caso NON rilevabile"), (1.27, "GBPUSD 1.27 — riferimento di scala (sempre rilevato)")]: ds = [] for _ in range(60): leg = US[int(rng.integers(0, 6))] u2 = dict(uc) s = _norm(u2[leg]).reindex(cal).ffill().copy() s.iloc[int(rng.integers(300, len(s) - 5))] /= fx u2[leg] = s ds.append(met(sleeve(u2, u2, cal, CAP, G.REBAL_BAND_USD, G.REBAL_EVERY, cost_form(fixed=1.0)))["sharpe"] - base["sharpe"]) ds = np.array(ds) print(f" {nome:52s} dSharpe mediano {np.median(ds):+.4f} · " f"peggiore {ds.min():+.4f} · |d|>0.05 nel {(np.abs(ds)>0.05).mean():.0%}") print("\n" + "=" * 108) if __name__ == "__main__": main()