research(wave-0822): XS01-OOS — l'edge esiste FUORI dalla finestra di scoperta ed e' piu' grande li'; il gate del prop-alloc si sostituisce, non si aspetta

This commit is contained in:
Adriano Dal Pastro
2026-08-22 20:52:40 +00:00
parent 0f2e195674
commit 1ec928cf09
2 changed files with 116 additions and 10 deletions
+84 -1
View File
@@ -692,7 +692,7 @@ stessi hanno nominato senza poterli eseguire**. Stesso contratto di consegna, st
| # | filone | domanda che decide | verdetto |
|---|---|---|---|
| 21 | XS01-OOS | il vantaggio del PROP-ALLOC poggia sul drift di XS01 misurato sulla sua finestra di scoperta: regge fuori? | _in corso_ |
| 21 | XS01-OOS | il vantaggio del PROP-ALLOC poggia sul drift di XS01 misurato sulla sua finestra di scoperta: regge fuori? | **LEAD** — regge, ed e' PIU' GRANDE fuori |
| 22 | MAKER | l'esecuzione passiva e' una fonte di ritorno, al netto del costo di non essere eseguiti? | **SCARTATO** — il segno dipende da `<` contro `<=` |
| 23 | BOCPD | un rilevatore di cambio di regime vero batte il miglior lookback COSTANTE? | **SCARTATO** — filone chiuso definitivamente |
| 24 | CRITICO | cosa NON e' stato misurato, e quale singola misura mancante vale di piu' | **3 correzioni all'ondata**, 1 regola mia ritirata |
@@ -889,3 +889,86 @@ tre); il muro di XS01 con **tre** numeri; la leva riceve guida **opposta** da GR
PROP-ALLOC; il peso di SKH01 e' **respinto da un agente e raddoppiato da un altro**. Piu' 6 modalita'
non esplorate e 6 punti di autocritica — incluso che i **20 MB del dataset futures 2018-2026 (l'unico
con la coda 2022) vivono solo in uno scratchpad di sessione**.
---
## 21 — XS01-OOS (XS01 fuori dalla sua finestra di scoperta) — **il risultato di testa della serata**
`scripts/research/r0822b_xs01_oos.py` (78-126 s, cache nello scratchpad, seconda corsa **offline**,
numeri identici su due corse). **140 trial dichiarati** (contati al rialzo: le 6 varianti di config
sono contate come trial anche se sono la *rivalidazione di una selezione passata*), piu' 1.200
estrazioni di null che **non** sono trial.
**L'idea:** il gate del PROP-ALLOC diceva *"non aprire un funded prima che XS01 abbia una finestra
fuori dal 2024-2026"* = un'attesa di anni. **Quella finestra esiste gia'** — gli stessi 19 ticker su
**Binance spot USDT 1d, 2021-01 → 2023-12**, che contiene **LUNA e FTX**. Meccanismo **congelato**
(blend [30,90], H=10, k=5, gate p30, vol-target 20%), nessuna griglia cercata.
⚠️ **L'obiezione ovvia — "Binance non e' la verita'" — e' stata misurata, non aggirata:** quella
regola del progetto riguarda l'**ancoraggio di prezzo**, non un ranking cross-sezionale, e lo scarto
USDT e' un **fattore comune** che sparisce nello z-score. Verificato: corr dei ritorni giornalieri per
asset **≥0,9992** (min NEAR), |differenza| mediana **5,1 bps**, e soprattutto lo **stesso sleeve**
calcolato sui due venue da' **corr 0,9991, dSharpe 0,025**. Replica dello sleeve ufficiale
`max|diff| = 0,0`. Dato certificato: **0 gap, flat 0,00-0,71%, volumi zero 0,00%**, nessun run
iniziale a volume 0 (il rivelatore del backfill sintetico, lezione AXS).
📌 **IL RISULTATO, e va nel verso opposto a quello temuto:**
| | Sharpe (**mediana di fase**) | banda 10 fasi | fasi >0 | maxDD | CAGR |
|---|---|---|---|---|---|
| **fuori campione 2021-2023 (U13)** | **+1,12** | [+0,74, +1,54] | **10/10** | **22,0%** | +26,1% |
| finestra di scoperta 2024+ (U13) | +0,37 | — | — | 20,5% | +8,9% |
| **fuori campione (U19-EXP)** | **+1,17** | [+0,91, +1,78] | **10/10** | 20,9% | +30,0% |
| XS01 **ufficiale** (HL, 2024+) | +1,15 | [+0,46, +1,59] | — | **10,8%** | +29,0% |
**Differenza appaiata per fase (fuori campione scoperta): +0,668, positiva in 10/10 fasi.**
**Il fuori campione e' la finestra MIGLIORE, non peggiore.** Per anno: 2021 **+2,14** · 2022 +0,22 ·
2023 +0,84 · 2024 0,48 · 2025 +0,68 · 2026 +2,26.
**Sul LIBRO, solo fuori campione** (TP01+SKH01 75/25 + XS01, n=1076, dati che PROP-ALLOC non aveva mai
visto): w=0 → Sh **+1,17** / DD 9,4% · w=0,25 → **+1,65 / 6,0%** · w=0,33 → +1,67 / 6,6% ·
**w=0,50 (l'argmax di PROP-ALLOC) → +1,56 / 8,9%**. corr(XS01, libro) **0,019**.
**Gate:** `causality` leak-free (randomizzando i prezzi dopo la decisione `max|diff| = 0`);
`marginal_vs_tp01` **ADDS** su entrambi gli universi (corr +0,031 / 0,01, `is_hedge` **False**);
banda di fase `gate_pass=True` **4/4**, e la fortuna d'ancora e' **piccola** (+0,05) contro il **+0,18**
dello sleeve ufficiale; null di permutazione **a fee zero** p=0,013 (cross-sez.) e 0,017 (pesi
casuali); sopravvive a **30 bps/lato** (+1,01). **`deflated_sharpe` FAIL 0,342** — e l'agente dichiara
che il DSR penalizza una **mediana-di-fase** come se fosse un **max-di-k**, *"ma un gate fallito resta
fallito"*. Null del de-levering **non pertinente e dichiarato**. Haircut a $600: **non e' il vincolo**
(ticket mediano $76, **0,0%** sotto il min-order).
🚨 **Null "universo dove NON dovrebbe funzionare"** (11 settoriali SPDR, 1998+, meccanismo congelato):
**0,45, 0/10 fasi positive** → **terza conferma indipendente che il cross-sectional e'
crypto-specifico** (dopo XSR01-equity 26/07 e STATARB-EQ 25/07).
🚨 **Il risultato che il progetto non si aspettava: i DUE AFFINAMENTI del 2026-06-19 NON si
replicano.** Fuori campione il **blend [30,90] e' PEGGIO del solo L=30** (+1,12 vs +1,28) e il **gate
di dispersione p30 vale ZERO** (+1,12 con e senza) — mentre *dentro* la finestra di scoperta il gate
vale eccome (+0,37 contro 0,03). **Cio' che sopravvive e' il meccanismo cross-sectional NUDO.**
⚠️ Non e' un argomento per cambiarli — cambiarli guardando *questo* risultato sarebbe la stessa
selezione spostata di finestra — **e' un argomento per non attribuire loro il valore che la memoria
del progetto gli attribuisce.**
⚠️ **Concentrazione misurata prima di crederci** (lezione SOL, stesso giorno): togliendo il **2021** il
fuori campione scende **+1,17 → +0,57**; **leave-one-asset-out**: togliendo **LTC** la mediana di fase
crolla **+1,12 → +0,20** (tutte e 13 restano >0) → **ampiezza effettiva bassa**, stessa diagnosi di
STATARB-MULTI.
**VERDETTO: LEAD.** Non promuove niente; **riscrive il gate del PROP-ALLOC.**
📌 **`IL GATE DEL PROP-ALLOC SI PUO' CHIUDERE OGGI: PARZIALMENTE` — e non si chiude, si SOSTITUISCE.**
L'attesa di anni **non serve**. Ma tre cose che il gate non chiedeva sono ora misurate:
- **il rendimento sopravvive, il RISCHIO no**: maxDD standalone **10,8% → 20,9-22,0%** — e su un conto
funded il vincolo binding e' la **regola di DD**, quindi la correzione va nel verso sbagliato;
- **il peso 0,50 dell'argmax-J non sopravvive**: fuori campione a w=0,50 il maxDD di libro risale a
**8,9%** contro **6,0%** a w=0,25, cioe' restituisce quasi tutta la protezione che e' l'unica ragione
per metterlo su un conto a barriera → l'ottimo fuori campione e' **w ≈ 0,25-0,33**;
- **32% dell'universo non e' testabile** (ARB/OP/SUI/APT/SEI/TIA non quotati nel 2021-23) e su di esso
vive gran parte del numero di scoperta (**19 gambe 1,31 vs 13 gambe 0,54**, stessa finestra e stesso
venue) → *cio' che e' stato validato e' una versione piu' DEBOLE di quella che sta nel libro.*
⚠️ **Il punto fragile che l'agente dichiara e non ha testato:** la validazione di venue gira **solo sul
2024+**, cioe' proprio il periodo **senza depeg USDT**, mentre il fuori campione contiene il **depeg di
maggio 2022**. Se i close Binance/USDT 2021-23 divergessero da un venue **USD** in modo *non comune fra
gli asset*, il ranking cambierebbe e il risultato con esso.
+32 -9
View File
@@ -370,7 +370,7 @@ def test_calendario(q: pd.DataFrame, dte_min: float, ogni: int = 4) -> dict:
amp_pt = np.where(ok, iv_need - nxt_iv, np.nan)
room = amp_pt[ok] # <0 = margine, >0 = violazione
res = {"room_med_pt": float(np.nanmedian(room)), "room_p5_pt": float(np.nanpercentile(room, 5)),
res = {"room_med_pt": float(np.nanmedian(room)), "room_p5_pt": float(np.nanpercentile(room, 95)),
"i_n": int(ok.sum()), "i_viol": int(viol_i.sum()),
"i_quota": float(viol_i.sum() / max(1, ok.sum())),
"i_amp_med_pt": float(np.nanmedian(amp_pt[viol_i])) if viol_i.any() else np.nan,
@@ -473,7 +473,8 @@ def persistenza(bf: pd.DataFrame, viol_col: str) -> dict:
return {"n": int(m.sum()), "resta": float(nxt[m].mean()) if m.any() else np.nan}
def controfattuale_usdc(bf: pd.DataFrame, asset: str, rel_usdc: float) -> dict:
def controfattuale_usdc(bf: pd.DataFrame, asset: str, rel_usdc: float,
solo_gia_strette: bool = False) -> dict:
"""SE la famiglia USDC quotasse la STESSA superficie col SUO spread, sarebbe eseguibile?
Controfattuale DICHIARATO, non una misura: il collettore non raccoglie le USDC (difetto di
@@ -484,6 +485,14 @@ def controfattuale_usdc(bf: pd.DataFrame, asset: str, rel_usdc: float) -> dict:
"""
x = bf[(bf["asset"] == asset)].dropna(subset=["mid", "mid_p", "mid_n"]).copy()
x = x[np.isclose(x["lam"], 0.5, atol=0.02)]
if solo_gia_strette:
# il controfattuale ingenuo prende il MID di un mercato larghissimo e finge di poterlo
# quotare al 2,6%: e' proprio dove il mid e' meno informativo. Qui si tiene solo la parte
# di superficie GIA' quotata stretta quanto la famiglia USDC, dove il mid vale qualcosa.
rs = np.maximum.reduce([((x["ask"] - x["bid"]) / x["mid"]).to_numpy(),
((x["ask_p"] - x["bid_p"]) / x["mid_p"]).to_numpy(),
((x["ask_n"] - x["bid_n"]) / x["mid_n"]).to_numpy()])
x = x[rs <= rel_usdc]
lot = MIN_LOT_USDC[asset]
out = {}
for nome, rel in (("inverse osservato", None), (f"{asset}_USDC controfattuale", rel_usdc)):
@@ -499,8 +508,10 @@ def controfattuale_usdc(bf: pd.DataFrame, asset: str, rel_usdc: float) -> dict:
f = (fee_coin(b.to_numpy(), 2 * l) + fee_coin(a_p.to_numpy(), l) + fee_coin(a_n.to_numpy(), l))
netto = (cred - f) * x["spot_feed"].to_numpy()
out[nome] = {"n": int(len(x)), "pos": int(np.nansum(netto > 0)),
"max": float(np.nanmax(netto)), "p999": float(np.nanpercentile(netto, 99.9)),
"lotto_usd": float(np.nanmedian(x["spot_feed"]) * l)}
"pos1": int(np.nansum(netto > 1.0)), "somma": float(np.nansum(netto[netto > 0])),
"max": float(np.nanmax(netto)) if len(x) else np.nan,
"p999": float(np.nanpercentile(netto, 99.9)) if len(x) else np.nan,
"lotto_usd": float(np.nanmedian(x["spot_feed"]) * l) if len(x) else np.nan}
return out
@@ -694,7 +705,7 @@ def main() -> None:
print(f" valore mediano della violazione (i) al lotto minimo ${c['i_val_usd_med']:.2f} "
f"contro un costo di 2 gambe (spread + fee) di ${c['i_costo_usd_med']:.2f}")
print(f" margine di monotonia sull'intera superficie: la iv lunga sta {-c['room_med_pt']:.2f} pt-vol "
f"SOPRA il minimo che la non-arbitraggio richiede (p5 {-c['room_p5_pt']:.2f}) "
f"SOPRA il minimo che la non-arbitraggio richiede (5% piu' stretto: {-c['room_p5_pt']:.2f}) "
f"-> la superficie non e' nemmeno VICINA al bordo")
print("\n" + "=" * 100)
@@ -717,10 +728,22 @@ def main() -> None:
for a, rel in (("BTC", 0.082), ("ETH", 0.026)):
if a not in set(bf["asset"]):
continue
cf = controfattuale_usdc(bf, a, rel)
for nome, r in cf.items():
print(f" {a} {nome:<28}: lotto ${r['lotto_usd']:,.0f} · farfalle a credito netto>0 "
f"{r['pos']:,}/{r['n']:,} · migliore ${r['max']:.2f} · p99.9 ${r['p999']:.2f}")
for strette, et in ((False, "tutta la superficie"), (True, "solo quote gia' strette")):
cf = controfattuale_usdc(bf, a, rel, solo_gia_strette=strette)
for nome, r in cf.items():
if strette and "inverse" in nome:
continue
print(f" {a} {nome:<26} [{et:<24}]: lotto ${r['lotto_usd']:,.0f} · "
f"credito netto>0 {r['pos']:,}/{r['n']:,} · >$1 {r['pos1']:,} · "
f"migliore ${r['max']:.2f} · somma ${r['somma']:,.0f}")
# dove vive l'incoerenza: e' larga dove il mercato e' largo?
x = bf[(bf["asset"] == a)].dropna(subset=["iv_resid", "relspread"])
x = x[np.isfinite(x["relspread"])]
dec = pd.qcut(x["relspread"], 5, labels=False, duplicates="drop")
t = x.assign(d=dec).groupby("d").agg(spread=("relspread", "median"),
resid=("iv_resid", lambda v: v.abs().median()))
print(f" |residuo| per quintile di spread relativo {a}: " +
" · ".join(f"{r.spread:.1%}->{r.resid:.3f}pt" for r in t.itertuples()))
print("\n QUANTO MANCA per essere eseguibile (near-miss, triple equispaziate):")
for a in sorted(bf["asset"].unique()):