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

This commit is contained in:
Adriano Dal Pastro
2026-08-22 17:29:19 +00:00
parent f0122d97a1
commit ea22a0e20c
9 changed files with 1445 additions and 93 deletions
+73 -10
View File
@@ -54,6 +54,7 @@ FEE_LEG_MODELLO = 0.0005 # [D] 0.05%/gamba, config CONGELATA di XSR01
XSR_SOGLIA_HAIRCUT = 0.40 # [D] soglia pre-registrata del gate 2026-10-23
CAPITALI = (600.0, 1000.0, 1500.0, 2000.0, 3000.0, 4000.0, 5000.0, 7500.0,
10000.0, 20000.0, 50000.0)
CAPITALI_XS = (100.0, 200.0, 300.0, 600.0, 1000.0, 2000.0, 3000.0, 5000.0, 10000.0)
TICKETS = (50.0, 100.0, 300.0)
ANN = np.sqrt(365.0)
@@ -257,6 +258,7 @@ def sim_libro(Weff: np.ndarray, R: np.ndarray, r_hedge: np.ndarray | None, cap0:
eq = np.empty(n)
nets = np.zeros(n)
n_fill = n_skip = n_lot0 = 0
ntl_req = ntl_done = 0.0
fee = np.broadcast_to(np.asarray(fee_leg, float).ravel(), (k,)) if np.ndim(fee_leg) else None
for i in range(n):
ret = float(np.dot(w, R[i]))
@@ -275,6 +277,8 @@ def sim_libro(Weff: np.ndarray, R: np.ndarray, r_hedge: np.ndarray | None, cap0:
n_lot0 += int(zero.sum())
move = move & (lots >= 1)
need = np.abs(w_t - w) > 1e-12 # una gamba gia' a posto NON e' un ordine saltato
ntl_req += float((np.abs(w_t - w) * cap).sum())
ntl_done += float((np.abs(w_t - w) * cap)[move].sum())
w_new = np.where(move, w_t, w)
n_fill += int((move & need).sum())
n_skip += int(((~move) & need).sum())
@@ -290,7 +294,8 @@ def sim_libro(Weff: np.ndarray, R: np.ndarray, r_hedge: np.ndarray | None, cap0:
w = w_new
tot = n_fill + n_skip
return dict(net=nets, eq=eq, n_fill=n_fill, n_skip=n_skip, n_lot0=n_lot0,
pct_eseguite=(100.0 * n_fill / tot) if tot else np.nan)
pct_eseguite=(100.0 * n_fill / tot) if tot else np.nan,
pct_nozionale=(100.0 * ntl_done / ntl_req) if ntl_req > 0 else np.nan)
def metriche(net: np.ndarray, idx: pd.DatetimeIndex) -> dict:
@@ -393,6 +398,19 @@ def main() -> None:
print(f" deviazione |%| mediana {ck['dev_pct'].abs().median():.2f}% "
f"max {ck['dev_pct'].abs().max():.2f}% ({ck['dev_pct'].abs().idxmax()}) "
f"asset controllati {len(ck)}")
mv = [float(np.abs(pd.read_parquet(RAW / f"hl_{sy.lower()}_1d.parquet", columns=["close"])
["close"].pct_change().dropna()).median()) * 100 for sy in ck.index]
print(f" movimento |giornaliero| tipico di questi asset: {float(np.median(mv)):.2f}% -> "
f"una deviazione mediana del {ck['dev_pct'].abs().median():.2f}% su un feed vecchio "
f"{ck['eta_h'].median():.0f}h e' cio' che ci si attende (non e' un disaccordo di venue).")
try:
from src.data.downloader import load_data as _ld
bd = float(_ld("BTC", "1h")["close"].iloc[-1])
hl = float(meta.loc["BTC", "markPx"])
print(f" cross-check su un TERZO venue: BTC Deribit certificato ${bd:,.0f} vs "
f"HL API ${hl:,.0f} = {100*(hl-bd)/bd:+.2f}%")
except Exception as e:
print(f" (cross-check Deribit non disponibile: {type(e).__name__})")
print(" -> un feed TESTNET non supera questo controllo: i suoi prezzi sono scollegati.")
tk = fee_sched
@@ -497,8 +515,7 @@ def main() -> None:
f"{'>=$5 [D]':>10}{'>=$10 [C]':>11}{'reb>=$10':>10}")
R_xs = dret
idx = C.index
base = None
for cap in CAPITALI:
for cap in CAPITALI_XS:
pct5 = 100.0 * float((tk_nz * cap >= MIN_ORDER_ASSUNTO).mean())
pct10 = 100.0 * float((tk_nz * cap >= MIN_ORDER_HL).mean())
pr10 = 100.0 * float((reb * cap >= MIN_ORDER_HL).mean())
@@ -513,15 +530,16 @@ def main() -> None:
print(f" (`turn=|dW|` prima dello scaling): e' il motivo per cui sta sotto il "
f"{m_off['sharpe']:.2f} pubblicato.")
print(f" {'cap XS01':>10}{'Sh mod':>9}{'Sh $5 [D]':>11}{'Sh $10 [C]':>12}"
f"{'haircut $10':>13}{'ordini fatti':>14}")
for cap in CAPITALI:
f"{'haircut $10':>13}{'eseguito':>21}")
for cap in CAPITALI_XS:
m0 = sim_libro(Wxs, R_xs, None, cap, None, None, None, fee_xs_mod)
m5 = sim_libro(Wxs, R_xs, None, cap, MIN_ORDER_ASSUNTO, None, None, fee_xs_mod)
m10 = sim_libro(Wxs, R_xs, None, cap, MIN_ORDER_HL, lot_xs, px_xs, fee_xs_mod)
a0, a5, a10 = (metriche(m["net"], idx) for m in (m0, m5, m10))
hc = (a0["sharpe"] - a10["sharpe"]) / abs(a0["sharpe"]) * 100 if a0["sharpe"] else np.nan
print(f" {cap:>10,.0f}{a0['sharpe']:>9.2f}{a5['sharpe']:>11.2f}{a10['sharpe']:>12.2f}"
f"{hc:>12.0f}%{m10['pct_eseguite']:>9.0f}%")
f"{hc:>12.0f}%{m10['pct_eseguite']:>8.0f}% ord "
f"{m10['pct_nozionale']:>4.0f}% ntl")
# ---------------------------------------------------------------- 4. XSR01
print(f"\n[4] XSR01 — soglia PUBBLICATA ~$5.000, gate 2026-10-23 con haircut a $5.000 <= 40%")
@@ -555,7 +573,7 @@ def main() -> None:
print(f" gambe {len(sx)} lordo mediano {np.median(np.abs(Wx).sum(axis=1)):.2f}x "
f"peso mosso mediano/gamba {np.median(tkx)*100:.3f}% p10 {np.percentile(tkx,10)*100:.3f}%")
print(f"\n {'cap':>9}{'ticket med':>12}{'Sh mod':>9}{'Sh $5 [D]':>11}{'Sh $10 [C]':>12}"
f"{'haircut $5':>12}{'haircut $10':>13}{'ordini fatti':>14}")
f"{'haircut $5':>12}{'haircut $10':>13}{'eseguito':>21}")
hc10_5000 = np.nan
for cap in CAPITALI:
m0 = sim_libro(Wx, Rx, rbx, cap, None, None, None, FEE_LEG_MODELLO)
@@ -568,9 +586,22 @@ def main() -> None:
hc10_5000 = h10
print(f" {cap:>9,.0f}{np.median(tkx)*cap:>12.2f}{a0['sharpe']:>9.2f}"
f"{a5['sharpe']:>11.2f}{a10['sharpe']:>12.2f}{h5:>11.0f}%{h10:>12.0f}%"
f"{m10['pct_eseguite']:>9.0f}%")
f"{m10['pct_eseguite']:>8.0f}% ord {m10['pct_nozionale']:>4.0f}% ntl")
print(f" -> haircut a $5.000 col pavimento VERO ($10): {hc10_5000:.0f}% "
f"(soglia pre-registrata {XSR_SOGLIA_HAIRCUT*100:.0f}%)")
print(f"\n la colonna haircut NON e' monotona (+35% a $2k, -6% a $4k): a un capitale solo e'")
print(f" una statistica RUMOROSA. Banda: haircut ricalcolato a pavimento $8/$10/$12 ->")
print(f" {'cap':>9}{'h($8)':>9}{'h($10)':>9}{'h($12)':>9}{'ampiezza':>11} giudizio")
for cap in CAPITALI:
s0 = metriche(sim_libro(Wx, Rx, rbx, cap, None, None, None, FEE_LEG_MODELLO)["net"], idxx)["sharpe"]
hs = []
for mo in (8.0, 10.0, 12.0):
sh = metriche(sim_libro(Wx, Rx, rbx, cap, mo, lot_x, px_x, FEE_LEG_MODELLO)["net"], idxx)["sharpe"]
hs.append((s0 - sh) / abs(s0) * 100)
amp = max(hs) - min(hs)
g = ("ROTTO" if min(hs) > XSR_SOGLIA_HAIRCUT * 100 else
"ok" if max(hs) < XSR_SOGLIA_HAIRCUT * 100 else "INDECIDIBILE")
print(f" {cap:>9,.0f}{hs[0]:>8.0f}%{hs[1]:>8.0f}%{hs[2]:>8.0f}%{amp:>10.0f}pp {g}")
print(f" NB: e' la lettura del PARAMETRO sul backtest, NON il gate del 23/10 — "
f"quello si decide sulla finestra FORWARD e anticiparlo sarebbe selezione.")
@@ -596,9 +627,41 @@ def main() -> None:
print(f" {nm:<44}{a_['sharpe']:>8.2f}{a_['maxdd']:>7.1f}%{a_['cagr']:>7.1f}%")
print(f" (a capitale infinito = nessun vincolo di min-order: isola il COSTO dal PAVIMENTO)")
print(f"\n [B] SENSIBILITA' AL PAVIMENTO a $5.000 (l'haircut a un punto solo e' rumore:")
print(f" qui si vede DOVE il parametro comincia a mordere)")
print(f" {'pavimento $':>13}{'Sharpe':>9}{'haircut':>10}{'ord fatti':>11}{'ntl mosso':>11}")
sh0 = metriche(sim_libro(Wx, Rx, rbx, 5000.0, None, None, None, FEE_LEG_MODELLO)["net"], idxx)["sharpe"]
for mo in (0.0, 5.0, 10.0, 15.0, 20.0, 30.0, 50.0):
m = sim_libro(Wx, Rx, rbx, 5000.0, mo if mo > 0 else None, None, None, FEE_LEG_MODELLO)
aa = metriche(m["net"], idxx)
print(f" {mo:>13.0f}{aa['sharpe']:>9.2f}"
f"{(sh0-aa['sharpe'])/abs(sh0)*100:>9.0f}%"
f"{m['pct_eseguite'] if np.isfinite(m['pct_eseguite']) else 100:>10.0f}%"
f"{m['pct_nozionale'] if np.isfinite(m['pct_nozionale']) else 100:>10.0f}%")
print(f"\n [B] BREAK-EVEN DEL COSTO — quanto dovrebbe valere lo slippage perche' il "
f"'rischio #1' fosse reale:")
print(f" {'costo/gamba':>13}{'= taker + slip':>18}{'Sharpe':>9}{'CAGR':>8}")
be = None
for bps in (4.5, 5.0, 7.5, 10.0, 15.0, 20.0, 30.0, 40.0):
aa = metriche(sim_libro(Wx, Rx, rbx, 5000.0, None, None, None, bps / 1e4)["net"], idxx)
if be is None and aa["sharpe"] < 1.0:
be = bps
print(f" {bps:>11.1f} bps{max(bps-4.5,0):>13.1f} slip{aa['sharpe']:>9.2f}"
f"{aa['cagr']:>7.1f}%")
slip_mis = float(np.median(fee_real) - taker) * 1e4
print(f" -> Sharpe scende sotto 1.0 a ~{be:.0f} bps/gamba = ~{be-4.5:.0f} bps di slippage,")
print(f" cioe' **{(be-4.5)/max(slip_mis,1e-9):.0f}x** il mezzo spread misurato oggi "
f"({slip_mis:.1f} bps).")
print("\n" + "=" * 104)
print(" LIMITI DICHIARATI: gli spread sono UNO/POCHI snapshot di OGGI su un venue la cui")
print(" liquidita' e' cresciuta -> sono la stima piu' FAVOREVOLE per i 2.6 anni di backtest.")
tt = sorted(b["time"] for v in books.values() for b in v)
print(f" LIMITI DICHIARATI (1) i {a.snaps} snapshot coprono {(tt[-1]-tt[0])/1000:.0f} SECONDI:")
print(" NON sono osservazioni indipendenti, sono UNA fotografia intraday di oggi. E la")
print(" liquidita' di HL e' cresciuta -> e' la stima piu' FAVOREVOLE per i 2.6 anni di")
print(" backtest. Cio' che regge al limite e' il MARGINE (21x), non il livello.")
print(" (2) il lotto in $ usa il mark di OGGI su 2.6 anni di storia: irrilevante perche' il")
print(" lotto non e' mai vincolante (max $1.69 contro $10 di pavimento), ma va detto.")
print(" Il pavimento $10 e' [C] (doc + messaggio d'errore), non provato da un ordine: provarlo")
print(" richiederebbe di mandarne uno, e questo audit non tocca il conto.")
print("=" * 104)