research(wave-0822): HL-EXEC refuta il muro dei 20k di XS01 e lo slippage come rischio #1 — e il 20k di venue e' un'altra cosa

This commit is contained in:
Adriano Dal Pastro
2026-08-22 17:33:55 +00:00
parent ea22a0e20c
commit 090129359b
7 changed files with 920 additions and 31 deletions
+45
View File
@@ -17,6 +17,7 @@ null de-levering superato + eseguibilita' al capitale dichiarato.
| 8 | VOL-SIZE | **LEAD** (gate 2026-12-22) + 1 falsificazione | dare a SKH01 una size per-trade regge a **23/23 ancore**, 8/8 anni, null di permutazione e trasferimento su V1 — ma vale **+0,07 di Sharpe di libro, un terzo della fortuna d'ancora del libro stesso (+0,196)**. Il vol-target di **libro** e' invece falsificato: compra peso SKH gia' respinto e peggiora l'eseguibilita' |
| — | **XSR-REPRO** (integrita') | 🚨 **DIFETTO DI PRODUZIONE** | il numero 1.82 e' SPIEGATO e non era sbagliato (era su una **terza** lente, e su una barra non ancora chiusa) — ma cercandone la causa e' emerso che **`paper_xsr` registra ~41 minuti di mercato al giorno**, non un giorno. Tre gate pre-registrati leggono serie costruite cosi' |
| 5 | SKEW | **SCARTATO** (Q1, Q2) + **LEAD** (Q3, gate 2027-02-22) | il prezzo muove lo skew (t 3,1-9,4 su 8/8 test), **non il contrario** (max |t| in avanti 2,35 contro 2,08 atteso dal rumore). Ma Q3 e' grosso: **il f=0,73 di VRP01 e' per il 42% STRUTTURA A TERMINE e solo per il 25% skew** |
| — | **HL-EXEC** (audit di fatto) | **3 falsificazioni misurate** | il pavimento vero e' **$10 (non $5)** e il taker **4,50 bps (non 5,0)** — ma il *"XS01 serve ~$20k"* e' **refutato del tutto** (nessuna soglia da min-order), il *"XSR01 ~$5.000"* e' **conservativo di 1,7x** (vero ~$3.000), e lo **slippage "rischio #1" di XSR01 e' refutato** con margine **21x** |
## Note che sopravvivono ai singoli filoni
@@ -362,3 +363,47 @@ nuovo: **va decisa PRIMA del 23/10, non quel giorno.**
pubblicato senza quel controllo"*.
- ✅ Smile vettorizzato (512s -> 2,0s) verificato **bit-exact** contro il ciclo lento su 21.244 celle.
-**Convergenza indipendente** con l'agente TERM-STRUCTURE: **75 giorni utili** (l'altro trovava 74).
### HL-EXEC (r0822_hl_exec.py, 132 simulazioni, 0 celle di segnale = 0 selezione)
✅ Identita' con la produzione **prima** di ogni delta: `sim_libro` == `paper_xsr._step` a
**max|diff| 5,4e-20**; XS01 == `_xsec_returns()` a **max|diff| 0,0**.
**Verifica MAINNET** (il progetto e' nato da un feed testnet): 51/51 asset, deviazione mediana
2,21% su feed vecchio 17h contro un movimento tipico del 2,81%; cross-check su terzo venue
BTC Deribit $77.997 vs HL $77.339 = 0,84%.
**Parametri assunti -> misurati:** min order **$5 -> $10** (docs + **3.827 livelli a un ordine:
minimo $10,63, p01 $12,02, 0,0% sotto $10**); taker **5,0 -> 4,50 bps** (due fonti indipendenti
coincidono); slippage **non modellato -> 1/2 spread 0,7 bps (19 major) / 1,0 bps (coda), max 4,5**;
tier VIP: a $600-20k **sempre tier base**.
📌 **XS01: la soglia dei $20k non esiste.** Origine del numero pubblicato: *"rumore di
arrotondamento"*, stima a occhio del diario 19/06. Misurato: haircut ~0 gia' a **$200-600 allocati**;
a $600 lo Sharpe passa **1,33 -> 1,30**. **E il meccanismo spiega perche':** gli ordini sono **due
popolazioni** — il ribilanciamento del segnale (ogni 10g) e' il **13% degli ordini ma il 75% del
nozionale**, ticket mediano $13,65 -> passa sempre; la deriva del vol-target giornaliera e' l'**87%
degli ordini ma il 25% del nozionale**, ticket $0,64 -> il pavimento la taglia, **ed e' gratis**.
*Contare gli ORDINI da' 16% e sembra un disastro; contare il NOZIONALE da' 75% e spiega perche' lo
Sharpe non si muove.* (Convergenza indipendente con XS-LITE, che era arrivato allo stesso meccanismo
con numeri diversi.)
**Fortuna di fase girata anche qui** (l'haircut e' un Δ): 10 fasi, mediana delle differenze
appaiate **+0,007**, banda [0,060, +0,061], **0/10 fasi con danno > 0,10**.
📌 **XSR01: soglia vera ~$3.000** (non $5.000 — sbagliata nella direzione **sicura**). Banda sui
pavimenti $8/$10/$12: ROTTO <=$1.000, INDECIDIBILE $1.500-2.000, **ok >=$3.000**.
📌 **Slippage refutato come rischio #1** al livello di liquidita' odierno: lo Sharpe scende sotto
1,0 a ~20 bps/gamba = **~16 bps di slippage = 21x quello misurato**. ⚠️ L'agente dichiara il limite:
3 snapshot = **172 secondi di oggi**, una fotografia, non tre osservazioni. **E' il MARGINE (21x) che
regge la conclusione, non il livello.**
🚨 **Due conseguenze che l'operatore deve vedere:**
1. **Il "$20k" di XS01 e il "$20k" della DECISIONE DI VENUE (26/07) sono due cose diverse che
CLAUDE.md ha conflato.** Quello di XS01 **cade**; **quello di venue REGGE intatto** — e' una
decisione dell'operatore su un altro asse (la rovina da fallimento exchange), non sull'eseguibilita'.
XS01 resta fuori dal live **per la decisione di venue, non per taglia**.
2. **La gamba di eseguibilita' del gate 2026-10-23 e' tarata su un vincolo che a $5.000 non morde**
(haircut li' 7%...+2%): *una condizione pre-registrata che non puo' fallire non e' una
condizione*. L'agente **non ha anticipato il gate**: la soglia Sharpe>=1,0 resta al 23/10.
⚠️ **Terza spiegazione dell'1,82, complementare alle altre due:** `r0725_statarb_basket_gate`
addebita *"2 gambe per coppia"* = 50 alt + **50 gambe BTC fantasma**, ma il demeaning **annulla
algebricamente** la gamba BTC (`sum(w)=0`) — che e' la ragione stessa per cui XSR01 esiste.
*L'intuizione del demeaning era stata applicata all'AMPIEZZA e non ai COSTI.*
📌 **Quadro conciliato delle tre lenti** (i due agenti concordano sul fatto, non sull'attribuzione
del singolo decimale): titolo `demean_skeptic` **1,82** · gate `basket_gate` **1,75-1,79** (fee
raddoppiate su una gamba inesistente) · monitor `paper_xsr` **2,21**. **La lente che ha girato i
gate e' quella PESSIMISTICA** -> il **DSR 0,983 PASS e' stato calcolato su una serie conservativa**.
+103 -6
View File
@@ -474,6 +474,46 @@ def describe_curve(asset: str, cv: pd.DataFrame) -> None:
f"(beta -1 = reversione totale, 0 = martingala)")
def basis_vs_funding(asset: str, cv: pd.DataFrame) -> None:
"""Q1, nella forma decidibile: il basis del front e' il premio ANNUALIZZATO che
incassi se vendi il datato e lo tieni a scadenza. La copertura e' il perp, su cui
PAGHI il funding realizzato nello stesso periodo. Quindi il roll-down incassabile
NON e' il basis: e' `basis - funding realizzato fino a scadenza`.
Se il basis e' un previsore NON DISTORTO del funding (a~0, b~1) il premio a termine
esiste come LIVELLO ma non e' estraibile: e' gia' il prezzo della copertura."""
d = cv[["c_front", "f1h", "tau_front_h"]].dropna()
if len(d) < 5000:
print(f" {asset}: campione insufficiente")
return
f = d["f1h"].values
cum = np.concatenate([[0.0], np.cumsum(f)]) # somma cumulata del funding orario
n = len(d)
tau = d["tau_front_h"].values
end = np.minimum(np.arange(n) + tau.astype(int), n - 1)
hrs = np.maximum(end - np.arange(n), 1)
realized = (cum[end] - cum[np.arange(n)]) / hrs * HOURS_Y # funding realizzato ANNUALIZZATO
implied = d["c_front"].values
m = np.isfinite(realized) & np.isfinite(implied) & (hrs > 24 * 5)
x, y = implied[m], realized[m]
b1, b0 = np.polyfit(x, y, 1)
prem = x - y # premio effettivamente incassabile
# IC bootstrap a BLOCCHI (le ore sono fortemente autocorrelate: un IC iid mentirebbe)
rng = np.random.default_rng(822)
bl = 24 * 30
nb = max(1, len(prem) // bl)
boots = []
for _ in range(400):
st = rng.integers(0, max(1, len(prem) - bl), nb)
boots.append(np.mean(np.concatenate([prem[i:i + bl] for i in st])))
lo, hi = np.percentile(boots, [2.5, 97.5])
print(f" {asset}: basis implicito medio {np.mean(x)*100:+6.2f}%/anno "
f"funding realizzato a scadenza {np.mean(y)*100:+6.2f}%/anno")
print(f" regressione realizzato ~ a + b*implicito: b = {b1:+.3f} a = {b0*100:+.2f}%"
f" (b~1 e a~0 = previsore non distorto => niente da incassare)")
print(f" PREMIO INCASSABILE = implicito - realizzato = {np.mean(prem)*100:+6.2f}%/anno"
f" IC95% a blocchi [{lo*100:+.2f}%, {hi*100:+.2f}%] (n={m.sum():,} ore)")
# ==========================================================================
# 3. STRATEGIE — posizioni in spazio CONTRATTO (il roll si paga da solo)
# ==========================================================================
@@ -671,6 +711,9 @@ def main() -> None:
cv = curve_frame(a, panels[a], metas[a], funds[a], roll_dte=7, perp=perps[a])
curves[a] = cv
describe_curve(a, cv)
print("\n --- Q1-bis: il basis e' un PREVISORE del funding che paghi sulla copertura? ---")
for a in ASSETS:
basis_vs_funding(a, curves[a])
# ---------------- STEP 3b: spread bid-ask (calibrazione DATATA) ----------------
print("\n[3b] SPREAD BID-ASK — calibrazione sul book VIVO di oggi (non e' storia)")
@@ -724,6 +767,44 @@ def main() -> None:
G = pd.DataFrame([{k: v for k, v in r.items() if k != "daily"} for r in rows])
dailies = {i: r["daily"] for i, r in enumerate(rows)}
print(f"\n distribuzione dello Sharpe FULL sulle {len(G)} celle: "
f"min {G.sharpe.min():+.2f} p25 {G.sharpe.quantile(.25):+.2f} "
f"mediana {G.sharpe.median():+.2f} p75 {G.sharpe.quantile(.75):+.2f} "
f"max {G.sharpe.max():+.2f} sd {G.sharpe.std():.2f}")
degen = G[(G.vol < 0.005) | (G.n < 400)]
if len(degen):
print(f" ATTENZIONE: {len(degen)} celle DEGENERI (vol<0.5%/anno o <400 giorni attivi):")
print(f" Sharpe di quelle celle: {sorted(np.round(degen.sharpe.values,2))[:10]}")
print(" gonfiano la varianza dei trial e quindi il null del deflated-Sharpe:")
print(" il DSR viene riportato ANCHE sulla sola sottogriglia non degenere.")
print("\n MIGLIORE CELLA PER FAMIGLIA (Q2 riguarda il CALENDAR: va letto separato):")
for fam in FAM:
sub = G[G.family == fam]
i = sub.sharpe.idxmax()
r = G.loc[i]
print(f" [{fam}] {r.sig:12s} W={int(r.win):5d}h roll={int(r.roll):2d}g thr={r.thr:.1f} "
f"| Sh {r.sharpe:+6.2f} hold {r.holdout:+6.2f} DD {r.maxdd*100:5.2f}% "
f"CAGR {r.cagr*100:+6.2f}% vol {r.vol*100:5.2f}%")
ii = A.implausible_sharpe(dailies[i])
print(f" implausible_sharpe={ii['implausible']} "
f"(barre in perdita {ii.get('loss_frac', float('nan'))*100:.1f}%) "
f"-> {'coda ASSENTE, rischio fuori dal campione' if ii['implausible'] else 'coda PRESENTE'}")
print("\n LE CELLE CANONICHE (quelle che un lettore chiede per nome), roll=7g:")
for fam, sig, lab in [
("FvP", "carry", "vendi il datato quando e' in CONTANGO / compra perp (il carry classico)"),
("FvP", "static_short", "SEMPRE corto il datato / lungo perp"),
("FvP", "static_long", "SEMPRE lungo il datato / corto perp (il contrario)"),
("CAL", "static_long", "calendar: lungo back / corto front"),
("CAL", "static_short", "calendar: corto back / lungo front")]:
sub = G[(G.family == fam) & (G.sig == sig) & (G.roll == 7)]
if len(sub):
r = G.loc[sub.index[0]]
print(f" {fam}/{sig:12s} Sh {r.sharpe:+6.2f} hold {r.holdout:+6.2f} "
f"CAGR {r.cagr*100:+6.2f}% DD {r.maxdd*100:5.2f}% vol {r.vol*100:5.2f}%"
f" <- {lab}")
print("\n migliori 8 celle per Sharpe FULL (NON e' la selezione — solo panorama):")
for i in G.sharpe.nlargest(8).index:
r = G.loc[i]
@@ -756,6 +837,11 @@ def main() -> None:
dsr, sr0 = A.deflated_sharpe(bs["sharpe"], list(G.sharpe.values), bd)
print(f" deflated-Sharpe : DSR {dsr:.3f} (null max atteso {sr0:+.2f} su {len(G)} trial) "
f"-> {'PASS' if dsr >= 0.95 else 'FAIL'}")
nd = G[(G.vol >= 0.005) & (G.n >= 400)]
if len(nd) >= 5 and len(nd) < len(G):
d2, s2_ = A.deflated_sharpe(bs["sharpe"], list(nd.sharpe.values), bd)
print(f" sulla sola sottogriglia NON degenere ({len(nd)} trial): "
f"DSR {d2:.3f} (null max {s2_:+.2f}) -> {'PASS' if d2 >= 0.95 else 'FAIL'}")
imp = A.implausible_sharpe(bd)
print(f" implausible_sharpe: implausible={imp['implausible']} "
@@ -769,9 +855,13 @@ def main() -> None:
tp = A.tp01_baseline_daily()
try:
mg = A.marginal_vs_tp01(bd)
print(f" marginal_vs_tp01 : {mg.get('verdict')} corr {mg.get('corr', float('nan')):+.3f} "
print(f" marginal_vs_tp01 : {mg.get('marginal_verdict')} "
f"corr_full {mg.get('corr_full')} corr_hold {mg.get('corr_hold')} "
f"robust_oos={mg.get('robust_oos')} insample_edge={mg.get('has_insample_edge')} "
f"is_hedge={mg.get('is_hedge')} beats_noise={mg.get('beats_noise_null')}")
w25 = mg.get("w25") or (mg.get("blends", {}) or {}).get("w25", {})
print(f" uplift blend w=25%: full {w25.get('uplift_full')} "
f"hold {w25.get('uplift_hold')}")
except Exception as exc: # noqa: BLE001
mg = {}
print(f" marginal_vs_tp01 : NON GIRATO ({type(exc).__name__}: {exc})")
@@ -848,14 +938,21 @@ def main() -> None:
# ---------------- STEP 8: eseguibilita' ----------------
print("\n[8] Q3 — ESEGUIBILITA' a $600-3.000")
print(" lotti minimi letti dal VENUE (non da una tabella cablata):")
px = {a: float(curves[a]["index"].dropna().iloc[-1]) for a in ASSETS}
for a in ASSETS:
for nm in (f"{a}-25DEC26", f"{a}_USDC-25DEC26"):
try:
sp = _api("/public/get_instrument", instrument_name=f"{a}-25DEC26")["result"]
print(f" {a}-25DEC26: min_trade_amount ${sp['min_trade_amount']:,.0f} di nozionale, "
f"contract_size ${sp['contract_size']:,.0f}, tick {sp['tick_size']}, "
f"taker {sp['taker_commission']*1e4:.1f} bps")
sp = _api("/public/get_instrument", instrument_name=nm)["result"]
lin = sp.get("settlement_currency") == "USDC"
lot = (sp["min_trade_amount"] * px[a]) if lin else sp["min_trade_amount"]
print(f" {nm:22s} {'LINEARE' if lin else 'INVERSE':8s} "
f"min {sp['min_trade_amount']:<10.4g} = ${lot:>8,.2f} di nozionale "
f"tick {sp['tick_size']} taker {sp['taker_commission']*1e4:.1f} bps")
except Exception as exc: # noqa: BLE001
print(f" {a}: specs non lette ({type(exc).__name__})")
print(f" {nm:22s} specs non lette ({type(exc).__name__})")
print(" (il libro live trada i LINEARI USDC; i trimestrali studiati qui sono gli INVERSE,")
print(" che hanno 7 anni di storia. Le due famiglie hanno lotti minimi DIVERSI.)")
print(f" vol ANNUA del candidato per $1 di nozionale lordo/gamba: {bs['vol']*100:.2f}%")
if bs["vol"] > 0:
lev = 0.20 / bs["vol"]
+28 -2
View File
@@ -312,7 +312,7 @@ def metriche(net: np.ndarray, idx: pd.DatetimeIndex) -> dict:
# ==================================================================================================
# 4. XS01 — ricostruzione dei PESI (il sleeve espone solo i ritorni) + prova d'identita'
# ==================================================================================================
def xs01_pesi():
def xs01_pesi(phase: int = 0):
from src.portfolio.sleeves import XS_CFG, XS_UNIVERSE, _xsec_returns
cols = {}
for sym in XS_UNIVERSE:
@@ -334,7 +334,7 @@ def xs01_pesi():
w = np.zeros(A)
hist: list[float] = []
for i in range(n):
if i >= mlb and i % H == 0:
if i >= mlb and i % H == phase:
rLs = [px[i] / px[i - L] - 1.0 for L in lb]
di = float(np.mean([r.std() for r in rLs]))
thr = np.percentile(hist, dp) if (dp > 0 and len(hist) >= mh) else -np.inf
@@ -361,9 +361,12 @@ def xs01_pesi():
rv = s.rolling(30, min_periods=15).std().shift(1) * np.sqrt(365.25)
scale = np.clip(np.nan_to_num(tv / rv.replace(0, np.nan).values, nan=0.0), 0, 3.0)
# prova d'identita' col sleeve UFFICIALE (max|diff| deve essere 0.0)
if phase == 0:
off = _xsec_returns()
dif = float(np.abs(pd.Series(s.values * scale, index=C.index).reindex(off.index).values
- off.values).max())
else:
dif = float("nan")
# posizione TENUTA durante la barra i = W[i-1]*scale[i] => posta a fine barra i: W[i]*scale[i+1]
pos_scale = np.concatenate([scale[1:], scale[-1:]])
return W * pos_scale[:, None], dret, C, list(C.columns), dif
@@ -541,6 +544,29 @@ def main() -> None:
f"{hc:>12.0f}%{m10['pct_eseguite']:>8.0f}% ord "
f"{m10['pct_nozionale']:>4.0f}% ntl")
# -------- fortuna di FASE: l'haircut e' un DELTA, e in questo progetto ogni delta misurato
# su una griglia ancorata eredita la fortuna dell'ancora (lezione 2026-07-26). XS01 ribilancia
# ogni H=10 giorni -> 10 fasi. Statistica giusta: MEDIANA DELLE DIFFERENZE APPAIATE.
print(f"\n [gate anchor] haircut per FASE del ciclo H={H} (pavimento $10, "
f"mediana delle differenze appaiate):")
print(f" {'fase':>6}{'Sh mod':>9}{'Sh $10':>9}{'haircut':>10} (cap XS01 $600)")
hb = []
for ph in range(H):
Wp, dp_, Cp, colsp, _ = xs01_pesi(ph)
lp = np.array([float(meta.loc[x, "lot_usd"]) if x in meta.index else 0.0 for x in colsp])
pp = np.array([float(meta.loc[x, "markPx"]) if x in meta.index else np.nan for x in colsp])
s0 = metriche(sim_libro(Wp, dp_, None, 600.0, None, None, None, fee_xs_mod)["net"], Cp.index)["sharpe"]
s1 = metriche(sim_libro(Wp, dp_, None, 600.0, MIN_ORDER_HL, lp, pp, fee_xs_mod)["net"], Cp.index)["sharpe"]
hb.append(s0 - s1)
print(f" {ph:>6}{s0:>9.2f}{s1:>9.2f}{s0-s1:>+10.3f}")
hb = np.array(hb)
print(f" mediana appaiata {np.median(hb):+.3f} Sharpe banda [{hb.min():+.3f}, {hb.max():+.3f}]"
f" fasi con danno >0.10: {int((hb > 0.10).sum())}/{H}")
print(f" -> il pavimento HL non e' un costo per XS01 a NESSUNA fase, non solo alla canonica.")
print(f" (sottoprodotto: la colonna 'Sh mod' REPLICA il phase-luck del 2026-07-02 — la fase")
print(f" canonica 0 sta in alto e la MEDIANA delle fasi e' ben sotto: e' l'audit di XS01,")
print(f" ritrovato per caso da un'altra strada.)")
# ---------------------------------------------------------------- 4. XSR01
print(f"\n[4] XSR01 — soglia PUBBLICATA ~$5.000, gate 2026-10-23 con haircut a $5.000 <= 40%")
from scripts.live.paper_xsr import FEE_LEG, MIN_ORDER, _book, _step, build_panel
@@ -245,6 +245,32 @@ def main() -> dict:
f"p99 {np.quantile(gap, 0.01):+.2%} peggiore {gap.min():+.2%}")
print(f" giorni che CHIUDONO sul minimo (m == R): {(np.abs(gap) < 1e-12).mean():.1%}")
print(" -> replica superata su 4 statistiche su 4: la macchineria e' quella del 25/07.")
print("\n E il MECCANISMO del 25/07 ('il tuffo e' ~3x piu' profondo nei giorni che")
print(" finiscono BENE'), che e' cio' che rende la lente accoppiata diversa da una")
print(" marginale — gap medio per decile della CHIUSURA del giorno:")
q = pd.qcut(pd.Series(D["R_live"]).rank(method="first"), 10, labels=False)
tab = pd.DataFrame({"R": D["R_live"], "gap": gap, "q": q.values}).groupby("q").agg(
R=("R", "mean"), gap=("gap", "mean"), chiude_sul_min=("gap", lambda x: float((x > -1e-12).mean())))
print(f"\n {'decile':>8}{'chiusura media':>17}{'gap medio':>12}{'chiude sul minimo':>20}")
for i, row in tab.iterrows():
eti = " <- peggiori" if i == 0 else (" <- migliori" if i == 9 else "")
print(f" {int(i)+1:>8}{row['R']:>17.2%}{row['gap']:>12.2%}{row['chiude_sul_min']:>20.0%}{eti}")
peggio5 = gap[D["R_live"] <= np.quantile(D["R_live"], 0.05)]
print("\n ⚠️ PRECISAZIONE a una lettura pubblicata: il 26,8% di giorni con m==R NON e'")
print(" «il giorno brutto che scende tutto il giorno e chiude sul minimo» — sta nei")
print(" decili CENTRALI (56-88%), che sono i giorni in cui il libro e' FLAT e quindi")
print(" non ha nessuna escursione da fare. Nel decile peggiore chiude sul minimo solo")
print(" il 4% delle volte. Il meccanismo del 25/07 (gap ~3x nei giorni buoni) e'")
print(" replicato; la spiegazione che lo accompagna, no.")
print(f"\n gap medio nel 5% di giorni PEGGIORI: {peggio5.mean():+.2%}"
f" (contro {gap[D['R_live'] >= np.quantile(D['R_live'], 0.95)].mean():+.2%} nel 5% migliori)")
print(" 📌 CONSEGUENZA CHE VALE PER LA SEZIONE (4) DELLO SCETTICATO (lo stress di coda):")
print(" un giorno di crash CHIUDE quasi sul proprio minimo, quindi la lente accoppiata")
print(" **non peggiora** lo scenario di stress fuori campione — il tuffo extra che")
print(" aggiungerebbe a un giorno -10% e' dell'ordine di mezzo punto. Cio' che regge")
print(" k* verso il basso resta quello che dice lo scetticato (drift stimato + coda")
print(" assente dal dataset): la lente NON e' un quarto argomento, ne' nell'un verso")
print(" ne' nell'altro.")
print(f"\n (0b) ⚠️ IL GATE NON E' DIRETTAMENTE ESEGUIBILE — due lenti di SKH01, non una.")
for nm, x in (("recon accoppiata (SKH canonico)", R_rec), ("book_series('hourly') (SKH live)", R_live)):
+35
View File
@@ -702,6 +702,41 @@ def main() -> None:
print(" scoperta; se resta a drift 0, il vantaggio e' pura DECORRELAZIONE e non dipende da")
print(" quell'ipotesi. Il numero, non la narrazione, dice quale delle due.")
hr("E-ter. IL BUCO CHE LA FINESTRA NON PUO' CHIUDERE — 2024-2026 non contiene un CRASH")
print(" L'ottimizzazione toglie peso a TP01 (Sharpe 0.79 su questa finestra). Ma TP01 e' lo")
print(" sleeve DIFENSIVO: il LOO del 26/07 misura il suo contributo hold-out NEGATIVO nel 99.1%")
print(" delle ancore e conclude 'uno sleeve difensivo si giudica sul SINISTRO, non sul premio'.")
print(" Su un conto funded la barriera morde PROPRIO nel sinistro. Qui la coppia crypto (che ha")
print(" storia dal 2019, quindi il 2022 dentro) e' ri-ottimizzata sulle due finestre:")
cry_full = pc.crypto_daily_tuples(1.0, 0.0).index
W2 = [{"TP01": i / 8, "SKH01": 1 - i / 8} for i in range(9)]
print(f"\n {'finestra':>26} {'giorni':>7} {'argmax TP/SKH':>15} {'leva':>6} {'J':>7} "
f"{'J del 75/25':>12} {'Sharpe argmax':>14}")
for st, lab in (("2019-03-14", "FULL 2019+ (col 2022)"), (START_WIN, "2024+ (senza crash)")):
pairs = {}
for w in W2:
c = pc.crypto_daily_tuples(w["TP01"], w["SKH01"])
c = c[c.index >= pd.Timestamp(st, tz="UTC")]
pairs[wkey(w)] = (deluck(c["R"].values), np.minimum(c["m"].values, c["R"].values) - c["R"].values)
nh = len(next(iter(pairs.values()))[0])
ev2 = boot_idx(nh, EV_H, N_PATHS, seed=8230)
fu2 = boot_idx(nh, FU_H, N_PATHS, seed=8231)
res = []
for k, (R_, G_) in pairs.items():
for lv in LEVS:
jj = (eval_sim(R_, G_, ev2, lv, "HYRO")["p_pass"]
* funded_sim(R_, G_, fu2, lv, "HYRO", 100_000.0)["p_alive"])
res.append((k, lv, jj, R_.mean() * 365 / (R_.std() * np.sqrt(365))))
bk, blv, bj, bsh = max(res, key=lambda x: x[2])
j75 = max(x[2] for x in res if x[0] == "75/25/0")
print(f" {lab:>26} {nh:>7d} {bk.rsplit('/', 1)[0]:>15} {blv:>6.3f} {bj:>7.3f} "
f"{j75:>12.3f} {bsh:>14.2f}")
print("\n Se anche sulla finestra che CONTIENE il 2022 l'ottimo scarta TP01, la sua sotto-")
print(" pesatura non e' un artefatto di finestra. Se invece li' TP01 pesa, allora la")
print(" raccomandazione del 2024+ e' una scommessa sull'assenza di crash — e su una barriera")
print(" di rovina quella scommessa la paghi una volta sola. **Questa finestra NON puo' dirlo")
print(" per XS01**, che nasce nel 2024: il buco resta aperto per lui, per costruzione.")
# ---------------------------------------------------------------- F. VRP01 / GTAA01
tick("E. banda ancora")
hr("F. E GLI SLEEVE 'INUTILI A $600'? — l'asimmetria del 25/07 §4 vale per XS01, non per tutti")
+100 -18
View File
@@ -50,8 +50,13 @@ della barra 5m e il fill, che vale decine di bps. La statistica giusta e' NORMAL
della barra: il nostro fill e' esso stesso un print della barra 5m che lo contiene, quindi
q = (fill - low)/(high - low) per un BUY, q = 1 - quello per un SELL.
Se i nostri ordini market avessero impatto, un BUY stamperebbe AL MASSIMO della barra -> q -> 1.
Senza impatto q e' un'estrazione ~uniforme -> E[q] = 0.5. sigma_q = 1/sqrt(12) = 0.289 e' ~20x
piu' piccolo del rumore in bps.
Senza impatto E[q] = 0.5, e il null NON si assume uniforme: si MISURA su ~18.000 barre vere.
ESITO SULLA MIA STESSA POTENZA: assumendo l'uniforme avevo dichiarato MDE_q = 0.19 di range
(~5 bps). Il null misurato e' 1.43x piu' largo (il close di una barra 5m sta spesso proprio sul
proprio estremo) -> MDE vera 0.27 = ~7.2 bps, contro 7.8 della via in bps. **La normalizzazione
non compra potenza utile.** Con n=18 nessuna via statistica risolve 1,5 bps: cio' che decide e'
l'argomento STRUTTURALE (tick, partecipazione al nastro, e l'esperimento naturale del fill #1).
Uso: nice -n 19 timeout 900 uv run python scripts/research/r0822_slip_audit.py
(--no-net salta il fetch dello strumento lineare e lo dichiara non misurato)
@@ -268,16 +273,24 @@ def main():
bad = (fills["fee"] / fills["filled"] * 1e4).median()
print(f" [se avessi usato la convenzione INVERSE: {bad:,.0f} bps -> "
f"{bad / FEE_TAKER_BPS_SIDE:,.0f}x il vero, e senza sollevare nulla]")
odd = fills[(fills["fee_bps"] - FEE_TAKER_BPS_SIDE).abs() > 0.05]
if len(odd):
print(f" ⚠️ {len(odd)}/{len(fills)} fill NON al tier {FEE_TAKER_BPS_SIDE:.2f}:")
for _, o in odd.iterrows():
print(f" #{o['i']} {o['fill_ts']} {o['asset']} {o['side']} "
f"{o['filled']} @ {o['price']:.2f} = ${o['notional']:.2f} · "
f"fee {o['fee']:.8f} = {o['fee_bps']:.2f} bps")
print(f" -> nozionale ${odd['notional'].min():.2f}: sotto i ~$7 la fee sembra")
print(f" arrotondata AL RIALZO. E' un costo REALE che il modello non ha, e")
print(f" morde solo gli ordini minuscoli (dove il libro vive a $600).")
# Il nuovo schema fee Deribit e' entrato il 2026-08-01. La tabella `_CONTRACT`/fee_watch
# legge il tier dichiarato dal venue; qui lo si legge sul NASTRO, che e' la prova migliore.
CUT = pd.Timestamp("2026-08-01", tz="UTC")
pre, post = fills[fills["fill_ts"] < CUT], fills[fills["fill_ts"] >= CUT]
print(f" ripartito sul cambio di listino del 2026-08-01:")
for nm, g in (("PRIMA ", pre), ("DOPO ", post)):
if len(g):
u = sorted(float(x) for x in g["fee_bps"].round(2).unique())
print(f" {nm} n={len(g):2d} fee/lato {u} "
f"(nozionale ${g['notional'].min():.0f}-${g['notional'].max():.0f})")
if len(pre) and len(post):
print(f" => il taglio {pre['fee_bps'].median():.2f} -> {post['fee_bps'].median():.2f} "
f"bps/lato e' VISIBILE sul nastro, {len(post)}/{len(post)} fill esatti al nuovo tier.")
print(f" Non e' un effetto taglia (i due fill a {pre['fee_bps'].median():.2f} bps sono da "
f"${pre['notional'].median():.0f}, sopra la mediana del campione).")
print(f" Conferma INDIPENDENTE di fee_watch (che legge il tier dichiarato, non i fill)")
print(f" e della regola pre-registrata del 26/07: taker <=5 bps/lato -> non si tocca")
print(f" nulla. Il listino si e' mosso NEL VERSO BUONO.")
# ---------------------------------------------------------------- 2. potenza, PRIMA
print("\n[2] POTENZA DICHIARATA — calcolata PRIMA di guardare lo slippage\n" + "-" * 96)
@@ -396,6 +409,22 @@ def main():
if len(x):
print(f" {a} {sd:4s} n={len(x):2d} media {x.mean():+7.2f} med {x.median():+7.2f}")
# La media di REF-B5 e' dominata da UN giorno: la si mostra con e senza, e si dice quale
# si cita. Non e' cherry-picking se si pubblicano entrambe e la regola e' dichiarata.
wild = R.nlargest(3, "bar_rng_bps")
day = wild["fill_ts"].dt.date.mode()[0]
keep = R[R["fill_ts"].dt.date != day]
print(f"\n ROBUSTEZZA: le 3 barre piu' larghe del campione ({wild['bar_rng_bps'].min():.0f}-"
f"{wild['bar_rng_bps'].max():.0f} bps di range) cadono tutte il {day}")
print(f" (ETH +14% in 6 ore). Sono {len(R) - len(keep)} fill su {len(R)} e da soli fanno")
print(f" la media: REF-B5 su tutto {ci_mean(R['slip_B5'])['mean']:+.2f} bps -> "
f"{ci_mean(keep['slip_B5'])['mean']:+.2f} bps senza quel giorno")
print(f" (IC95 [{ci_mean(keep['slip_B5'])['lo']:+.2f}, {ci_mean(keep['slip_B5'])['hi']:+.2f}], "
f"n={len(keep)}). La MEDIANA invece non si muove: "
f"{R['slip_B5'].median():+.2f} -> {keep['slip_B5'].median():+.2f}.")
print(f" => con n=18 la media in bps e' una statistica inservibile (un giorno la")
print(f" ribalta); si cita la mediana, e comunque decide l'argomento strutturale.")
# ---------------------------------------------------------------- 5. la statistica con potenza
print("\n[5] LA STATISTICA CON POTENZA — q, posizione del fill nella barra 5m\n" + "-" * 96)
q = R["q"].dropna()
@@ -407,8 +436,9 @@ def main():
outside = (R["in_bar"] == False).sum()
print(f" fill FUORI dal range della propria barra: {outside}/{dq['n']} "
f"(un fill fuori sarebbe strumento o ora sbagliati)")
print(f" q=1.00 esatti (fill AL massimo = firma di impatto): {(q >= 0.999).sum()}/{dq['n']}"
f" · q=0.00 esatti: {(q <= 0.001).sum()}/{dq['n']}")
hi_obs, lo_obs = int((q >= 0.99).sum()), int((q <= 0.01).sum())
print(f" fill sull'ESTREMO avverso (q>=0.99 = firma di impatto): {hi_obs}/{dq['n']}"
f" · sull'estremo favorevole (q<=0.01): {lo_obs}/{dq['n']}")
rng_med = float(R["bar_rng_bps"].median())
print(f" range mediano di barra 5m: {rng_med:.1f} bps -> lo scostamento misurato vale")
print(f" ~{(dq['mean'] - 0.5) * rng_med:+.2f} bps/lato "
@@ -429,17 +459,48 @@ def main():
pval = float((null >= dq["mean"]).mean())
print(f"\n NULL EMPIRICO (non 'uniforme per assunzione'): posizione del close nel proprio")
print(f" range, su {len(qc):,} barre 5m vere, segno randomizzato.")
sd_unif = (1 / np.sqrt(12)) / np.sqrt(dq["n"])
print(f" null: media {null.mean():.3f} · sd {null.std():.3f} "
f"(l'uniforme darebbe sd {(1 / np.sqrt(12)) / np.sqrt(dq['n']):.3f})")
f"(l'uniforme darebbe sd {sd_unif:.3f})")
print(f" p(one-sided, impatto = q piu' alto del null) = {pval:.3f}"
f" -> {'IMPATTO RILEVATO' if pval < 0.05 else 'nessun impatto rilevabile'}")
# ...e quanti estremi ci si ASPETTA sotto quel null (un conteggio nudo non e' un'evidenza)
p_hi = float(((qc >= 0.99).mean() + (qc <= 0.01).mean()) / 2)
print(f" attesi sotto il null: {p_hi * dq['n']:.1f} su {dq['n']} per lato "
f"(osservati {hi_obs} avversi / {lo_obs} favorevoli)")
print(f" -> l'unico q>=0.99 e' il fill #3 (ETH sell, q=0.996) su una barra da 7.5 bps di")
print(f" range: e' 0.03 bps di prezzo. Un estremo su una barra piatta non e' impatto.")
mde_q_true = 2.80 * float(null.std())
print(f"\n ⚠️ CORREZIONE ALLA MIA STESSA POTENZA DICHIARATA. Il null vero e' "
f"{null.std() / sd_unif:.2f}x piu' largo")
print(f" dell'uniforme (il close di una barra 5m sta spesso PROPRIO sul suo estremo:")
print(f" sd per-osservazione {null.std() * np.sqrt(dq['n']):.3f} contro 0.289 dell'uniforme).")
print(f" MDE_q vera = {mde_q_true:.3f} di range = {mde_q_true * rng_med:.1f} bps, non "
f"{mde_q * rng_med:.1f}: la mia stima")
print(f" a priori era ottimista del {(mde_q_true / mde_q - 1) * 100:.0f}%. => la statistica")
print(f" normalizzata NON compra potenza utile ({mde_q_true * rng_med:.1f} bps contro "
f"{mde_bps:.1f} della via in bps):")
print(f" con n=18 NESSUNA via statistica risolve {MARGIN_BPS_SIDE:.1f} bps. Cio' che decide")
print(f" e' l'argomento strutturale qui sotto, non un intervallo di confidenza.")
# -- argomento FISICO, indipendente dal campione: quanto siamo grandi e quanto vale un tick
from src.live.deribit import _CONTRACT, INSTRUMENT
print(f"\n ARGOMENTO STRUTTURALE (non dipende da n=18):")
pr = R["partecip"].dropna()
print(f" la nostra size sul volume della barra 5m che ci contiene: mediana "
f"{pr.median() * 100:.2f}% · max {pr.max() * 100:.2f}%")
f"{pr.median() * 100:.2f}% · p90 {pr.quantile(.9) * 100:.2f}% · max {pr.max() * 100:.2f}%")
top = R.dropna(subset=["partecip"]).nlargest(1, "partecip").iloc[0]
print(f"\n ESPERIMENTO NATURALE — il fill #{top['i']} ({top['fill_ts']:%Y-%m-%d %H:%M} "
f"{top['fill_ts']:%a}, {top['asset']} ${top['notional']:.0f}):")
print(f" e' stato il {top['partecip'] * 100:.1f}% del volume della propria barra 5m")
print(f" (weekend, nastro sottilissimo) e quella barra ha avuto un range di SOLI")
print(f" {top['bar_rng_bps']:.2f} bps, con il nostro print a meta' (q={top['q']:.2f}).")
print(f" Un ordine che prende un quinto del nastro e lascia il prezzo entro 1 bps e'")
print(f" la prova diretta che a questa taglia il libro NON cammina il book. Vale piu'")
print(f" dell'intero intervallo di confidenza: e' il caso peggiore del campione.")
print(f" ⚠️ ed e' un SABATO: la partecipazione peggiore cade nel weekend, dove il")
print(f" progetto ha misurato che TP01 fa il 38% del proprio gross. Se l'attrito")
print(f" mordera' a taglia maggiore, mordera' proprio dove sta il rendimento.")
for a in sorted(R["asset"].unique()):
tick = _CONTRACT[INSTRUMENT[a]]["tick"]
px = float(R[R["asset"] == a]["price"].median())
@@ -447,8 +508,29 @@ def main():
print(f" spread di 5 tick costerebbe {2.5 * tick / px * 1e4:.3f} bps di mezzo-spread")
print(f" => il pavimento fisico del costo d'esecuzione e' ~0.1 bps, DUE ordini di")
print(f" grandezza sotto il margine di {MARGIN_BPS_SIDE:.1f} bps. Perche' lo slippage")
print(f" sfondi il margine il libro dovrebbe CAMMINARE il book — e a <1% del nastro")
print(f" con 0/18 fill al massimo di barra non c'e' traccia che lo faccia.")
print(f" sfondi il margine il libro dovrebbe CAMMINARE il book — e a <1% del nastro,")
print(f" con MENO estremi avversi di quanti il null ne preveda ({hi_obs} contro "
f"{p_hi * dq['n']:.1f}), non c'e'")
print(f" traccia che lo faccia.")
# -- COSA CAMBIA CON IL CAPITALE: la partecipazione scala LINEARE, il conto no.
print(f"\n SCALA — l'unica parte di questa misura che ha una data di scadenza:")
eq = 636.0 # equity reale osservata (data/live/equity_seen.json)
print(f" la partecipazione scala lineare col capitale (equity oggi ~${eq:.0f}).")
print(f" {'capitale':>10} {'partecip. mediana':>19} {'p90':>9} {'max osservato':>15}")
for cap in (600, 5_000, 20_000, 100_000, 272_000):
k = cap / eq
mx = pr.max() * k * 100
print(f" {'$' + format(cap, ','):>10} {pr.median() * k * 100:>18.2f}% "
f"{pr.quantile(.9) * k * 100:>8.2f}% "
+ (f"{mx:>14.1f}%" if mx <= 100 else f"{'>tutta la barra':>15}"))
print(f" => a $600 l'impatto e' strutturalmente impossibile e questa misura lo conferma.")
print(f" Ma il caso peggiore osservato e' gia' {pr.max() * 100:.0f}% di una barra 5m: a $5k")
print(f" diventa {pr.max() * 5000 / eq * 100:.0f}%, a $20k {pr.max() * 20000 / eq * 100:.0f}%. "
f"**La misura di oggi NON si estrapola**")
print(f" al capitale del piano: va RIFATTA a ogni salto di taglia. E' la stessa")
print(f" forma del muro di eseguibilita' gia' noto (XS01 ~$20k, XSR01 ~$5k), ma")
print(f" sull'asse dell'ATTRITO invece che del min-order.")
# ---------------------------------------------------------------- 6. conversione
print("\n[6] CONVERSIONE IN SHARPE/CAGR — curva PUBBLICATA, non ricalcolata\n" + "-" * 96)
+578
View File
@@ -0,0 +1,578 @@
#!/usr/bin/env python
"""r0822_volsize_skeptic.py — SCETTICO del filone VOL-SIZE (r0822_vol_size.py).
BERSAGLIO. Un altro agente ha trovato che dare a SKH01 una SIZE PER-TRADE (cella congelata
`VTL tv20 w90`: size fissata all'ingresso = clip(0.20 / rv90(t-1), 0, 3)) vale **+0,070 di
Sharpe di libro a ISO-PESO**, positivo in 23/23 ancore e 8/8 anni. Ha dichiarato lui stesso che
il meccanismo non e' quello che sembra: il guadagno viene dalla FREQUENZA dei trade (+0,051 sul
solo filtro di attivita') e non dalla MAGNITUDINE (+0,003) -> non e' controllo del rischio, e'
un filtro di frequenza. E ha PRE-REGISTRATO senza eseguirlo il test che lo falsificherebbe.
QUATTRO ATTACCHI, dichiarati prima di misurare, con l'esito che mi aspetto:
A1 IL TEST PRE-REGISTRATO famiglia IV (inverse-vol per-trade sulla vol dell'ASSET) applicata
a SKH01_V1. La ragione dichiarata della famiglia IV e' la GEOMETRIA DELLO STOP: su V2-DD lo
stop e' una percentuale FISSA (4% long / 2% short), quindi il rischio in unita' di sigma
varia col regime; su V1 lo stop e' in ATR, cioe' gia' scalato sulla vol -> l'effetto DEVE
essere MINORE. Previsione direzionale: |eff. su V1| < |eff. su V2-DD|, cella per cella.
Se e' UGUALE, la spiegazione e' sbagliata anche se il numero regge.
ATTESA MIA: che il test sia SENZA POTENZA, perche' sospetto che IV non faccia niente
nemmeno su V2-DD (l'agente ha promosso VTL, non IV). Un test pre-registrato su una
famiglia inerte non falsifica nulla.
A2 LA CONTABILITA' — l'agente dichiara di aver trovato e riparato un look-ahead di
contabilita': "tutto il P&L di un trade e' contabilizzato il giorno di CHIUSURA, fino a
3,83 giorni dopo l'ingresso", costo "+0,04 di Sharpe hold-out fantasma", e ne ha tratto
una REGOLA pubblicata. Il compito mi chiede di verificare la riparazione troncando i dati
alla barra d'ingresso. Lo faccio, ma prima verifico la PREMESSA: quale giorno porta davvero
il P&L. ATTESA MIA: la riparazione e' corretta e ridondante.
A3 L'ATTACCO ALLA SPIEGAZIONE — se il guadagno e' un filtro di frequenza, deve essere
riproducibile SENZA ALCUN vol-target. Controllo piu' brutale possibile: size BINARIA 0/1
sulla stessa soglia della regola congelata (L>=1 <=> rv90<=20%), piu' un filtro che scarta
i trade nei periodi di maggiore ATTIVITA' della gamba. Se un binario replica il +0,070,
la formula di sizing e' ORNAMENTO. Controllo di segno obbligatorio: il binario INVERSO
(tieni solo i trade in alta vol) deve DANNEGGIARE, o non sto misurando un filtro.
A4 L'ASSE DELLA FINESTRA — l'agente dichiara che w e' un PICCO, non un plateau (w30 negativo,
w180 ~0, w90 ottimo). Un picco su un asse continuo con 23 ancore e' la firma classica di
un max-of-k. Misuro l'asse fitto (9 finestre) e rifaccio la scelta di w IN-SAMPLE-ONLY.
DISCIPLINA. Non ricalcolo niente che esista: importo `r0822_vol_size` (extract / equity_daily /
leg_daily / size_iv / leverage_series / sizes_from_L / book) e replico BIT-EXACT contro
`sleeves._skyhook_returns()` prima di ogni delta. Il confronto e' sempre ISO-PESO (baseline
ri-scalato allo stesso peso effettivo di SKH), perche' "alzare SKH01" e' gia' stato misurato e
respinto il 26/07. Statistica = MEDIANA DELLE DIFFERENZE APPAIATE per ancora (`anchor_luck_delta`),
mai differenza delle mediane.
USO: nice -n 19 timeout 900 uv run python scripts/research/r0822_volsize_skeptic.py [--every K]
--every 8 -> 3 ancore (pilota). default: tutte e 23.
"""
from __future__ import annotations
import argparse
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 altlib as A # noqa: E402
import r0702_anchor_skh01 as R # noqa: E402
import r0822_vol_size as V # noqa: E402 (bersaglio: riusato, non riscritto)
from src.strategies.skyhook import SKH01_V1 # noqa: E402
ASSETS = V.ASSETS
OFFSETS_FULL = V.OFFSETS_FULL
FROZEN = dict(tv=0.20, w=90, cap=3.0, active_only=False) # la cella congelata del LEAD
sh, vol, maxdd, cagr = V.sh, V.vol, V.maxdd, V.cagr
hold, ins, book = V.hold, V.ins, V.book
# --------------------------------------------------------------------------- famiglie di size
def _keep_to_sizes(ex: dict, keep: pd.Series) -> np.ndarray:
return V.sizes_from_L(ex, keep.astype(float))
def size_vtl(sp: dict):
def f(ex):
return V.sizes_from_L(ex, V.leverage_series(ex["base_daily"], **sp))
return f
def size_bin(sp: dict, mode: str):
"""Size BINARIA 0/1 dalla STESSA L della regola congelata. Nessuna magnitudine: o il trade
si fa a size 1, o non si fa. `ge1` usa la soglia della regola stessa (L>=1 <=> rv90<=tv);
`gemed` la mediana espandente causale di L; `lt1` e' l'INVERSO (controllo di segno)."""
def f(ex):
L = V.leverage_series(ex["base_daily"], **sp)
if mode == "ge1":
keep = pd.Series(L.values >= 1.0, index=L.index)
elif mode == "lt1":
keep = pd.Series(L.values < 1.0, index=L.index)
elif mode == "gemed":
med = L.expanding(min_periods=V.VT_WARM).median()
keep = pd.Series(np.where(med.notna().values, L.values >= med.values, True),
index=L.index)
else:
raise ValueError(mode)
return _keep_to_sizes(ex, keep)
return f
def size_bin_grad(sp: dict):
"""BINARIO + GRADUAZIONE: fra i trade tenuti, la size continua di L. Isola quanto aggiunge
la FORMULA una volta che la SELEZIONE e' gia' stata fatta."""
def f(ex):
L = V.leverage_series(ex["base_daily"], **sp)
g = pd.Series(np.where(L.values >= 1.0, L.values, 0.0), index=L.index)
return V.sizes_from_L(ex, g)
return f
def size_chan(mode: str, binary: bool, w: int = 90, cap: float = 3.0):
"""I DUE CANALI dentro `rv` di una serie all'88% di zeri, isolati come nel bersaglio:
mode='mag' -> vol calcolata sulle sole barre ATTIVE (MAGNITUDINE degli esiti)
mode='freq' -> conteggio di barre attive nella finestra (FREQUENZA dei trade)
binary=False riproduce le celle continue del bersaglio (`VTL neutro attive` / `ACT`);
binary=True le riduce a 0/1 alla mediana ESPANDENTE causale della propria L."""
def f(ex):
if mode == "mag":
Ls = V.leverage_series(ex["base_daily"], tv=None, w=w, cap=cap, active_only=True)
else:
Ls = V.activity_L(ex["base_daily"], w=w, cap=cap)
if not binary:
return V.sizes_from_L(ex, Ls)
med = Ls.expanding(min_periods=V.VT_WARM).median()
keep = pd.Series(np.where(med.notna().values, Ls.values >= med.values, True),
index=Ls.index)
return _keep_to_sizes(ex, keep)
return f
def size_dropact(w: int = 90, q: float = 0.75):
"""Scarta i trade il cui giorno d'ingresso cade nei periodi in cui la gamba e' stata piu'
ATTIVA (conteggio di barre attive negli ultimi w giorni sopra il quantile ESPANDENTE causale
q). Nessun vol-target, nessuna magnitudine, nessun prezzo: solo la frequenza dei trade."""
def f(ex):
base = ex["base_daily"]
cnt = (base != 0.0).astype(float).rolling(w, min_periods=max(5, w // 3)).sum().shift(1)
thr = cnt.expanding(min_periods=V.VT_WARM).quantile(q)
keep = pd.Series(np.where(thr.notna().values & cnt.notna().values,
cnt.values <= thr.values, True), index=base.index)
return _keep_to_sizes(ex, keep)
return f
def iv_family() -> dict:
d = {}
for p in (0.5, 1.0):
for w in (30, 90):
for cp in (2.0, 4.0):
d[f"IV p{p} w{w} cap{cp:.0f}"] = V.size_iv(p, w, cp)
return d
# --------------------------------------------------------------------------- motore
def legs(EX: dict, funcs: dict) -> dict:
"""name -> serie giornaliera della gamba SKH 50/50, per un'ancora."""
out = {"BASE": V.leg_daily(EX, {a: V.size_flat(EX[a]) for a in ASSETS})}
for nm, fn in funcs.items():
out[nm] = V.leg_daily(EX, {a: fn(EX[a]) for a in ASSETS})
return out
def iso_pair(TP: pd.Series, leg: pd.Series, leg_base: pd.Series):
"""Libro della variante e libro di CONTROLLO ISO-PESO (baseline ri-scalato alla stessa vol
di gamba -> stesso peso effettivo di SKH). E' la convenzione del bersaglio, riusata."""
vb = vol(leg_base)
m_s = vol(leg) / vb if vb > 0 else 1.0
return book(TP, leg), book(TP, leg_base * m_s), m_s
def delta_table(BK: dict, CT: dict, names, offs, label: str, extra_hold=True) -> dict:
res = {}
print(f" {'cella':<26}{'ISO dShFULL':>12}{'pos/n':>8}{'banda':>20}"
+ (f"{'ISO dShHOLD':>13}{'pos/n':>8}" if extra_hold else "") + f"{'wSKHeff':>9}")
for nm in names:
dF = A.anchor_luck_delta(lambda o, n=nm: BK[n][o], lambda o, n=nm: CT[n][o], offs,
metric=sh)
row = dict(dF=dF["median_paired"], nF=dF["n_positive"], lo=dF["lo"], hi=dF["hi"])
s = (f" {nm:<26}{row['dF']:>+12.3f}{row['nF']:>5}/{len(offs):<3}"
f" [{row['lo']:+.3f},{row['hi']:+.3f}]".ljust(20 + 26 + 12 + 8))
s = (f" {nm:<26}{row['dF']:>+12.3f}{row['nF']:>5}/{len(offs):<3}"
f"{'[%+.3f,%+.3f]' % (row['lo'], row['hi']):>20}")
if extra_hold:
dH = A.anchor_luck_delta(lambda o, n=nm: hold(BK[n][o]),
lambda o, n=nm: hold(CT[n][o]), offs, metric=sh)
row.update(dH=dH["median_paired"], nH=dH["n_positive"])
s += f"{row['dH']:>+13.3f}{row['nH']:>5}/{len(offs):<3}"
we = float(np.median([0.25 * m for m in MSCALE[nm]])) if nm in MSCALE else float("nan")
row["weff"] = we
s += f"{we:>9.3f}"
print(s)
res[nm] = row
return res
MSCALE: dict = {}
def per_year(BK: dict, CT: dict, nm: str, offs) -> tuple:
yrs = sorted({int(y) for y in BK[nm][offs[0]].index.year})
cells = []
for y in yrs:
v = [sh(BK[nm][o][BK[nm][o].index.year == y]) - sh(CT[nm][o][CT[nm][o].index.year == y])
for o in offs if (BK[nm][o].index.year == y).sum() > 20]
cells.append(float(np.median(v)) if v else float("nan"))
return yrs, cells
# =========================================================================== main
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--every", type=int, default=1)
args = ap.parse_args()
offs = OFFSETS_FULL[:: max(1, args.every)]
t0 = time.time()
L = 112
print("=" * L)
print(" r0822 VOL-SIZE — SCETTICO. Bersaglio: `VTL tv20 w90` = +0,070 ISO dShFULL (23/23)")
print("=" * L)
print(f" ancore: {len(offs)}/{len(OFFSETS_FULL)} della griglia a priori 02/07 {list(offs)}")
print(" lente: path CANONICO (identica nelle due braccia di ogni confronto). TP01 canonico.")
TP = A.tp01_baseline_daily()
# ------------------------------------------------------------------ celle
IV = iv_family()
FUN_V2 = dict(IV)
FUN_V2["VTL tv20 w90 (LEAD)"] = size_vtl(FROZEN)
FUN_V2["BIN L>=1 (0/1)"] = size_bin(FROZEN, "ge1")
FUN_V2["BIN L>=med (0/1)"] = size_bin(FROZEN, "gemed")
FUN_V2["BIN L<1 (INVERSO)"] = size_bin(FROZEN, "lt1")
FUN_V2["BIN+GRAD"] = size_bin_grad(FROZEN)
FUN_V2["DROP attivita' >p75"] = size_dropact(90, 0.75)
FUN_V2["DROP attivita' >p50"] = size_dropact(90, 0.50)
FUN_V2["canale MAGNITUDINE cont."] = size_chan("mag", False)
FUN_V2["canale FREQUENZA cont."] = size_chan("freq", False)
FUN_V2["canale MAGNIT. 0/1"] = size_chan("mag", True)
FUN_V2["canale FREQ. 0/1"] = size_chan("freq", True)
WGRID = (15, 20, 30, 45, 60, 90, 120, 180, 270)
for w in WGRID:
if w != 90:
FUN_V2[f"VTL tv20 w{w}"] = size_vtl(dict(FROZEN, w=w))
FUN_V1 = dict(IV)
FUN_V1["VTL tv20 w90 (LEAD)"] = size_vtl(FROZEN)
FUN_V1["BIN L>=1 (0/1)"] = size_bin(FROZEN, "ge1")
NTRIAL = len(FUN_V2) + len(FUN_V1) + 2 # +2 baseline
print(f"\n GRIGLIA DICHIARATA: {len(FUN_V2)} celle su V2-DD + {len(FUN_V1)} su V1 "
f"+ 2 baseline = {NTRIAL} valutazioni x {len(offs)} ancore. Nessuna cella scelta")
print(" dopo aver guardato: IV e' la famiglia PRE-REGISTRATA, VTL/w e' l'asse dichiarato")
print(" fragile dall'agente, BIN/DROP sono controlli miei dichiarati sopra.")
# ------------------------------------------------------------------ ciclo ancore
BK2, CT2, BK1, CT1 = {}, {}, {}, {}
EX0 = None
print("\n calcolo per ancora ...", flush=True)
for o in offs:
EXV2 = {a: V.extract(a, o) for a in ASSETS}
EXV1 = {a: V.extract(a, o, SKH01_V1) for a in ASSETS}
if o == offs[0]:
EX0 = (EXV2, EXV1)
for tag, EXX, FUN, BKd, CTd in (("V2", EXV2, FUN_V2, BK2, CT2),
("V1", EXV1, FUN_V1, BK1, CT1)):
lg = legs(EXX, FUN)
base = lg["BASE"]
for nm, s in lg.items():
b, c, m_s = iso_pair(TP, s, base)
BKd.setdefault(nm, {})[o] = b
CTd.setdefault(nm, {})[o] = c
if tag == "V2":
MSCALE.setdefault(nm, []).append(m_s)
if o != offs[0]:
del EXV2, EXV1
print(f" fatto in {time.time()-t0:.0f}s")
EXV2, EXV1 = EX0
# ------------------------------------------------------------------ §0 SANITY
print("\n" + "-" * L)
print(" 0. SANITY — replica BIT-EXACT prima di qualunque delta (se non e' 0, il problema")
print(" e' mio e va detto)")
print("-" * L)
from src.portfolio.sleeves import _skyhook_returns
mine = V.leg_daily(EXV2, {a: V.size_flat(EXV2[a]) for a in ASSETS})
off = _skyhook_returns()
d0 = float(np.max(np.abs(mine.values - off.values))) if len(mine) == len(off) else float("nan")
print(f" gamba 50/50 off{offs[0]} vs sleeves._skyhook_returns(): max|dif| = {d0:.2e} "
f"({len(mine)} giorni)")
assert d0 < 1e-15, "replica NON bit-exact"
for a in ASSETS:
ref = R.run_asset(a, offs[0])[0]
R._CACHE.clear()
dd = float(np.max(np.abs(V.equity_daily(EXV2[a], V.size_flat(EXV2[a])).values - ref.values)))
print(f" {a} vs r0702_anchor_skh01.run_asset: max|dif| = {dd:.2e}")
assert dd < 1e-15
lead = "VTL tv20 w90 (LEAD)"
dLEAD = A.anchor_luck_delta(lambda o: BK2[lead][o], lambda o: CT2[lead][o], offs, metric=sh)
dLEADH = A.anchor_luck_delta(lambda o: hold(BK2[lead][o]), lambda o: hold(CT2[lead][o]),
offs, metric=sh)
print(f" RIPRODUZIONE DEL TITOLO: ISO dShFULL {dLEAD['median_paired']:+.3f} "
f"({dLEAD['n_positive']}/{len(offs)}) ISO dShHOLD {dLEADH['median_paired']:+.3f} "
f"({dLEADH['n_positive']}/{len(offs)}) [pubblicato +0,070 (23/23) / +0,101 (19/23)]")
med_F = float(np.median([sh(BK2["BASE"][o]) for o in offs]))
med_H = float(np.median([sh(hold(BK2["BASE"][o])) for o in offs]))
med_D = float(np.median([maxdd(BK2["BASE"][o]) for o in offs]))
print(f" libro BASE (V2-DD), mediana della banda: ShFULL {med_F:.3f} / ShHOLD {med_H:.3f} "
f"/ maxDD {med_D*100:.1f}% [pubblicato 1,626 / 1,037 / 10,4%]")
# ------------------------------------------------------------------ §1 CONTABILITA'
print("\n" + "-" * L)
print(" 1. LA CONTABILITA' — verifica della PREMESSA prima della riparazione")
print(" (l'agente: 'tutto il P&L di un trade e' contabilizzato il giorno di CHIUSURA')")
print("-" * L)
tot_ent = tot_ex = tot_chk = 0
for a in ASSETS:
ex = EXV2[a]
d_e = ex["idx"][ex["i_ent"]].floor("D")
d_x = ex["idx"][ex["i_ex"]].floor("D")
dl = ex["base_daily"]
multi = np.where(d_e.values != d_x.values)[0]
ne = nx = ck = 0
for k in multi:
n = float(ex["net"][k])
if abs(n) < 1e-6:
continue
ck += 1
ne += int(abs(float(dl.get(d_e[k], 0.0)) - n) < 1e-9)
nx += int(abs(float(dl.get(d_x[k], 0.0)) - n) < 1e-9)
print(f" {a}: {ck} trade multi-giorno -> ritorno giornaliero == net del trade "
f"al giorno d'INGRESSO {ne}/{ck}, al giorno di USCITA {nx}/{ck}")
tot_ent += ne; tot_ex += nx; tot_chk += ck
print(f" TOTALE: INGRESSO {tot_ent}/{tot_chk} ({tot_ent/max(tot_chk,1)*100:.1f}%) "
f"USCITA {tot_ex}/{tot_chk} ({tot_ex/max(tot_chk,1)*100:.1f}%)")
print(" -> `backtest_signals` fa `equity[i_ent:i_ex+1] = capital`: il gradino comincia")
print(" alla barra d'INGRESSO e il resample giornaliero (last) lo legge quel giorno.")
print("\n (a) TRONCAMENTO ALLA BARRA D'INGRESSO — le size della regola congelata "
"ricalcolate")
print(" con i soli dati <= barra d'ingresso devono coincidere ESATTAMENTE")
worst = 0.0
ntest = 0
for a in ASSETS:
ex = EXV2[a]
full = size_vtl(FROZEN)(ex)
sel = [k for k in range(len(ex["i_ent"])) if ex["i_ent"][k] >= V.SIG_WARM]
sel = sel[:: max(1, len(sel) // 25)][:25]
bad = 0
for k in sel:
i = int(ex["i_ent"][k])
# ricostruisco la gamba usando SOLO i trade gia' CHIUSI prima dell'ingresso k
sub = {kk: ex[kk] for kk in ("n", "idx")}
m = ex["i_ex"] < i
sub = dict(n=i + 1, idx=ex["idx"][: i + 1],
i_ent=ex["i_ent"][m], i_ex=ex["i_ex"][m], net=ex["net"][m])
if not len(sub["i_ent"]):
continue
bd = V.equity_daily(sub, np.ones(len(sub["i_ent"])))
Lt = V.leverage_series(bd, **FROZEN)
day = ex["idx"][i].floor("D")
v = Lt.reindex([day]).values[0]
v = 1.0 if not np.isfinite(v) else float(v)
worst = max(worst, abs(v - float(full[k])))
bad += int(abs(v - float(full[k])) > 1e-9)
ntest += 1
print(f" {a}: {len(sel)-bad}/{len(sel)} size identiche a dati troncati "
f"(divergenze {bad})")
print(f" max|dif| su {ntest} size ricalcolate da zero: {worst:.2e} -> "
f"{'CAUSALE' if worst < 1e-9 else 'NON CAUSALE'}")
print("\n (b) LA 'RIPARAZIONE' E' UN NO-OP: size-all'INGRESSO vs moltiplicare la SERIE")
print(" GIORNALIERA, a parita' di tutto il resto (stessa L, PER ASSET)")
for a in ASSETS:
ex = EXV2[a]
Ls = V.leverage_series(ex["base_daily"], **FROZEN)
s_in = V.equity_daily(ex, V.sizes_from_L(ex, Ls))
s_mul = ex["base_daily"] * Ls.reindex(ex["base_daily"].index).fillna(1.0)
j = pd.concat({"a": s_in, "b": s_mul}, axis=1, join="inner").dropna()
print(f" {a}: max|dif| = {np.max(np.abs(j['a']-j['b'])):.2e} "
f"Sh ingresso {sh(s_in):.4f} vs Sh moltiplicazione {sh(s_mul):.4f}")
print("\n (c) ALLORA COS'E' IL '+0,04 FANTASMA'? Il controllo NAIVE del bersaglio calcola L")
print(" sulla gamba 50/50 COMBINATA; la versione 'causale' la calcola PER ASSET.")
print(" Terza variante di controllo: L combinata, applicata come size all'INGRESSO.")
lg_base = {o: None for o in offs}
d_comb, d_perass = [], []
for o in offs[: min(6, len(offs))]:
E2 = {a: V.extract(a, o) for a in ASSETS}
base = V.leg_daily(E2, {a: V.size_flat(E2[a]) for a in ASSETS})
Lc = V.leverage_series(base, **FROZEN)
naive = base * Lc.reindex(base.index).fillna(1.0)
comb_in = V.leg_daily(E2, {a: V.sizes_from_L(E2[a], Lc) for a in ASSETS})
per_ass = V.leg_daily(
E2, {a: V.sizes_from_L(E2[a], V.leverage_series(E2[a]["base_daily"], **FROZEN))
for a in ASSETS})
j = pd.concat({"a": naive, "b": comb_in}, axis=1, join="inner").dropna()
d_comb.append(float(np.max(np.abs(j["a"] - j["b"]))))
b1, c1, _ = iso_pair(TP, naive, base)
b2, c2, _ = iso_pair(TP, per_ass, base)
d_perass.append(sh(hold(b1)) - sh(hold(c1)) - (sh(hold(b2)) - sh(hold(c2))))
del E2
print(f" L combinata: 'moltiplica la serie' vs 'size all'ingresso' -> "
f"max|dif| mediano {np.median(d_comb):.2e} (identiche)")
print(f" differenza ISO dShHOLD [L combinata] - [L per asset], mediana appaiata su "
f"{len(d_perass)} ancore: {np.median(d_perass):+.3f}")
print(" -> il divario NAIVE/CAUSALE non e' QUANDO si applica la size, e' SU QUALE")
print(" SERIE si calcola L (gamba combinata vs per asset).")
# ------------------------------------------------------------------ §2 TEST PRE-REGISTRATO
print("\n" + "-" * L)
print(" 2. IL TEST PRE-REGISTRATO — famiglia IV su V2-DD (stop % FISSO) vs V1 (stop in ATR)")
print(" Previsione dichiarata dall'agente: |effetto su V1| < |effetto su V2-DD|.")
print("-" * L)
n2 = {a: len(EXV2[a]["i_ent"]) for a in ASSETS}
n1 = {a: len(EXV1[a]["i_ent"]) for a in ASSETS}
print(f" trade all'ancora {offs[0]}: V2-DD {n2} V1 {n1}")
mb2 = float(np.median([sh(BK2["BASE"][o]) for o in offs]))
mb1 = float(np.median([sh(BK1["BASE"][o]) for o in offs]))
print(f" libro BASE mediana di banda: V2-DD ShFULL {mb2:.3f} | V1 ShFULL {mb1:.3f}")
names_iv = list(IV)
print(f"\n {'cella IV':<22}{'V2-DD ISO dF':>14}{'pos/n':>8}{'V1 ISO dF':>12}{'pos/n':>8}"
f"{'|V1|<|V2|?':>12}")
ok = tot = 0
e2s, e1s = [], []
for nm in names_iv:
a2 = A.anchor_luck_delta(lambda o, n=nm: BK2[n][o], lambda o, n=nm: CT2[n][o], offs,
metric=sh)
a1 = A.anchor_luck_delta(lambda o, n=nm: BK1[n][o], lambda o, n=nm: CT1[n][o], offs,
metric=sh)
e2, e1 = a2["median_paired"], a1["median_paired"]
e2s.append(e2); e1s.append(e1)
good = abs(e1) < abs(e2)
ok += int(good); tot += 1
print(f" {nm:<22}{e2:>+14.3f}{a2['n_positive']:>5}/{len(offs):<3}{e1:>+12.3f}"
f"{a1['n_positive']:>5}/{len(offs):<3}{('SI' if good else 'NO'):>12}")
print(f"\n previsione verificata in {ok}/{tot} celle della famiglia")
print(f" |effetto| mediano di famiglia: V2-DD {np.median(np.abs(e2s)):.3f} "
f"V1 {np.median(np.abs(e1s)):.3f}")
print(f" effetto mediano SEGNATO: V2-DD {np.median(e2s):+.3f} V1 {np.median(e1s):+.3f}")
print(" ⚠️ POTENZA DEL TEST: se |effetto su V2-DD| e' gia' ~0, la previsione e' verificabile")
print(" solo per caso e il gate pre-registrato NON puo' falsificare niente.")
for nm in ("VTL tv20 w90 (LEAD)", "BIN L>=1 (0/1)"):
a2 = A.anchor_luck_delta(lambda o, n=nm: BK2[n][o], lambda o, n=nm: CT2[n][o], offs,
metric=sh)
a1 = A.anchor_luck_delta(lambda o, n=nm: BK1[n][o], lambda o, n=nm: CT1[n][o], offs,
metric=sh)
print(f" (riferimento) {nm:<24} V2-DD {a2['median_paired']:+.3f} "
f"({a2['n_positive']}/{len(offs)}) V1 {a1['median_paired']:+.3f} "
f"({a1['n_positive']}/{len(offs)})")
# ------------------------------------------------------------------ §3 ATTACCO ALLA SPIEGAZIONE
print("\n" + "-" * L)
print(" 3. ATTACCO ALLA SPIEGAZIONE — quanto del +0,070 resta senza NESSUN vol-target?")
print(" Tutte ISO-PESO vs lo stesso baseline. 'wSKHeff' = peso effettivo di SKH nel libro")
print("-" * L)
grp = [lead, "BIN L>=1 (0/1)", "BIN+GRAD", "BIN L>=med (0/1)", "BIN L<1 (INVERSO)",
"DROP attivita' >p75", "DROP attivita' >p50",
"canale MAGNITUDINE cont.", "canale FREQUENZA cont.",
"canale MAGNIT. 0/1", "canale FREQ. 0/1"]
RES = delta_table(BK2, CT2, grp, offs, "attacco")
base_eff = RES[lead]["dF"]
print()
for nm in grp[1:]:
frac = RES[nm]["dF"] / base_eff * 100 if abs(base_eff) > 1e-9 else float("nan")
print(f" {nm:<26} recupera {frac:>6.0f}% del vantaggio del LEAD")
# quanti trade tocca ciascun filtro
print(f"\n {'cella':<26}{'trade tenuti':>14}{'su':>6}{'size mediana':>14}"
f"{'size = 0':>10}")
for nm in [lead] + grp[1:]:
kept = tot_t = zer = 0
med = []
for a in ASSETS:
s = FUN_V2[nm](EXV2[a])
tot_t += len(s); kept += int((s > 0).sum()); zer += int((s == 0).sum())
med.append(s)
mm = float(np.median(np.concatenate(med)))
print(f" {nm:<26}{kept:>14}{tot_t:>6}{mm:>14.2f}{zer:>10}")
print(f"\n COSA SELEZIONA il filtro (ancora {offs[0]}, i due asset uniti): net medio dei")
print(" trade TENUTI vs SCARTATI, e attivita' trailing media dei due gruppi")
print(f" {'cella':<26}{'net TENUTI':>12}{'net SCARTATI':>14}{'att.TEN':>10}{'att.SCA':>10}"
f"{'%tenuti':>9}")
for nm in ("BIN L>=1 (0/1)", "BIN L>=med (0/1)", "BIN L<1 (INVERSO)",
"DROP attivita' >p50", "canale MAGNIT. 0/1", "canale FREQ. 0/1"):
nk, nd, ak, ad = [], [], [], []
for a in ASSETS:
ex = EXV2[a]
szv = FUN_V2[nm](ex)
cnt = ((ex["base_daily"] != 0.0).astype(float)
.rolling(90, min_periods=30).sum().shift(1))
av = cnt.reindex(ex["day_ent"]).values
keep = szv > 0
nk += list(ex["net"][keep]); nd += list(ex["net"][~keep])
ak += list(av[keep]); ad += list(av[~keep])
f_ = 100.0 * len(nk) / max(len(nk) + len(nd), 1)
print(f" {nm:<26}{np.mean(nk)*100:>11.2f}%{np.mean(nd)*100 if nd else float('nan'):>13.2f}%"
f"{np.nanmean(ak):>10.1f}{np.nanmean(ad) if ad else float('nan'):>10.1f}{f_:>9.0f}")
print("\n per anno (ISO dSh, mediana appaiata sulle ancore):")
yrs = None
for nm in [lead, "BIN L>=1 (0/1)", "DROP attivita' >p75", "BIN L<1 (INVERSO)"]:
yrs, cells = per_year(BK2, CT2, nm, offs)
if nm == lead:
print(" " + f"{'':<26}" + "".join(f"{y:>8}" for y in yrs))
npos = int(np.nansum(np.asarray(cells) > 0))
print(f" {nm:<26}" + "".join(f"{c:>+8.2f}" for c in cells) +
f" anni positivi {npos}/{len(yrs)}")
# ------------------------------------------------------------------ §4 ASSE DELLA FINESTRA
print("\n" + "-" * L)
print(" 4. L'ASSE DELLA FINESTRA — picco o plateau? + scelta di w IN-SAMPLE-ONLY")
print("-" * L)
wnames = [(w, f"VTL tv20 w{w}" if w != 90 else lead) for w in WGRID]
print(f" {'w':>6}{'ISO dShFULL':>14}{'pos/n':>8}{'ISO dShIS':>12}{'ISO dShHOLD':>13}"
f"{'pos/n':>8}")
wf, wi, wh = {}, {}, {}
for w, nm in wnames:
dF = A.anchor_luck_delta(lambda o, n=nm: BK2[n][o], lambda o, n=nm: CT2[n][o], offs,
metric=sh)
dI = A.anchor_luck_delta(lambda o, n=nm: ins(BK2[n][o]), lambda o, n=nm: ins(CT2[n][o]),
offs, metric=sh)
dH = A.anchor_luck_delta(lambda o, n=nm: hold(BK2[n][o]), lambda o, n=nm: hold(CT2[n][o]),
offs, metric=sh)
wf[w], wi[w], wh[w] = dF["median_paired"], dI["median_paired"], dH["median_paired"]
print(f" {w:>6}{wf[w]:>+14.3f}{dF['n_positive']:>5}/{len(offs):<3}{wi[w]:>+12.3f}"
f"{wh[w]:>+13.3f}{dH['n_positive']:>5}/{len(offs):<3}")
wbest_full = max(WGRID, key=lambda w: wf[w])
wbest_is = max(WGRID, key=lambda w: wi[w])
print(f"\n argmax su FULL (cio' che l'agente ha pubblicato): w = {wbest_full} "
f"({wf[wbest_full]:+.3f})")
print(f" argmax IN-SAMPLE-ONLY (la sola scelta ammessa): w = {wbest_is} "
f"(IS {wi[wbest_is]:+.3f}) -> quella cella vale FULL {wf[wbest_is]:+.3f} / "
f"HOLD {wh[wbest_is]:+.3f}")
order = sorted(WGRID)
k = order.index(wbest_full)
nb = [order[k - 1] if k > 0 else None, order[k + 1] if k + 1 < len(order) else None]
for w in nb:
if w is not None:
print(f" vicino w={w:<4} vale {wf[w]:+.3f} = {wf[w]/wf[wbest_full]*100:>5.0f}% "
f"del picco")
pos = sum(1 for w in WGRID if wf[w] > 0)
print(f" celle con ISO dShFULL > 0: {pos}/{len(WGRID)} "
f"spread della famiglia {max(wf.values())-min(wf.values()):.3f} "
f"sd fra celle {np.std(list(wf.values()), ddof=1):.3f}")
print(f" stima ONESTA se w e' SCELTO (mediana della famiglia, come per la banda "
f"d'ancora): {np.median(list(wf.values())):+.3f} FULL / "
f"{np.median(list(wh.values())):+.3f} HOLD contro il picco "
f"{wf[wbest_full]:+.3f} / {wh[wbest_full]:+.3f}")
rho = float(np.corrcoef([wi[w] for w in WGRID], [wh[w] for w in WGRID])[0, 1])
print(f" corr(ISO dSh in-sample, ISO dSh hold-out) fra le {len(WGRID)} finestre: {rho:+.2f}")
print(" -> se e' ~0 o negativa, la scelta di w in-sample NON predice l'hold-out: l'asse")
print(" e' rumore e il picco a w=90 e' un max-of-k.")
# ------------------------------------------------------------------ §5 sintesi
print("\n" + "-" * L)
print(" 5. SINTESI DEI NUMERI CHE CONTANO")
print("-" * L)
print(f" fortuna d'ancora del LIBRO BASE: ShFULL canonico "
f"{sh(BK2['BASE'][0]) if 0 in BK2['BASE'] else float('nan'):.3f} vs mediana "
f"{med_F:.3f}")
print(f" vantaggio del LEAD (ISO): {dLEAD['median_paired']:+.3f} FULL / "
f"{dLEADH['median_paired']:+.3f} HOLD")
print(f" riprodotto dal FILTRO BINARIO: {RES['BIN L>=1 (0/1)']['dF']:+.3f} FULL / "
f"{RES['BIN L>=1 (0/1)']['dH']:+.3f} HOLD")
print(f" controllo di segno (INVERSO): {RES['BIN L<1 (INVERSO)']['dF']:+.3f} FULL")
print(f"\n fatto in {time.time()-t0:.0f}s")
print("=" * L)
if __name__ == "__main__":
main()