432 lines
22 KiB
Python
432 lines
22 KiB
Python
"""XSR-REPRO — lo Sharpe 1.82 di XSR01 non si riproduce: e' un problema di CODICE, di DATI, o di LENTE?
|
|
|
|
PERCHE'. XSR01 ha un gate di deploy PRE-REGISTRATO al 2026-10-23 (`r0725_xsr_deploy_gate.py`):
|
|
deploy solo se Sharpe forward >= 1.0 E haircut di eseguibilita' a $5.000 <= 40%. I suoi numeri di
|
|
ammissione — Sharpe netta 1.82, lorda 2.70, maxDD -2.6%, vol 2.3%, ret 4.2%/a, lag 1.82/1.19/0.81/
|
|
0.51, Sharpe per anno 1.03/1.98/3.11 — sono in CLAUDE.md, nel diario del 25/07 e CABLATI nel gate
|
|
(`IS_SHARPE_NET = 1.82`). Il filone XS-LITE (22/08) ha misurato che sulla finestra identica a quella
|
|
di scoperta oggi escono 1.75 (lente "paniere") e 2.23 (lente "libro"): 1.82 non e' nessuna delle due.
|
|
Una decisione fra due mesi poggia su un numero che non si riproduce.
|
|
|
|
Questo script NON giudica XSR01 e NON anticipa il gate del 23/10 (sarebbe selection-on-forward).
|
|
Stabilisce SOLO se il numero su cui si decidera' e' solido, in quest'ordine:
|
|
|
|
T1 QUALE LENTE. Tre implementazioni convivono nel repo e danno numeri diversi sugli STESSI dati:
|
|
L1 `r0725_statarb_basket_gate.basket_from_positions(demean=True)` — media per riga sulle sole
|
|
colonne NON-NaN (divisore variabile). E' la lente che ha girato i gate (marginale, DSR).
|
|
L2 `r0725_statarb_demean_skeptic.ret_from_pos` — P.fillna(0) e divisore FISSO 50.
|
|
L3 `scripts/live/paper_xsr.build_panel` + `_step` — libro con capitale, pesi Q/50, gamba BTC
|
|
esplicita. E' quella che alimenta il gate.
|
|
T2 QUALE STATO DEL DATO. `data/raw` e' gitignored e il cron riscrive i parquet HL ogni notte.
|
|
Ipotesi da falsificare: "stesso codice, dati diversi" (identico allo scoperto GTAA/TLT 07/08).
|
|
Prove indipendenti: (a) il log del cron registra `reali`/`bfill`/`start_reale` per simbolo a
|
|
ogni giro -> se il conteggio delle barre PASSATE e' cambiato, il feed e' stato riscritto;
|
|
(b) lo Sharpe per ANNO CHIUSO (2024, 2025) e' un'impronta della storia: se la storia e'
|
|
stabile deve riprodursi al centesimo attraverso 4 settimane di riscritture.
|
|
T3 IL MONITOR FORWARD. `paper_xsr` e' forward-only e append-only, quindi in teoria immune alla
|
|
riscrittura. Test diretto: replay del libro MODELED sui dati di OGGI dall'inception, barra per
|
|
barra, contro cio' che il monitor ha REGISTRATO giorno per giorno. Se la storia fosse stabile
|
|
e la lente giusta, le due serie devono coincidere.
|
|
T4 L'HAIRCUT A $5.000, l'altra meta' del gate: dipende dal min-order del venue. Il numero
|
|
pubblicato ($14.41 di ticket, soglia 40%) assume min-order $5 (Deribit). Hyperliquid potrebbe
|
|
essere $10. Si misura la sensibilita'.
|
|
|
|
COSA MI ASPETTAVO PRIMA DI MISURARE: che fosse la lente (tre implementazioni, nessuna nominata nel
|
|
diario) e che il dato fosse stabile, perche' il log del cron mostra bar-count che crescono di 1/g.
|
|
Solo la prima meta' e' risultata vera.
|
|
|
|
nice -n 19 timeout 900 uv run python scripts/research/r0822_xsr_repro.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import sys
|
|
from collections import OrderedDict
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
RAW = ROOT / "data" / "raw"
|
|
for _p in (ROOT, ROOT / "scripts" / "research", ROOT / "scripts" / "research" / "alt"):
|
|
sys.path.insert(0, str(_p))
|
|
|
|
from r0725_statarb_multi import BASE, MIN_BARS, _dd, _sh, load_hl, signal, universe # noqa: E402
|
|
from r0725_statarb_basket_gate import basket_from_positions, pair_frames # noqa: E402
|
|
from r0725_statarb_demean_skeptic import ret_from_pos # noqa: E402
|
|
|
|
ANN = np.sqrt(365.0)
|
|
DISCOVERY = pd.Timestamp("2026-07-25", tz="UTC") # ultima barra vista dalla scoperta
|
|
CRONLOG = ROOT / "logs" / "cron_daily.log"
|
|
FWD = ROOT / "data" / "paper_xsr" / "returns.jsonl"
|
|
FWD_STATE = ROOT / "data" / "paper_xsr" / "state.json"
|
|
|
|
# I NUMERI PUBBLICATI (CLAUDE.md + diario 2026-07-25 + docstring di paper_xsr / del gate)
|
|
PUB = dict(netta=1.82, lorda=2.70, dd=-2.6, vol=2.3, ret=4.2,
|
|
lag=(1.82, 1.19, 0.81, 0.51), anni=(1.03, 1.98, 3.11))
|
|
|
|
|
|
def _cut(df, ts):
|
|
return df[df.index <= ts]
|
|
|
|
|
|
def _stats(r: pd.Series) -> dict:
|
|
return dict(n=len(r), sh=_sh(r.values), dd=_dd(r.values) * 100,
|
|
vol=float(r.std() * ANN * 100), ret=float(r.mean() * 365 * 100))
|
|
|
|
|
|
# ------------------------------------------------------------------ pannelli
|
|
def panel(partial_last: bool = False, cut: pd.Timestamp | None = None):
|
|
"""(P, S) come `pair_frames`, ma con due gradi di liberta' espliciti:
|
|
`cut` -> ultima barra inclusa (troncamento dell'INPUT, non dell'output);
|
|
`partial_last` -> l'ultima barra e' resa PARZIALE (close := open), cioe' com'era il giorno in
|
|
cui il cron l'aveva appena scritta. E' l'unico stato del dato che questo
|
|
script NON puo' leggere da disco: va ricostruito."""
|
|
def px(sym):
|
|
d = pd.read_parquet(RAW / f"hl_{sym.lower()}_1d.parquet")
|
|
ix = pd.to_datetime(d["timestamp"], unit="ms", utc=True)
|
|
c = pd.Series(d["close"].astype(float).values, index=ix).sort_index()
|
|
o = pd.Series(d["open"].astype(float).values, index=ix).sort_index()
|
|
c = c[~c.index.duplicated(keep="last")]
|
|
o = o[~o.index.duplicated(keep="last")]
|
|
if cut is not None:
|
|
c, o = c[c.index <= cut], o[o.index <= cut]
|
|
if partial_last and len(c):
|
|
c.iloc[-1] = o.iloc[-1]
|
|
return c
|
|
|
|
base = px(BASE)
|
|
pc, sc = {}, {}
|
|
for sym in universe():
|
|
try:
|
|
tgt = px(sym)
|
|
except FileNotFoundError:
|
|
continue
|
|
ix = base.index.intersection(tgt.index)
|
|
if len(ix) < MIN_BARS:
|
|
continue
|
|
p, s = signal(base[ix], tgt[ix])
|
|
pc[sym] = pd.Series(p, index=ix)
|
|
sc[sym] = pd.Series(s, index=ix)
|
|
P = pd.concat(pc, axis=1, sort=True).sort_index()
|
|
S = pd.concat(sc, axis=1, sort=True).sort_index()
|
|
return P, S
|
|
|
|
|
|
def lens_L1(P, S):
|
|
return basket_from_positions(P, S, demean=True)
|
|
|
|
|
|
def lens_L2(P, S, fee_leg=0.0005, lag=0):
|
|
P0 = P.fillna(0.0)
|
|
S0 = S.reindex(P0.index)[P0.columns]
|
|
return ret_from_pos(P0, S0, fee_leg=fee_leg, lag=lag)
|
|
|
|
|
|
# ------------------------------------------------------------------ T1
|
|
def t1_lenti():
|
|
print("=" * 100)
|
|
print(" T1 — QUALE LENTE, E QUALE STATO DEL DATO, PRODUCE 1.82")
|
|
print("=" * 100)
|
|
P, S = pair_frames()
|
|
print(f"\n dato su disco oggi: {P.shape[1]} coppie, {len(P)} barre "
|
|
f"({P.index[0].date()} -> {P.index[-1].date()})")
|
|
|
|
rows = []
|
|
for tag, (Pi, Si) in [
|
|
("OGGI pieno", (P, S)),
|
|
(f"tronc. {DISCOVERY.date()} (barra completa)", (_cut(P, DISCOVERY), _cut(S, DISCOVERY))),
|
|
]:
|
|
rows.append((f"L1 gate {tag}", _stats(lens_L1(Pi, Si))))
|
|
rows.append((f"L2 scett. {tag}", _stats(lens_L2(Pi, Si))))
|
|
|
|
Pp, Sp = panel(partial_last=True, cut=DISCOVERY)
|
|
rows.append(("L1 gate STATO DEL 25/07 (ultima barra PARZIALE)", _stats(lens_L1(Pp, Sp))))
|
|
rows.append(("L2 scett. STATO DEL 25/07 (ultima barra PARZIALE)", _stats(lens_L2(Pp, Sp))))
|
|
|
|
print(f"\n {'lente / stato del dato':<52}{'n':>6}{'Sharpe':>9}{'maxDD':>9}{'vol':>8}{'ret/a':>8}")
|
|
for nm, s in rows:
|
|
print(f" {nm:<52}{s['n']:>6}{s['sh']:>9.4f}{s['dd']:>8.2f}%{s['vol']:>7.2f}%{s['ret']:>7.2f}%")
|
|
print(f" {'--- PUBBLICATO 2026-07-25':<52}{'937?':>6}{PUB['netta']:>9.2f}"
|
|
f"{PUB['dd']:>8.1f}%{PUB['vol']:>7.1f}%{PUB['ret']:>7.1f}%")
|
|
|
|
# confronto completo sugli OTTO numeri pubblicati, per la sola combinazione che li riproduce
|
|
print("\n --- verifica completa sullo stato che riproduce: L2 + ultima barra PARZIALE ---")
|
|
r = lens_L2(Pp, Sp)
|
|
g = lens_L2(Pp, Sp, fee_leg=0.0)
|
|
lags = tuple(_sh(lens_L2(Pp, Sp, lag=L).values) for L in (0, 1, 2, 3))
|
|
anni = tuple(_sh(r[r.index.year == y].values) for y in (2024, 2025, 2026))
|
|
st = _stats(r)
|
|
ok = 0
|
|
def chk(nome, mio, pub, tol):
|
|
nonlocal ok
|
|
good = abs(mio - pub) <= tol
|
|
ok += good
|
|
print(f" {nome:<22} misurato {mio:>8.4f} pubblicato {pub:>7.2f} "
|
|
f"{'COINCIDE' if good else 'DIVERGE'}")
|
|
chk("Sharpe netta", st["sh"], PUB["netta"], 0.005)
|
|
chk("Sharpe lorda", _sh(g.values), PUB["lorda"], 0.005)
|
|
chk("maxDD %", st["dd"], PUB["dd"], 0.05)
|
|
chk("vol ann %", st["vol"], PUB["vol"], 0.05)
|
|
chk("ret ann %", st["ret"], PUB["ret"], 0.05)
|
|
for i, L in enumerate((0, 1, 2, 3)):
|
|
chk(f"lag +{L}g", lags[i], PUB["lag"][i], 0.006)
|
|
for i, y in enumerate((2024, 2025, 2026)):
|
|
chk(f"Sharpe {y}", anni[i], PUB["anni"][i], 0.005)
|
|
print(f"\n -> {ok}/12 numeri pubblicati riprodotti da L2 sullo stato del dato del 25/07.")
|
|
|
|
# la lente che ha girato i GATE non e' quella del titolo
|
|
print("\n --- e la lente che ha girato i gate (marginale, deflated-Sharpe) e' L1, non L2 ---")
|
|
print(f" L1 sullo stato del 25/07 : Sharpe {_sh(lens_L1(Pp, Sp).values):.4f}")
|
|
print(" docstring di r0725_statarb_demean_skeptic.py, riga 5, cita il gate come "
|
|
"\"Sharpe 1.79\"")
|
|
print(" -> il numero di TITOLO (1.82, L2) e i numeri di GATE (DSR/marginale, L1) non sono")
|
|
print(" mai stati la stessa serie.")
|
|
|
|
# deflated-Sharpe delle 3 varianti pre-registrate, oggi, sulle due lenti
|
|
try:
|
|
from altlib import deflated_sharpe
|
|
from src.portfolio.sleeves import XS_UNIVERSE
|
|
maj = [s for s in XS_UNIVERSE if s != BASE]
|
|
Pc, Sc = _cut(P, DISCOVERY), _cut(S, DISCOVERY)
|
|
var = {"V1 ALL50": dict(cols=None, demean=False),
|
|
"V2 MAJ19": dict(cols=maj, demean=False),
|
|
"V3 DEMEAN": dict(cols=None, demean=True)}
|
|
ser = {k: basket_from_positions(Pc, Sc, **v) for k, v in var.items()}
|
|
srs = [_sh(v.values) for v in ser.values()]
|
|
print("\n --- deflated-Sharpe delle 3 varianti pre-registrate, L1, finestra 25/07 ---")
|
|
for k, v in ser.items():
|
|
d, nm = deflated_sharpe(_sh(v.values), srs, v.values, dpy=365.0)
|
|
print(f" {k:<11} Sharpe {_sh(v.values):>5.2f} DSR {d:>6.3f} "
|
|
f"(max atteso dal null {nm:>5.2f}) {'PASS' if d >= 0.95 else 'SOTTO 0.95'}")
|
|
print(" (pubblicato per V3: DSR 0.985)")
|
|
except Exception as e: # pragma: no cover
|
|
print(f" [DSR non calcolabile: {e.__class__.__name__}: {e}]")
|
|
return P, S
|
|
|
|
|
|
# ------------------------------------------------------------------ T2
|
|
def t2_feed(P, S):
|
|
print("\n" + "=" * 100)
|
|
print(" T2 — IL FEED HL E' STATO RISCRITTO? (l'ipotesi principale: stesso codice, dati diversi)")
|
|
print("=" * 100)
|
|
|
|
# (a) il log del cron registra reali/bfill/start_reale a ogni giro: e' un registro storico
|
|
runs, cur, inblk, hdr = OrderedDict(), None, False, None
|
|
for ln in CRONLOG.read_text(errors="replace").splitlines():
|
|
m = re.match(r"^===== (\S+) cron_daily =====", ln)
|
|
if m:
|
|
cur, inblk = m.group(1), False
|
|
runs[cur] = {}
|
|
continue
|
|
if "FETCH + CERTIFY Hyperliquid" in ln:
|
|
inblk, hdr = True, None
|
|
continue
|
|
if inblk and cur is not None:
|
|
if ln.strip().startswith("sym"):
|
|
hdr = ln.split()
|
|
continue
|
|
f = ln.split()
|
|
if hdr and len(f) >= 7 and re.match(r"^[A-Z0-9]+$", f[0]) and f[1].isdigit():
|
|
runs[cur][f[0]] = ((int(f[1]), f[3], f[-1]) if len(hdr) >= 9
|
|
else (int(f[1]), f[2], f[-1]))
|
|
elif runs[cur] and (not ln.strip() or ln.startswith("=")):
|
|
inblk = False
|
|
keys = [k for k, v in runs.items() if v]
|
|
a = next((k for k in keys if k.startswith("2026-07-25")), None)
|
|
b = keys[-1] if keys else None
|
|
print(f"\n (a) registro del cron: {len(keys)} giri con blocco Hyperliquid, "
|
|
f"da {keys[0][:10]} a {keys[-1][:10]}")
|
|
if a and b:
|
|
A, B = runs[a], runs[b]
|
|
atteso = (pd.Timestamp(b[:10]) - pd.Timestamp(a[:10])).days
|
|
anom = []
|
|
for s in sorted(set(A) | set(B)):
|
|
ra, rb = A.get(s), B.get(s)
|
|
if ra is None or rb is None:
|
|
anom.append((s, "presente in un solo giro", ra, rb)); continue
|
|
if rb[0] - ra[0] != atteso or ra[1] != rb[1]:
|
|
anom.append((s, f"delta {rb[0]-ra[0]} invece di {atteso}; start {ra[1]}->{rb[1]}",
|
|
ra, rb))
|
|
print(f" confronto {a[:10]} vs {b[:10]} su {len(set(A)|set(B))} simboli, "
|
|
f"crescita attesa {atteso} barre")
|
|
if not anom:
|
|
print(" -> TUTTI i simboli crescono di esattamente il numero di giorni e "
|
|
"start_reale INVARIATO")
|
|
for s, why, ra, rb in anom:
|
|
print(f" {s:<8} {why} (verdetto oggi: {rb[2] if rb else '-'})")
|
|
print(" NB: i simboli con delta 0 sono quelli con verdetto `scarta` (serie ferma sul")
|
|
print(" venue): NON producono un parquet, quindi non entrano nell'universo.")
|
|
|
|
# (b) impronta indipendente della storia: lo Sharpe degli ANNI CHIUSI
|
|
Pp, Sp = panel(partial_last=True, cut=DISCOVERY)
|
|
r_then = lens_L2(Pp, Sp)
|
|
r_now = lens_L2(P, S)
|
|
print("\n (b) impronta della storia — Sharpe degli anni CHIUSI, che una riscrittura sposterebbe:")
|
|
print(f" {'anno':<8}{'stato 25/07':>14}{'stato oggi':>13}{'pubblicato':>13}{'barre':>8}")
|
|
for y, pub in zip((2024, 2025, 2026), PUB["anni"]):
|
|
s1 = r_then[r_then.index.year == y]
|
|
s2 = r_now[r_now.index.year == y]
|
|
note = " <- anno IN CORSO: cambia perche' contiene barre nuove" if y == 2026 else ""
|
|
print(f" {y:<8}{_sh(s1.values):>14.4f}{_sh(s2.values):>13.4f}{pub:>13.2f}"
|
|
f"{len(s2):>8}{note}")
|
|
print(" -> 2024 e 2025 (731 barre) si riproducono al centesimo attraverso 4 settimane di")
|
|
print(" riscritture notturne: la STORIA CHIUSA e' stabile.")
|
|
|
|
# (c) l'unica barra che cambia: l'ultima
|
|
d = pd.read_parquet(RAW / "hl_btc_1d.parquet")
|
|
vol = d["volume"].astype(float).values
|
|
q = float(vol[-1] / np.median(vol[-40:-1]))
|
|
print("\n (c) l'unica barra che NON e' definitiva e' l'ULTIMA. `fetch_hyperliquid` gira nel")
|
|
print(" cron delle 00:30 con END = oggi, quindi scrive la barra del giorno IN CORSO:")
|
|
print(f" hl_btc_1d ultima barra {pd.Timestamp(int(d['timestamp'].iloc[-1]), unit='ms', tz='UTC')}"
|
|
f" volume {vol[-1]:,.0f} = {q*100:.1f}% del volume mediano di un giorno pieno")
|
|
print(f" (una barra piena di ~24h; questa ne ha ~{q*24*60:.0f} minuti di scambi)")
|
|
print("\n VERDETTO T2: il feed VIENE riscritto, ma NON come ipotizzato. Non c'e' riscrittura")
|
|
print(" arbitraria della storia: c'e' UNA barra provvisoria per volta — l'ultima — che il giro")
|
|
print(" successivo completa. Ogni numero calcolato su dato HL il giorno D contiene una barra")
|
|
print(" di ~30-40 minuti travestita da giorno.")
|
|
|
|
|
|
# ------------------------------------------------------------------ T3
|
|
def t3_monitor():
|
|
print("\n" + "=" * 100)
|
|
print(" T3 — IL MONITOR FORWARD (che alimenta il gate del 23/10) REGISTRA CIO' CHE CREDE?")
|
|
print("=" * 100)
|
|
if not FWD.exists() or not FWD_STATE.exists():
|
|
print(" [stato di paper_xsr assente: non misurabile]")
|
|
return
|
|
import importlib.util
|
|
spec = importlib.util.spec_from_file_location("paper_xsr_ro", ROOT / "scripts" / "live" / "paper_xsr.py")
|
|
px = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(px) # sola lettura: non si chiama main()
|
|
|
|
rec = [json.loads(x) for x in FWD.read_text().splitlines() if x.strip()]
|
|
st = json.loads(FWD_STATE.read_text())
|
|
ts, dt, W, R, rb, syms = px.build_panel()
|
|
print(f"\n inception {pd.Timestamp(st['start_ts'], unit='ms', tz='UTC').date()} "
|
|
f"barre registrate {len(rec)} universo {len(syms)} "
|
|
f"(uguale a quello congelato: {syms == st['syms']})")
|
|
|
|
i0 = int(np.where(ts == st["start_ts"])[0][0])
|
|
bk = px._book(px.MODELED_CAPITAL, len(syms))
|
|
ric = {}
|
|
for i in range(i0 + 1, len(ts)):
|
|
ric[int(ts[i])] = px._step(bk, W[i], R[i], float(rb[i]), None)
|
|
|
|
a = np.array([r["net_modeled"] for r in rec if r["ts"] in ric])
|
|
b = np.array([ric[r["ts"]] for r in rec if r["ts"] in ric])
|
|
same = int((np.abs(a - b) <= 1e-6).sum())
|
|
print(f"\n replay del libro MODELED sui dati di OGGI, dall'inception, contro cio' che il")
|
|
print(f" monitor ha REGISTRATO giorno per giorno ({len(a)} barre appaiate):")
|
|
print(f" barre identiche entro 1e-6 : {same}/{len(a)}")
|
|
print(f" correlazione registrato/ricalcolato: {np.corrcoef(a, b)[0, 1]:+.4f}")
|
|
print(f" vol annualizzata REGISTRATA : {a.std()*ANN*100:.2f}%")
|
|
print(f" vol annualizzata RICALCOLATA : {b.std()*ANN*100:.2f}%")
|
|
print(f" (la strategia e' progettata a vol ~{PUB['vol']:.1f}%: la seconda e' quella giusta)")
|
|
frac = (a.std() / b.std()) ** 2
|
|
print(f" rapporto di varianza {(a.std()/b.std())**2:.4f} -> in radice di tempo il monitor")
|
|
print(f" sta misurando ~{frac*24*60:.0f} minuti di mercato al giorno, non 24 ore.")
|
|
print("\n MECCANISMO (leggibile nel codice, non dedotto): `advance()` processa le barre con")
|
|
print(" ts > last_ts e poi porta last_ts sull'ULTIMA di esse. Al giro delle 00:30 del giorno D")
|
|
print(" l'ultima barra e' quella di D, appena scritta e lunga ~30 minuti: il monitor ne")
|
|
print(" registra il rendimento e SPOSTA last_ts oltre. Quando il giro successivo trova la")
|
|
print(" barra di D completata, D e' gia' passato. Le 23 ore e mezza restanti di OGNI giorno")
|
|
print(" non entrano in nessun rendimento registrato.")
|
|
sh_a = float(a.mean() / a.std() * ANN)
|
|
sh_b = float(b.mean() / b.std() * ANN)
|
|
se = float(ANN / np.sqrt(len(a)) / np.sqrt(365) * np.sqrt(365))
|
|
se = float(np.sqrt(365.0 / len(a)))
|
|
print(f"\n Sharpe della finestra forward: REGISTRATO {sh_a:+.2f} RICALCOLATO {sh_b:+.2f}")
|
|
print(f" ⚠ NESSUNO DEI DUE E' UNA LETTURA DEL GATE. Con {len(a)} barre l'errore standard di")
|
|
print(f" uno Sharpe annualizzato e' ~{se:.2f}: entrambi i numeri sono indistinguibili da 0 e")
|
|
print(" dalla soglia 1.0. Il gate e' il 2026-10-23 e questo script non lo anticipa; qui")
|
|
print(" servono solo a mostrare che i due strumenti non misurano la stessa grandezza.")
|
|
|
|
|
|
# ------------------------------------------------------------------ T4
|
|
def t4_haircut(P, S):
|
|
print("\n" + "=" * 100)
|
|
print(" T4 — L'ALTRA META' DEL GATE: l'haircut di eseguibilita' a $5.000 e il min-order")
|
|
print("=" * 100)
|
|
import importlib.util
|
|
spec = importlib.util.spec_from_file_location("paper_xsr_ro2", ROOT / "scripts" / "live" / "paper_xsr.py")
|
|
px = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(px)
|
|
ts, dt, W, R, rb, syms = px.build_panel()
|
|
n = len(syms)
|
|
|
|
def replay(cap0, min_order):
|
|
bk = px._book(cap0, n)
|
|
out = []
|
|
for i in range(len(ts)):
|
|
out.append(px._step(bk, W[i], R[i], float(rb[i]), min_order))
|
|
r = pd.Series(out, index=pd.to_datetime(ts, unit="ms", utc=True))
|
|
tot = float(np.prod(1 + r.values) - 1)
|
|
fill = bk["n_fill"] / max(bk["n_fill"] + bk["n_skip"], 1)
|
|
return r, tot, fill
|
|
|
|
print("\n replay dell'INTERA storia in-sample (non della finestra forward: quella e' del gate)")
|
|
print(f" {'libro':<28}{'Sharpe':>9}{'ret tot':>10}{'gambe eseguite':>17}{'haircut vs modeled':>21}")
|
|
rm, tot_m, _ = replay(5000.0, None)
|
|
print(f" {'MODELED (ribil. continuo)':<28}{_sh(rm.values):>9.2f}{tot_m*100:>9.1f}%"
|
|
f"{'100%':>17}{'-':>21}")
|
|
for mo in (5.0, 10.0, 25.0):
|
|
r5, tot5, fill = replay(5000.0, mo)
|
|
hc = (tot_m - tot5) / abs(tot_m) if tot_m else float("nan")
|
|
flag = "" if hc <= 0.40 else " <-- SOPRA LA GUARDIA 40%"
|
|
print(f" {f'REAL $5.000 min-order ${mo:.0f}':<28}{_sh(r5.values):>9.2f}{tot5*100:>9.1f}%"
|
|
f"{fill*100:>16.0f}%{hc*100:>20.1f}%{flag}")
|
|
|
|
# ticket medio per gamba
|
|
dW = np.abs(np.diff(W, axis=0, prepend=np.zeros((1, n))))
|
|
tick = dW[dW > 0] * 5000.0
|
|
print(f"\n ticket per gamba a $5.000: mediano ${np.median(tick):.2f} medio ${tick.mean():.2f}")
|
|
print(f" (pubblicato: $14.41 medio) quota di ordini sotto $5: {(tick < 5).mean()*100:.0f}%"
|
|
f" sotto $10: {(tick < 10).mean()*100:.0f}%")
|
|
print("\n -> il min-order NON e' un dettaglio: e' la variabile che decide la seconda meta' del")
|
|
print(" gate, e il numero pubblicato assume $5. Se Hyperliquid e' a $10, la riga da leggere")
|
|
print(" il 23/10 e' la seconda, non la prima.")
|
|
|
|
|
|
def main() -> None:
|
|
P, S = t1_lenti()
|
|
t2_feed(P, S)
|
|
t3_monitor()
|
|
t4_haircut(P, S)
|
|
print("\n" + "=" * 100)
|
|
print(" CONTROMISURA PROPOSTA (non implementata: questo script non tocca produzione)")
|
|
print("=" * 100)
|
|
print("""
|
|
Il difetto strutturale e' che `data/raw` e' gitignored e ogni numero di ammissione basato su
|
|
hl_* e' irriproducibile per costruzione. La contromisura minima, nella forma che il progetto usa
|
|
gia' altrove (guardia `TRONCATO`/`STORIA-CORTA` di `fetch_ib_equities.certify`):
|
|
|
|
1. IMPRONTA PER FINESTRA CHIUSA. A ogni giro, il certificatore HL scrive in un file versionato
|
|
(non in data/raw) `sha256` + conteggio barre + primo/ultimo timestamp per simbolo, calcolati
|
|
SOLO sulle barre fino a ieri (finestra chiusa). Un test confronta l'impronta di una finestra
|
|
gia' registrata: se cambia, la storia e' stata riscritta e il test rompe. Costa ~50 hash/giorno.
|
|
2. LA BARRA IN CORSO VA ETICHETTATA, NON NASCOSTA. `load_hl` (e chiunque legga hl_*) deve poter
|
|
chiedere solo le barre CHIUSE. La forma minima e' un flag esplicito con default sicuro; la
|
|
forma che il progetto preferisce e' derivare la regola dal codice sorvegliato invece di
|
|
ridichiararla in ogni consumatore.
|
|
3. I MONITOR FORWARD NON DEVONO CONSUMARE LA BARRA IN CORSO. `advance()` deve fermarsi
|
|
all'ultima barra CHIUSA; oggi ne consuma una provvisoria e sposta `last_ts` oltre, perdendo
|
|
il 98% di ogni giornata. E' un bug di produzione, non una scelta di modellazione.
|
|
|
|
RISULTATI PUBBLICATI CHE POGGIANO SU hl_* (tutti da citare con questa riserva):
|
|
XS01 (sleeve nel book di ricerca al 15%) — 19 major HL 1d
|
|
XSR01 (candidato, gate 23/10) — 50 alt HL 1d
|
|
STATARB-MULTI (falsificazione 25/07) — 50 coppie alt/BTC HL
|
|
XS-LITE (22/08) — stesse serie
|
|
CC01 / breadth alt / dispersione XDISP — stesse serie
|
|
Per i BACKTEST l'effetto e' una barra su ~965 (piccolo ma non nullo: qui vale 1.82 -> 1.78).
|
|
Per i MONITOR FORWARD l'effetto e' totale, perche' la barra provvisoria e' l'UNICA che leggono.
|
|
""")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|