Files
PythagorasGoal/scripts/research/r0725_xsr_deploy_gate.py
T
Adriano Dal Pastro 3034bfdc58 gate XSR01: riscrittura DICHIARATA (opzione A dell'operatore) + haircut con script
Decisione operatore 26/08, a 58 giorni dall'esito, in direzione che stringe:
(1) capitale deploy $5k -> $20k — riconcilia il gate (25/07) con la decisione
vincolante "100% Deribit fino a $20k" (26/07), posteriore, che governa (M28);
deploy XSR01 e scelta del venue ora coincidono in un punto solo.
(2) gamba haircut: numero decisivo da r0826_xsr_haircut.py (N11) a pavimento
$10 (il vero, HL-EXEC), citato con la frazione di ordini eseguiti (P7).
Misurato: FULL 968 barre floor $10 -> haircut -1,1% con 22% di eseguiti (la
guardia da sola era vacua: un libro fermo ha haircut piccolo); ticket mediano
$3,34/gamba, 84% sotto $10 — sostituisce il "$14,41" senza script.
(3) contesto 1.82 -> 1.79 (lente dei gate). Soglie numeriche INVARIATE.

Chiude il debito S5.3. Diario 2026-08-26-xsr01-gate-riscritto.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B9UyJLHzR7EJzxR3iQ3RN1
2026-08-26 11:04:57 +00:00

123 lines
7.0 KiB
Python

"""r0725_xsr_deploy_gate — gate di decisione PRE-REGISTRATO per XSR01 (fissato 2026-07-25).
PERCHE' PRE-REGISTRATO. La regola si fissa oggi, forward-day 0, PRIMA di vedere qualunque barra
out-of-sample. Decidere "a occhio" dopo aver visto i numeri e' selection-on-forward — il bias che
questo progetto ha gia' pagato due volte (gate SELECTION-ON-HOLDOUT 2026-06-29; EW-STR refutato
come selezione di 2° ordine 2026-07-01). Stesso trattamento gia' dato a STATARB-RESID.
CONTESTO. XSR01 e' il candidato del 2026-07-25: cross-sectional residual dollar-neutral sui 50 alt
HL. In-sample (2.6 anni) fa Sharpe netta 1.82 / lorda 2.70, maxDD -2.6%, marginale ADDS,
deflated-Sharpe 0.985, corr ~0 a tutti e 5 gli sleeve. Sopravvive a tre null fee-neutrali e al
test di lag. NON e' nel book per tre motivi dichiarati: storia corta e monotona crescente,
`weights_tilt_null` fallito, e margine di costo sottile con slippage NON modellato.
⚠️ MODIFICA DICHIARATA 2026-08-26 (diario `2026-08-26-xsr01-gate-riscritto.md`) — decisa
dall'operatore PRIMA di vedere l'esito, a 58 giorni dalla data, e in direzione che STRINGE:
1. CAPITALE per il deploy: $5.000 -> $20.000. Non e' tuning sull'esito: riconcilia il gate
(scritto il 25/07) con la decisione vincolante "100% Deribit fino a $20k" (26/07), che e'
POSTERIORE e lo governa — sotto $20k nessun capitale esce da Deribit, quindi il criterio
a $5k autorizzava una cosa che un'altra decisione gia' vietava (M28).
2. GAMBA HAIRCUT: il numero decisivo alla decisione e' quello dello script committato
`r0826_xsr_haircut.py` a pavimento $10 (il pavimento VERO misurato, HL-EXEC 22/08 — il
libro REAL-$5000 del monitor gira a $5 ed e' ottimista per costruzione), CITATO ACCANTO
alla frazione di ordini eseguiti (P7): a $10 si esegue ~1 ordine su 5, e un haircut
piccolo con pochi ordini eseguiti non e' eseguibilita', e' un libro fermo. La SOGLIA
resta il 40% pre-registrato: nessuna soglia nuova viene scritta oggi coi dati in mano.
3. Il riferimento "in-sample 1.82" era una TERZA lente (XSR-REPRO 22/08): la lente dei gate
da' 1.79 alla scoperta. Corretto il contesto, non il criterio.
La serie forward e' quella RIGENERATA il 26/08 (advance() riparato, §5.1): stessa finestra,
stesso forward-day 0, barre finalmente VERE.
REGOLA (immutabile — ogni modifica va motivata nel diario come violazione):
- Data decisione: 2026-10-23 (90 giorni forward dal 2026-07-25). Non prima.
- Metrica primaria: Sharpe annualizzato di `net_modeled` su TUTTA la finestra forward.
- GUARDIA DI COSTO, la piu' importante per questa strategia (turnover 38.5% del lordo/giorno su
50 alt anche illiquidi): il rapporto Sharpe_forward / Sharpe_in_sample_netta(1.82) va letto
insieme allo scarto MODELED-vs-REAL$5000. Se l'haircut di eseguibilita' a $5000 supera il 40%
del rendimento modellato, la strategia e' un artefatto di costo e va RITIRATA a prescindere
dallo Sharpe.
- Esiti:
Sharpe >= 1.0 AND haircut$5000 <= 40% -> CANDIDATO SLEEVE: proporre peso {5,10}% e
giudicarlo con `weights_tilt_null` (che oggi NON passa) + maxDD combinato.
Deploy solo se il tilt-null passa E il capitale e' >= $20.000 (mod. 26/08).
0.3 <= Sh < 1.0 -> ESTENDI una sola volta di 90g (2027-01-21).
Sharpe < 0.3 OR haircut > 40% -> RITIRO dal forward-monitor.
- Guardie accessorie: config invariata (il test `tests/test_paper_xsr.py` la blocca);
universo costante a 50 gambe (un cambio impone --reset e azzera la finestra).
Uso: `uv run python scripts/research/r0725_xsr_deploy_gate.py`
"""
from __future__ import annotations
import json
from datetime import date
from pathlib import Path
import numpy as np
ROOT = Path(__file__).resolve().parents[2]
RETURNS = ROOT / "data" / "paper_xsr" / "returns.jsonl"
START = date(2026, 7, 25)
DECISION = date(2026, 10, 23)
DECISION_EXT = date(2027, 1, 21)
SH_DEPLOY = 1.0
SH_RETIRE = 0.3
HAIRCUT_MAX = 0.40
CAPITALE_DEPLOY = 20_000.0 # mod. dichiarata 26/08: riconcilia col muro "100% Deribit" (26/07)
IS_SHARPE_NET = 1.79 # lente dei gate alla scoperta (il "1.82" era una terza lente)
def main() -> None:
print("=" * 92)
print(" XSR01 — gate di decisione PRE-REGISTRATO (fissato 2026-07-25, forward-day 0)")
print("=" * 92)
if not RETURNS.exists():
print(f" nessun dato forward ancora: {RETURNS} assente.")
print(f" data decisione: {DECISION} (mancano {(DECISION - date.today()).days} giorni)")
return
rows = [json.loads(x) for x in RETURNS.read_text().splitlines() if x.strip()]
if len(rows) < 5:
print(f" finestra forward troppo corta ({len(rows)} barre).")
print(f" data decisione: {DECISION} (mancano {(DECISION - date.today()).days} giorni)")
return
rm = np.array([r["net_modeled"] for r in rows])
key5 = "net_REAL-$5000"
r5 = np.array([r.get(key5, np.nan) for r in rows])
sh = float(rm.mean() / rm.std() * np.sqrt(365)) if rm.std() > 0 else 0.0
eq = np.cumprod(1 + rm)
dd = float(np.max((np.maximum.accumulate(eq) - eq) / np.maximum.accumulate(eq)))
tot_m = float(np.prod(1 + rm) - 1)
tot_5 = float(np.prod(1 + r5[np.isfinite(r5)]) - 1) if np.isfinite(r5).any() else float("nan")
haircut = (tot_m - tot_5) / abs(tot_m) if tot_m not in (0.0,) and np.isfinite(tot_5) else float("nan")
today = date.today()
print(f" finestra forward : {rows[0]['dt'][:10]} -> {rows[-1]['dt'][:10]} ({len(rows)} barre)")
print(f" Sharpe fwd (mod) : {sh:+.2f} (in-sample netta era {IS_SHARPE_NET:.2f})")
print(f" maxDD fwd : {dd:.1%}")
print(f" ritorno MODELED : {tot_m*100:+.2f}% REAL-$5000: {tot_5*100:+.2f}%")
print(f" haircut monitor : {haircut*100:.1f}% (pavimento $5 — ottimista per costruzione)")
print(f" guardia <= {HAIRCUT_MAX*100:.0f}%: il numero DECISIVO e' quello di "
f"`r0826_xsr_haircut.py` a pavimento $10,")
print( " citato accanto alla frazione di ordini eseguiti (mod. 26/08)")
print(f" data decisione : {DECISION} (proroga unica: {DECISION_EXT})")
if today < DECISION:
print(f"\n -> NESSUNA DECISIONE OGGI ({today}): mancano {(DECISION - today).days} giorni.")
print(" Il numero corrente NON autorizza deploy anticipato (regola pre-registrata).")
return
if np.isfinite(haircut) and haircut > HAIRCUT_MAX:
print("\n -> RITIRO: l'haircut di eseguibilita' supera la guardia. E' costo, non edge.")
elif sh >= SH_DEPLOY:
print("\n -> CANDIDATO SLEEVE: proporre peso {5,10}% e passarlo a weights_tilt_null "
f"(oggi NON passa) + maxDD combinato. Deploy solo con capitale >= "
f"${CAPITALE_DEPLOY:,.0f} (mod. 26/08, allineato a '100% Deribit fino a $20k').")
elif sh >= SH_RETIRE:
print(f"\n -> SOTTO SOGLIA ma >= {SH_RETIRE}: estensione unica fino al {DECISION_EXT}.")
else:
print("\n -> RITIRO dal forward-monitor.")
if __name__ == "__main__":
main()