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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user