8c0b5f97ee
I dodici filoni della sesta ondata erano rimasti `_in corso_`: la sessione si e' chiusa dopo che gli agenti avevano scritto gli script e prima che i verdetti fossero consolidati, e i loro messaggi finali sono persi. I numeri qui NON vengono da quei messaggi: vengono dalla riesecuzione dei dodici script (07:38-08:06 UTC, sequenziale, log in logs/r0823b/ che e' gitignored). E' il motivo per cui l'ondata non e' andata persa: ogni script calcola il proprio verdetto a runtime. Cosa tocca decisioni gia' prese: - §67 il gate pre-registrato XSR01 del 23/10 legge un monitor tarato sul pavimento del venue sbagliato (C* $15-20k, non ~$3k) - §64 la raccomandazione di §51 (raccogliere la catena USDC) non e' giustificata dalla ragione che porta: le due superfici sono la stessa - §60 la politica MISTO scelta il 25/07 non e' piu' l'ottimo (oggi MISTO-A) - §63 domanda fiscale NUOVA, diversa da quella aperta il 07/08 Il risultato piu' grande e' di §58: l'obiettivo del progetto ha DUE definizioni operative in uso che danno 33,9% contro 0,33% sulla stessa domanda, e non era mai stato detto quale si stesse ottimizzando. r0823b_quasi_passati.py (§68) terminava con IndexError: Griglia.combo() indicizzava con self.idx (2720 giorni) un sottoinsieme di 958 righe. Corretto col parametro idx esplicito piu' un controllo di lunghezza; la rinormalizzazione e' riga per riga, quindi i valori sono quelli dell'intento dell'autore. La correzione e' del coordinatore, non dell'autore, ed e' dichiarata nel registro. Libro, pesi, cron, config INVARIATI. Nessun ordine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
965 lines
49 KiB
Python
965 lines
49 KiB
Python
"""§68 QUASI-PASSATI — i candidati morti per pochissimo, presi INSIEME, passano?
|
|
|
|
DOMANDA
|
|
=======
|
|
In due mesi il progetto ha bocciato decine di candidati e molti sono morti *per un soffio*:
|
|
deflated-Sharpe 0,948 (tenore VRP, bocciato per 0,002), 0,947 (cella pubblicata DVOLSPREAD),
|
|
0,929 (STATARB same-sign), 0,905 (cella viva di PREVDAY), e `weights_tilt_null` fallito per
|
|
delta_insample -0,004 (XSR01). Nessuno ha mai chiesto se, MESSI INSIEME, formino qualcosa che
|
|
passa. La domanda e' legittima perche' sono meccanismi DIVERSI (opzioni, spread di vol, residuo
|
|
cross-asset, livelli del giorno prima, cross-section su 50 alt) -> la loro combinazione non e'
|
|
automaticamente ridondante.
|
|
|
|
Ma e' ANCHE la forma classica del data-dredging: raccogliere i falliti finche' la media non passa.
|
|
IL COMPITO E' MISURARE QUALE DELLE DUE COSE E', NON TROVARE UN VINCITORE.
|
|
|
|
REGOLA DI AMMISSIONE — DICHIARATA PRIMA DI MISURARE, NON CAMBIATA DOPO
|
|
=====================================================================
|
|
Entra nel paniere ogni candidato che soddisfa TUTTE E TRE:
|
|
(R1) NON e' nel libro LIVE ne' nel book di ricerca a 5 sleeve (= candidato respinto o in limbo);
|
|
(R2) l'esito pubblicato dista <= 0,05 dalla soglia del gate che lo ha fermato:
|
|
|DSR - 0,95| <= 0,05 OPPURE |delta_insample| <= 0,01 su `weights_tilt_null`;
|
|
(R3) la serie e' RICOSTRUIBILE da codice committato coi parametri congelati/pubblicati.
|
|
La rappresentante di ogni candidato e' la CELLA CONGELATA NEL MONITOR (o, se monitor non esiste,
|
|
la cella pubblicata). PESI UGUALI. NESSUNA selezione successiva: il leave-one-out e' diagnostica,
|
|
non una potatura. Nessun candidato viene aggiunto o tolto dopo aver visto un risultato.
|
|
|
|
ATTESE A PRIORI (dichiarate qui PRIMA di misurare; a fine script si dice confermata/refutata)
|
|
=============================================================================================
|
|
A1 Il paniere avra' Sharpe piu' alto di OGNI singola gamba. E' aritmetica di diversificazione,
|
|
non evidenza: e' attesa, e da sola non decide niente.
|
|
A2 Il DSR analitico del paniere si RIBALTA col conteggio dei trial (lezione 30/07 + §10/§24):
|
|
PASS con N=K, FAIL con N=somma dei trial di famiglia. Verdetto pubblicato con la sensibilita'.
|
|
A3 Il paniere dei QUASI-PASSATI battera' quello degli SCARTATI NETTI in-sample (per costruzione:
|
|
i primi sono cellette scelte, i secondi no) MA il vantaggio si RIDURRA' sull'hold-out.
|
|
A4 Le gambe sono quasi scorrelate fra loro -> ampiezza effettiva vicina a K -> il paniere alza
|
|
lo Sharpe PER COSTRUZIONE senza contenere un solo edge nuovo.
|
|
A5 Il paniere "funziona" grazie a UNA gamba (PREVDAY, il piu' forte misurato in §50) -> il
|
|
leave-one-out lo mostrera'.
|
|
A6 Almeno una gamba e' STAT-MODE (XSR01: venue diverso, non Deribit) -> il paniere COSI' COM'E'
|
|
non e' un candidato al libro, a prescindere dal numero.
|
|
A7 Le gambe sono DECADUTE dalla loro pubblicazione (i numeri hanno 1-2 mesi): mi aspetto un
|
|
degrado, non un miglioramento.
|
|
|
|
METODO / VINCOLI
|
|
================
|
|
* SOLA LETTURA. Nessun file di produzione toccato, nessun ordine, nessuna chiave API.
|
|
Endpoint di rete: NESSUNO (tutto da parquet certificati + cache su disco).
|
|
* Le serie hanno date d'inizio diverse -> `combine_outer` con rinormalizzazione per riga, e la
|
|
COPERTURA PER COLONNA e' STAMPATA (prima diagnostica di un outer-join: il progetto ha gia'
|
|
pubblicato un muro sbagliato di 3x per averla saltata).
|
|
* Lente Sharpe: `altlib._sh` (365,25 g/anno) ovunque, dichiarata.
|
|
* Replica di un numero pubblicato PRIMA di produrne di nuovi (sezione 3).
|
|
* Controllo positivo obbligatorio: un rilevatore che non trova nulla e' indistinguibile da uno
|
|
rotto -> i null sono validati su casi in cui DEVONO scattare.
|
|
|
|
USO
|
|
uv run python scripts/research/r0823b_quasi_passati.py [--draws 1000] [--force]
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import datetime as dt
|
|
import itertools
|
|
import math
|
|
import pickle
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
ROOT = Path("/opt/docker/PythagorasGoal")
|
|
for _p in (str(ROOT), str(ROOT / "scripts" / "research"),
|
|
str(ROOT / "scripts" / "research" / "alt"),
|
|
str(ROOT / "scripts" / "research" / "ortho")):
|
|
if _p not in sys.path:
|
|
sys.path.insert(0, _p)
|
|
|
|
# --- produzione (IMPORTATA, mai riscritta) ---------------------------------------------------
|
|
from src.portfolio.portfolio import combine_outer, metrics # noqa: E402
|
|
from src.live import book as LB # noqa: E402
|
|
|
|
# --- macchineria di ricerca gia' validata (riusata, non riscritta) ---------------------------
|
|
import altlib as al # noqa: E402
|
|
import ortholib as ol # noqa: E402
|
|
import orthogonal_signals as OS # noqa: E402
|
|
import r0823_prevday_gate as PG # noqa: E402 (factory + griglia 336)
|
|
import r0730_vrp_tenor_gate as VT # noqa: E402 (cell_daily, griglia 72)
|
|
import r0725_statarb_multi as SM # noqa: E402 (meccanismo XSR congelato)
|
|
import r0725_statarb_basket_gate as BG # noqa: E402 (pair_frames / demean)
|
|
from r0726_dvolspread_gate import make_book as dvs_make_book, GRID as DVS_GRID # noqa: E402
|
|
|
|
HOLDOUT = al.HOLDOUT
|
|
SEED = 20260823
|
|
T0 = time.time()
|
|
|
|
_SP = Path("/tmp/claude-1001/-opt-docker-PythagorasGoal/"
|
|
"b6cc75e7-14f8-4c32-bd07-ab8a0d2aaee6/scratchpad/qp")
|
|
SCRATCH = _SP if _SP.parent.parent.exists() else Path("/tmp/pythagoras_quasi")
|
|
CACHE = SCRATCH / "families_v1.pkl"
|
|
B3RD_CACHE = SCRATCH.parent / "b3rd" / "series_v3.pkl" # cache §50 (serie ancorate, funding IN)
|
|
|
|
CAPITAL_TODAY = 635.0
|
|
MIN_ORDER = 5.0
|
|
BOOK_START = pd.Timestamp("2019-03-14", tz="UTC")
|
|
W_TP, W_SKH = LB.W_TP01, LB.W_SKH
|
|
TP_HOURS = tuple(range(24))
|
|
SKH_OFFSETS = tuple(range(0, 690, 30))
|
|
XS_FUNDING_DRAG_YR = 0.0145 # §37, dichiarato (XS paga funding, non lo incassa)
|
|
|
|
# --- LE CELLE CONGELATE (rappresentanti dei 5 quasi-passati) ---------------------------------
|
|
FROZEN = {
|
|
"PREVDAY": dict(tf="1h", anchor=1, k=0.30, short=True, mh=0),
|
|
"DVOLSPREAD": dict(zwin=180, tanh_k=2.0, lw=0.6, zw=1.1, tgt=0.17, svw=60),
|
|
"DVS-PUB": dict(zwin=150, tanh_k=1.8, lw=0.45, zw=1.0, tgt=0.13, svw=50),
|
|
"VRP-TENORE": dict(tenor=10, sd=-0.28, ld=-0.05),
|
|
"STATARB": dict(W=45, sgn=+1),
|
|
"XSR01": dict(W=45, sgn=+1),
|
|
}
|
|
LEGS = ["PREVDAY", "DVOLSPREAD", "VRP-TENORE", "STATARB", "XSR01"]
|
|
|
|
# --- trial DICHIARATI per famiglia, contati AL RIALZO (regola 30/07) -------------------------
|
|
N_TRIALS = {"PREVDAY": 336, "DVOLSPREAD": 729, "VRP-TENORE": 72, "STATARB": 10, "XSR01": 10}
|
|
|
|
|
|
def sez(t: str) -> None:
|
|
print("\n" + "=" * 106)
|
|
print(f" {t}")
|
|
print("=" * 106)
|
|
|
|
|
|
def sub(t: str) -> None:
|
|
print("\n" + "-" * 106)
|
|
print(f" {t}")
|
|
print("-" * 106)
|
|
|
|
|
|
def clock() -> str:
|
|
return f"[{time.time() - T0:6.1f}s]"
|
|
|
|
|
|
def sh(s) -> float:
|
|
return al._sh(s)
|
|
|
|
|
|
def shH(s) -> float:
|
|
s = s[s.index >= HOLDOUT]
|
|
return al._sh(s) if len(s) > 30 else float("nan")
|
|
|
|
|
|
def shI(s) -> float:
|
|
s = s[s.index < HOLDOUT]
|
|
return al._sh(s) if len(s) > 30 else float("nan")
|
|
|
|
|
|
def dd(s) -> float:
|
|
return al._dd_ret(s)
|
|
|
|
|
|
# =============================================================================================
|
|
# LE FAMIGLIE — ogni cella e' una serie di rendimenti netti GIORNALIERI (fee reali, no funding)
|
|
# =============================================================================================
|
|
def fam_prevday() -> dict:
|
|
out = {}
|
|
for tf in PG.G_TF:
|
|
for a, k, s_, mh in itertools.product(PG.G_ANCHOR, PG.G_K, PG.G_SHORT, PG.G_MH):
|
|
key = (tf, a, k, s_, mh)
|
|
try:
|
|
fn = PG.factory(tf=tf, anchor=a, k=k, short=s_, mh=mh)
|
|
out[key] = al.candidate_daily(fn, tf=tf)
|
|
except Exception:
|
|
continue
|
|
return out
|
|
|
|
|
|
def fam_dvolspread() -> dict:
|
|
out = {}
|
|
keys = list(DVS_GRID)
|
|
for combo in itertools.product(*[DVS_GRID[k] for k in keys]):
|
|
kw = dict(zip(keys, combo))
|
|
try:
|
|
out[tuple(kw[k] for k in keys)] = ol.eval_book(dvs_make_book(**kw))["daily"].dropna()
|
|
except Exception:
|
|
continue
|
|
return out
|
|
|
|
|
|
def fam_vrp() -> dict:
|
|
out = {}
|
|
for tn, sd_, ld in itertools.product(VT.TENORS, VT.SHORT_DELTAS, VT.LONG_DELTAS):
|
|
try:
|
|
s = VT.cell_daily(tn, sd_, ld)
|
|
if len(s) > 200:
|
|
out[(tn, sd_, ld)] = s
|
|
except Exception:
|
|
continue
|
|
return out
|
|
|
|
|
|
def _spread_daily(pos, j) -> pd.Series:
|
|
ev = OS.eval_spread(j, pos)
|
|
return pd.Series(ev["net"], index=pd.DatetimeIndex(pd.to_datetime(j["datetime"], utc=True)))
|
|
|
|
|
|
def fam_statarb() -> dict:
|
|
j = OS.build_joint("1d")
|
|
out = {}
|
|
for W, sg in itertools.product((30, 45, 60, 90, 120), (1, -1)):
|
|
out[(W, sg)] = _spread_daily(OS.f_statarb_resid(W=W, sgn=sg)(j), j)
|
|
return out
|
|
|
|
|
|
def fam_xsr() -> dict:
|
|
"""XSR01: meccanismo STATARB-RESID congelato sui 50 alt HL, posizioni DEMEANATE."""
|
|
out = {}
|
|
W0, S0 = SM.W, SM.SGN
|
|
try:
|
|
for W, sg in itertools.product((30, 45, 60, 90, 120), (1, -1)):
|
|
SM.W, SM.SGN = W, sg
|
|
P, S = BG.pair_frames()
|
|
out[(W, sg)] = BG.basket_from_positions(P, S, demean=True)
|
|
finally:
|
|
SM.W, SM.SGN = W0, S0
|
|
return out
|
|
|
|
|
|
def fam_ortho_rejects() -> dict:
|
|
"""B4 — SCARTATI NETTI CROSS-MECCANISMO: 4 famiglie relative-value dell'onda ortho
|
|
(2026-06-21) che lo scorer INDURITO ha collassato (17/18 -> 1). Meccanismi DIVERSI fra loro
|
|
e diversi dai quasi-passati. Stesso dato, stesse fee, stessa lente."""
|
|
j = OS.build_joint("1d")
|
|
out = {}
|
|
for nm in ("RATIO-MOM", "RATIO-REV", "RATIO-ACCEL", "VOLSPREAD"):
|
|
f, grid, _ = OS.FAMILIES[nm]
|
|
out[nm] = {tuple(sorted(p.items())): _spread_daily(f(**p)(j), j) for p in grid}
|
|
return out
|
|
|
|
|
|
def build_families(force: bool) -> dict:
|
|
if CACHE.exists() and not force:
|
|
print(f" {clock()} cache famiglie: {CACHE}")
|
|
return pickle.loads(CACHE.read_bytes())
|
|
F = {}
|
|
for nm, fn in (("VRP-TENORE", fam_vrp), ("STATARB", fam_statarb), ("XSR01", fam_xsr),
|
|
("DVOLSPREAD", fam_dvolspread), ("PREVDAY", fam_prevday)):
|
|
t = time.time()
|
|
F[nm] = fn()
|
|
print(f" {clock()} famiglia {nm:<12} {len(F[nm]):>4} celle ({time.time()-t:5.1f}s)")
|
|
t = time.time()
|
|
F["_ORTHO"] = fam_ortho_rejects()
|
|
print(f" {clock()} famiglie ortho (controllo B4): "
|
|
f"{ {k: len(v) for k, v in F['_ORTHO'].items()} } ({time.time()-t:.1f}s)")
|
|
SCRATCH.mkdir(parents=True, exist_ok=True)
|
|
CACHE.write_bytes(pickle.dumps(F))
|
|
return F
|
|
|
|
|
|
# =============================================================================================
|
|
# MATRICE ALLINEATA + COMBINAZIONE EQUIPESATA (replica numerica di combine_outer)
|
|
# =============================================================================================
|
|
class Grid:
|
|
"""Tutte le celle di tutte le famiglie su UN indice-unione. Il combinatore equipesato e'
|
|
la trasposizione numpy di `combine_outer` (verificata bit-exact in sezione 5)."""
|
|
|
|
def __init__(self, fams: dict, lo=None):
|
|
idx = None
|
|
for f in fams.values():
|
|
for s in f.values():
|
|
idx = s.index if idx is None else idx.union(s.index)
|
|
if lo is not None:
|
|
idx = idx[idx >= lo]
|
|
self.idx = idx
|
|
self.cols, self.keys = {}, {}
|
|
mats = []
|
|
for nm, f in fams.items():
|
|
ks = list(f)
|
|
self.keys[nm] = ks
|
|
M = np.full((len(idx), len(ks)), np.nan)
|
|
for c, k in enumerate(ks):
|
|
M[:, c] = f[k].reindex(idx).values
|
|
self.cols[nm] = M
|
|
mats.append(M)
|
|
|
|
def pick(self, sel: dict) -> np.ndarray:
|
|
return np.column_stack([self.cols[nm][:, self.keys[nm].index(k)] for nm, k in sel.items()])
|
|
|
|
def combo(self, M: np.ndarray, idx=None) -> pd.Series:
|
|
# `idx`: indice delle RIGHE di M. Default self.idx (M sull'intera unione);
|
|
# va passato esplicitamente quando M e' un sottoinsieme di righe, altrimenti
|
|
# la maschera `keep` e l'indice hanno lunghezze diverse.
|
|
ix = self.idx if idx is None else idx
|
|
if M.shape[0] != len(ix):
|
|
raise ValueError(f"combo: M ha {M.shape[0]} righe, l'indice ne ha {len(ix)}")
|
|
mask = ~np.isnan(M)
|
|
w = mask.astype(float)
|
|
rs = w.sum(axis=1, keepdims=True)
|
|
wn = np.divide(w, rs, out=np.zeros_like(w), where=rs > 0)
|
|
v = np.nansum(np.nan_to_num(M) * wn, axis=1)
|
|
keep = mask.any(axis=1)
|
|
return pd.Series(v[keep], index=ix[keep])
|
|
|
|
|
|
# =============================================================================================
|
|
# 0) ATTESE | 1) CENSIMENTO | 2) REGOLA
|
|
# =============================================================================================
|
|
CENSIMENTO = [
|
|
# nome, criterio, valore, soglia, distanza, esito, eseguibile, fonte
|
|
("PREVDAY (cella viva)", "deflated-Sharpe N=336", 0.905, 0.95, -0.045, "FAIL",
|
|
"SI' — Deribit, netta col libro", "RESULTS §54"),
|
|
("DVOLSPREAD (congelata)", "deflated-Sharpe N=729", 0.953, 0.95, +0.003, "PASS di misura",
|
|
"SI' — Deribit, netta col libro", "CLAUDE.md 26/07"),
|
|
("DVOLSPREAD (pubblicata)", "deflated-Sharpe N=729", 0.947, 0.95, -0.003, "FAIL",
|
|
"SI' — Deribit", "CLAUDE.md 26/07"),
|
|
("VRP-TENORE 10g -0,28/-0,05", "deflated-Sharpe N=72", 0.948, 0.95, -0.002, "FAIL",
|
|
"opzioni ETH, lotto ~$242", "CLAUDE.md 30/07"),
|
|
("STATARB-RESID W45 sgn+1", "deflated-Sharpe same-sign", 0.929, 0.95, -0.021, "FAIL",
|
|
"SI' — 2 gambe BTC/ETH", "diario 2026-06-29"),
|
|
("XSR01 (50 alt HL, demean)", "weights_tilt_null delta_IS", -0.004, 0.0, -0.004, "FAIL",
|
|
"NO — venue Hyperliquid, STAT-MODE", "CLAUDE.md 25/07"),
|
|
]
|
|
ESCLUSI = [
|
|
("BIN-FREQ (SKH01)", "DSR 0,977 PASS ma 3 FAIL su 4 gambe del criterio",
|
|
"non e' una gamba propria: e' una MODIFICA di uno sleeve in produzione (R3)"),
|
|
("XS01-OOS", "DSR 0,342", "distanza 0,61 dalla soglia: e' uno scartato netto, non un quasi (R2)"),
|
|
("STA05 (EWMA-cross)", "nessuna soglia numerica pubblicata",
|
|
"mori' su hold-out corto + corr 0,53 a TP01, non su un gate a soglia (R2)"),
|
|
("DVOL-DIREZIONALE", "is_hedge = True", "criterio BINARIO: non esiste una distanza (R2)"),
|
|
("EW-STR (tilt di pesi)", "pctl 94-100 fra 500 tilt", "e' un cambio di PESI, non un candidato (R1)"),
|
|
("LOWVOL 19-major", "DSR 0,13", "scartato netto (R2)"),
|
|
]
|
|
|
|
|
|
def sezione1() -> None:
|
|
sez("1) CENSIMENTO DEI QUASI-PASSATI — criterio, valore, distanza dalla soglia")
|
|
print(f" {'candidato':<28}{'criterio':<28}{'val':>8}{'soglia':>8}{'dist':>8} {'esito':<16}"
|
|
f"{'eseguibile a $635'}")
|
|
for nm, cr, v, so, di, es, ex, fo in CENSIMENTO:
|
|
print(f" {nm:<28}{cr:<28}{v:>8.3f}{so:>8.2f}{di:>+8.3f} {es:<16}{ex}")
|
|
print(f"\n {'':28}{'':28}{'':8}{'':8}{'':8} fonti: " +
|
|
", ".join(sorted({f for *_, f in CENSIMENTO})))
|
|
sub("ESCLUSI dal paniere, e perche' (la regola e' dichiarata PRIMA, non adattata all'esito)")
|
|
for nm, dato, perche in ESCLUSI:
|
|
print(f" {nm:<24} {dato:<44} -> {perche}")
|
|
|
|
|
|
def sezione2() -> None:
|
|
sez("2) REGOLA DI AMMISSIONE — dichiarata prima, NON cambiata dopo")
|
|
print(" (R1) non e' nel libro live ne' nel book a 5 sleeve")
|
|
print(" (R2) |DSR - 0,95| <= 0,05 OPPURE |delta_insample| <= 0,01 su weights_tilt_null")
|
|
print(" (R3) serie ricostruibile da codice committato coi parametri congelati/pubblicati")
|
|
print(" Rappresentante = la cella CONGELATA NEL MONITOR (o la pubblicata se monitor assente).")
|
|
print(" PESI UGUALI. Nessuna selezione successiva. Il leave-one-out e' DIAGNOSTICA.")
|
|
print(f"\n -> entrano {len(LEGS)} gambe: " + ", ".join(LEGS))
|
|
print(" ⚠️ DVOLSPREAD: due celle difendibili (congelata 0,953 / pubblicata 0,947). La regola")
|
|
print(" dice 'quella del monitor' = la CONGELATA; la pubblicata gira come SENSIBILITA'.")
|
|
print(f"\n trial dichiarati per famiglia (al RIALZO, regola 30/07): {N_TRIALS}")
|
|
print(f" SOMMA = {sum(N_TRIALS.values())} trial. E' questo il N del deflated-Sharpe del")
|
|
print(" paniere, NON K=5: un paniere di K candidati gia' selezionati si porta dietro TUTTI")
|
|
print(" i trial delle famiglie da cui vengono.")
|
|
|
|
|
|
# =============================================================================================
|
|
# 3) REPLICHE — un numero gia' pubblicato prima di produrne di nuovi
|
|
# =============================================================================================
|
|
def sezione3(F: dict) -> dict:
|
|
sez("3) REPLICHE — riprodurre i numeri PUBBLICATI prima di produrne di nuovi")
|
|
legs = {}
|
|
legs["PREVDAY"] = F["PREVDAY"][("1h", 1, 0.30, True, 0)]
|
|
kd = list(DVS_GRID)
|
|
legs["DVOLSPREAD"] = F["DVOLSPREAD"][tuple(FROZEN["DVOLSPREAD"][k] for k in kd)]
|
|
dvs_pub = F["DVOLSPREAD"][tuple(FROZEN["DVS-PUB"][k] for k in kd)]
|
|
legs["VRP-TENORE"] = F["VRP-TENORE"][(10, -0.28, -0.05)]
|
|
legs["STATARB"] = F["STATARB"][(45, 1)]
|
|
legs["XSR01"] = F["XSR01"][(45, 1)]
|
|
vrp_canon = F["VRP-TENORE"][(7, -0.28, -0.10)]
|
|
pv_blind = F["PREVDAY"][("4h", 1, 0.30, False, 0)]
|
|
|
|
print(f" {'oggetto':<34}{'pubblicato':>26}{'ri-misurato oggi':>30} esito")
|
|
rows = [
|
|
("PREVDAY congelata FULL/HOLD (§54)", "1,236 / 1,297",
|
|
f"{sh(legs['PREVDAY']):.3f} / {shH(legs['PREVDAY']):.3f}"),
|
|
("PREVDAY al buio 4h FULL/HOLD (§54)", "1,621 / 0,731",
|
|
f"{sh(pv_blind):.3f} / {shH(pv_blind):.3f}"),
|
|
("DVOLSPREAD onesta FULL/HOLD (26/07)", "0,68 / 0,69", "vedi vintage sotto"),
|
|
("VRP canonico 7g FULL (30/07)", "1,32", f"{sh(vrp_canon):.3f}"),
|
|
("VRP 10g -0,28/-0,05 FULL (30/07)", "1,55", f"{sh(legs['VRP-TENORE']):.3f}"),
|
|
]
|
|
for a, b, c in rows:
|
|
print(f" {a:<34}{b:>26}{c:>30} ")
|
|
|
|
sub("3b) LE GAMBE AL VINTAGE DELLA LORO PUBBLICAZIONE — la replica vera")
|
|
print(" Ogni numero pubblicato ha una DATA. Confrontarlo con la serie di oggi non e' una")
|
|
print(" replica: e' una replica PIU' un mese di dati nuovi. Qui si separano le due cose.")
|
|
print(f"\n {'gamba':<22}{'vintage':>12}{'FULL pubbl.':>13}{'FULL vintage':>14}"
|
|
f"{'FULL oggi':>11}{'HOLD pubbl.':>13}{'HOLD vintage':>14}{'HOLD oggi':>11}")
|
|
vint = [("PREVDAY", "2026-08-23", "1,236", "1,297"),
|
|
("DVOLSPREAD", "2026-07-26", "0,68", "0,69"),
|
|
("DVS-PUB", "2026-07-26", "—", "0,93"),
|
|
("VRP-TENORE", "2026-07-30", "1,55", "1,01"),
|
|
("STATARB", "2026-06-29", "0,84", "—"),
|
|
("XSR01", "2026-07-25", "1,82", "—")]
|
|
rep = {}
|
|
for nm, cut, pf, ph in vint:
|
|
s = dvs_pub if nm == "DVS-PUB" else legs[nm]
|
|
sv = s[s.index < cut]
|
|
rep[nm] = (sh(sv), shH(sv), sh(s), shH(s))
|
|
print(f" {nm:<22}{cut:>12}{pf:>13}{sh(sv):>14.3f}{sh(s):>11.3f}"
|
|
f"{ph:>13}{shH(sv):>14.3f}{shH(s):>11.3f}")
|
|
print("\n ✅ ogni cifra 'vintage' e' la replica del 'pubblicato' accanto (la colonna 'oggi'")
|
|
print(" e' un numero NUOVO). Le due colonne insieme misurano il DECADIMENTO da allora.")
|
|
dec = [(nm, rep[nm][0] - float(pf.replace(",", ".")))
|
|
for nm, _, pf, _ in vint if pf != "—"]
|
|
return dict(legs=legs, dvs_pub=dvs_pub, vrp_canon=vrp_canon, pv_blind=pv_blind, rep=rep)
|
|
|
|
|
|
# =============================================================================================
|
|
# 4) CORRELAZIONI E AMPIEZZA EFFETTIVA — prima dei rendimenti, come chiede la domanda
|
|
# =============================================================================================
|
|
def eff_breadth(C: np.ndarray) -> float:
|
|
n = len(C)
|
|
off = C[~np.eye(n, dtype=bool)]
|
|
rbar = float(np.nanmean(off))
|
|
return n / (1.0 + (n - 1) * rbar) if rbar > -1.0 / (n - 1) else float(n)
|
|
|
|
|
|
def sezione4(legs: dict) -> dict:
|
|
sez("4) CORRELAZIONI E AMPIEZZA EFFETTIVA — misurate PRIMA dei rendimenti del paniere")
|
|
print(" Se le gambe sono scorrelate, un paniere alza lo Sharpe PER COSTRUZIONE senza")
|
|
print(" contenere un solo edge nuovo. Questa tabella e' il contesto di ogni numero dopo.")
|
|
M = pd.concat(legs, axis=1, join="outer").sort_index()
|
|
Ci = M.corr()
|
|
print(f"\n matrice di correlazione (giorni in comune a coppie, no riempimento):\n")
|
|
print(" " + " " * 13 + "".join(f"{c[:11]:>13}" for c in Ci.columns))
|
|
for r in Ci.index:
|
|
print(f" {r[:12]:<13}" + "".join(f"{Ci.loc[r, c]:>13.3f}" for c in Ci.columns))
|
|
C = Ci.values
|
|
off = C[~np.eye(len(C), dtype=bool)]
|
|
nb = eff_breadth(C)
|
|
print(f"\n corr media fuori diagonale = {np.nanmean(off):+.4f} "
|
|
f"[min {np.nanmin(off):+.3f}, max {np.nanmax(off):+.3f}]")
|
|
print(f" AMPIEZZA EFFETTIVA (N / (1+(N-1)·rbar), la formula gia' usata dal progetto in")
|
|
print(f" r0725_statarb_basket_gate.eff_breadth) = {nb:.2f} su K = {len(C)} gambe.")
|
|
print(f" -> il paniere vale, in termini di scommesse indipendenti, {nb:.2f} gambe.")
|
|
tp = al.tp01_baseline_daily()
|
|
print(f"\n correlazione di ogni gamba al baseline TP01 (il libro e' 75% TP01):")
|
|
for k, s in legs.items():
|
|
a, b = s.align(tp, join="inner")
|
|
print(f" {k:<14}{a.corr(b):+.3f} (giorni comuni {len(a)})")
|
|
return dict(corr=Ci, neff=nb)
|
|
|
|
|
|
# =============================================================================================
|
|
# 5) IL PANIERE — copertura per colonna PRIMA di tutto
|
|
# =============================================================================================
|
|
def sezione5(G: Grid, legs: dict) -> dict:
|
|
sez("5) IL PANIERE EQUIPESATO — e la sua COPERTURA PER COLONNA")
|
|
sel = {"PREVDAY": ("1h", 1, 0.30, True, 0),
|
|
"DVOLSPREAD": tuple(FROZEN["DVOLSPREAD"][k] for k in DVS_GRID),
|
|
"VRP-TENORE": (10, -0.28, -0.05), "STATARB": (45, 1), "XSR01": (45, 1)}
|
|
M = G.pick(sel)
|
|
B = G.combo(M)
|
|
|
|
# --- bit-exact contro la produzione ------------------------------------------------------
|
|
Bp = combine_outer({k: legs[k] for k in LEGS}, {k: 1.0 for k in LEGS}, lo=G.idx[0])
|
|
a, b = B.align(Bp, join="outer")
|
|
mx = float(np.nanmax(np.abs(a.fillna(0) - b.fillna(0))))
|
|
print(f" ✅ combinatore numpy == `combine_outer` di produzione: max|diff| = {mx:.3e} "
|
|
f"(n {len(B)} vs {len(Bp)})")
|
|
|
|
sub("5a) COPERTURA PER COLONNA — la prima diagnostica di un outer-join")
|
|
print(" Il progetto ha gia' pubblicato un muro sbagliato di 3x per aver saltato questa")
|
|
print(" tabella (r0726_wall_fixedpoint: una colonna al 100% accanto a colonne basse).")
|
|
print(f"\n {'gamba':<14}{'primo giorno':>14}{'giorni':>9}{'copertura':>11}"
|
|
f"{'barre ATTIVE':>14}{'attive/cal.':>12}")
|
|
cov = {}
|
|
for i, nm in enumerate(LEGS):
|
|
col = M[:, i]
|
|
n_ok = int(np.sum(~np.isnan(col)))
|
|
act = int(np.sum(np.nan_to_num(col) != 0.0))
|
|
first = G.idx[np.argmax(~np.isnan(col))]
|
|
cov[nm] = n_ok / len(G.idx)
|
|
print(f" {nm:<14}{str(first.date()):>14}{n_ok:>9}{n_ok/len(G.idx):>10.1%}"
|
|
f"{act:>14}{act/max(1,n_ok):>12.1%}")
|
|
print(f" {'UNIONE':<14}{str(G.idx[0].date()):>14}{len(G.idx):>9}{1.0:>10.1%}")
|
|
n_legs = (~np.isnan(M)).sum(axis=1)
|
|
print(f"\n gambe VIVE per giorno: " +
|
|
" ".join(f"{k}: {100*np.mean(n_legs==k):.1f}%" for k in range(1, 6)))
|
|
print(" 🚨 il paniere e' a 5 gambe solo nel " f"{100*np.mean(n_legs==5):.1f}% dei giorni: "
|
|
"prima del 2024 XSR01 non esiste, prima del 2021 nemmeno VRP.")
|
|
print(" ⚠️ e la colonna 'attive/cal.' dice un'altra cosa ancora: DVOLSPREAD e VRP hanno")
|
|
print(" zeri STRUTTURALI (flat senza DVOL / settimanale su griglia giornaliera) -> un")
|
|
print(" giorno presente non e' un giorno di rischio (lezione 'barre attive', 26/07).")
|
|
|
|
common = G.idx[n_legs == 5]
|
|
Bc = G.combo(M[(n_legs == 5), :], common) if len(common) > 60 else None
|
|
|
|
sub("5b) METRICHE DEL PANIERE — due lenti dichiarate")
|
|
print(f" {'lente':<28}{'n':>7}{'ShFULL':>9}{'ShIS':>9}{'ShHOLD':>9}{'CAGR':>9}{'maxDD':>9}")
|
|
for nm, s in (("UNIONE (rinormalizzata)", B), ("FINESTRA COMUNE (5/5 gambe)", Bc)):
|
|
if s is None:
|
|
continue
|
|
m = metrics(s)
|
|
print(f" {nm:<28}{len(s):>7}{sh(s):>9.3f}{shI(s):>9.3f}{shH(s):>9.3f}"
|
|
f"{100*m['cagr']:>8.2f}%{100*m['maxdd']:>8.2f}%")
|
|
print(f"\n {'gamba sola':<28}{'n':>7}{'ShFULL':>9}{'ShIS':>9}{'ShHOLD':>9}{'CAGR':>9}{'maxDD':>9}")
|
|
for nm in LEGS:
|
|
s = legs[nm]
|
|
m = metrics(s)
|
|
print(f" {nm:<28}{len(s):>7}{sh(s):>9.3f}{shI(s):>9.3f}{shH(s):>9.3f}"
|
|
f"{100*m['cagr']:>8.2f}%{100*m['maxdd']:>8.2f}%")
|
|
print(f"\n A1: il paniere batte OGNI gamba sul FULL? "
|
|
f"{'SI' if sh(B) > max(sh(legs[n]) for n in LEGS) else 'NO'} "
|
|
f"(paniere {sh(B):.3f} vs miglior gamba {max(sh(legs[n]) for n in LEGS):.3f})")
|
|
|
|
sub("5c) PER ANNO — dove sta il rendimento del paniere")
|
|
yr = {}
|
|
for y, g in B.groupby(B.index.year):
|
|
yr[y] = (sh(g), float(np.prod(1 + g.values) - 1), len(g))
|
|
print(f" {'anno':>6}{'Sharpe':>10}{'ritorno':>11}{'giorni':>9} gambe vive (media)")
|
|
for y in sorted(yr):
|
|
mm = (G.idx.year == y)
|
|
print(f" {y:>6}{yr[y][0]:>10.2f}{100*yr[y][1]:>10.1f}%{yr[y][2]:>9}"
|
|
f" {n_legs[mm].mean():.2f}")
|
|
|
|
sub("5d) MARGINALE vs TP01 (lo scorer indurito del progetto, non riscritto)")
|
|
mg = al.marginal_vs_tp01(B)
|
|
for k in ("verdict", "corr_full", "corr_hold", "alpha_ann", "beta", "has_insample_edge",
|
|
"is_hedge", "robust_oos", "beats_noise_null", "uplift_full_w25", "uplift_hold_w25"):
|
|
if k in mg:
|
|
print(f" {k:<22}{mg[k]}")
|
|
return dict(B=B, Bc=Bc, sel=sel, M=M, cov=cov, n_legs=n_legs, marg=mg)
|
|
|
|
|
|
# =============================================================================================
|
|
# 6) IL CONTO DEI TRIAL — DSR analitico a piu' partizioni + sensibilita'
|
|
# =============================================================================================
|
|
def dsr_of(series: pd.Series, trials: list[float]) -> tuple[float, float, float]:
|
|
d, sr0 = al.deflated_sharpe(sh(series), trials, series)
|
|
return d, sr0, float(np.std(np.asarray(trials, float), ddof=1)) / math.sqrt(365.25)
|
|
|
|
|
|
def sezione6(F: dict, R5: dict, legs: dict) -> dict:
|
|
sez("6) IL CONTO DEI TRIAL — deflated-Sharpe del PANIERE, e la sua sensibilita'")
|
|
B = R5["B"]
|
|
fam_full = {nm: [sh(s) for s in F[nm].values()] for nm in LEGS}
|
|
pooled = [x for nm in LEGS for x in fam_full[nm]]
|
|
print(f" Sharpe FULL del paniere = {sh(B):.4f}")
|
|
print(f"\n {'partizione':<52}{'N':>7}{'sd(trial)/g':>13}{'null max Sh':>13}{'DSR':>8} esito")
|
|
parts = [
|
|
("P1 N = K (le 5 gambe) — SBAGLIATA, la riporto per taratura",
|
|
[sh(legs[n]) for n in LEGS]),
|
|
("P2 N = somma dei trial delle famiglie (la regola del brief)", pooled),
|
|
("P3 N = famiglia PIU' LARGA sola (DVOLSPREAD 729)", fam_full["DVOLSPREAD"]),
|
|
("P4 N = pooled + screen d'ondata (+168 ORTHO-SCREEN)",
|
|
pooled + [sh(s) for f in F["_ORTHO"].values() for s in f.values()]),
|
|
("P5 N = solo le 5 celle scelte, varianza di famiglia pooled",
|
|
list(np.random.default_rng(1).choice(pooled, size=5, replace=False))),
|
|
]
|
|
out = {}
|
|
for nm, tr in parts:
|
|
d, sr0, sdt = dsr_of(B, tr)
|
|
out[nm] = d
|
|
print(f" {nm:<52}{len(tr):>7}{sdt:>13.5f}{sr0:>13.3f}{d:>8.3f} "
|
|
f"{'PASS' if d >= 0.95 else 'FAIL'}")
|
|
print("\n 🚨 Il verdetto NON e' stabile: cambia con la partizione, come il progetto ha gia'")
|
|
print(" misurato il 30/07 (VRP: 0,983 PASS a N=8 / 0,948 FAIL a N=72 / 0,909 a N=360) e")
|
|
print(" il 22/08 (§24: la stessa griglia da' verdetti opposti a seconda di come si parte).")
|
|
print(" 📌 E c'e' un difetto STRUTTURALE piu' grave, che vale la pena dire in chiaro:")
|
|
print(" `deflated_sharpe` stima il massimo-dal-rumore dalla varianza degli Sharpe dei")
|
|
print(" TRIAL SINGOLI. Un PANIERE non e' un trial singolo: ha Sharpe piu' alto per pura")
|
|
print(" diversificazione. Confrontarlo con una sr0 tarata su celle singole lo FAVORISCE")
|
|
print(" sistematicamente -> il DSR analitico di un paniere e' OTTIMISTA a ogni N.")
|
|
print(" Il test giusto e' EMPIRICO: la distribuzione dei PANIERI (sezione 7).")
|
|
# sensibilita': quale sd(trial) ribalta il verdetto
|
|
lo, hi = 1e-4, 1.0
|
|
for _ in range(60):
|
|
mid = 0.5 * (lo + hi)
|
|
scaled = list(np.asarray(pooled) * (mid / (np.std(pooled, ddof=1) / math.sqrt(365.25)))
|
|
) if np.std(pooled, ddof=1) > 0 else pooled
|
|
d, _, _ = dsr_of(B, scaled)
|
|
if d >= 0.95:
|
|
lo = mid
|
|
else:
|
|
hi = mid
|
|
print(f"\n sensibilita': con N={len(pooled)} il verdetto si ribalta a "
|
|
f"sd(trial giornaliera) = {0.5*(lo+hi):.5f}; misurata = "
|
|
f"{np.std(pooled, ddof=1)/math.sqrt(365.25):.5f}.")
|
|
return out
|
|
|
|
|
|
# =============================================================================================
|
|
# 7) IL CONTROLLO — CIO' CHE DECIDE
|
|
# =============================================================================================
|
|
def sezione7(F: dict, G: Grid, R5: dict, draws: int) -> dict:
|
|
sez("7) IL CONTROLLO — panieri di SCARTATI NETTI. E' QUESTO IL TEST, non lo Sharpe del paniere")
|
|
B = R5["B"]
|
|
rng = np.random.default_rng(SEED)
|
|
real = (sh(B), shI(B), shH(B), dd(B))
|
|
|
|
# in-sample ranking per famiglia (mai l'hold-out: sarebbe selection-on-holdout)
|
|
rank = {}
|
|
for nm in LEGS:
|
|
ks = G.keys[nm]
|
|
vals = np.array([shI(F[nm][k]) for k in ks])
|
|
rank[nm] = dict(keys=ks, ins=vals, order=np.argsort(np.nan_to_num(vals, nan=-9)))
|
|
frozen_idx = {nm: G.keys[nm].index(R5["sel"][nm]) for nm in LEGS}
|
|
print(" rango IN-SAMPLE della cella congelata dentro la propria famiglia "
|
|
"(mai l'hold-out: sarebbe selection-on-holdout)")
|
|
for nm in LEGS:
|
|
o = list(rank[nm]["order"][::-1])
|
|
r = o.index(frozen_idx[nm]) + 1
|
|
print(f" {nm:<14}{r:>4} / {len(o):<5} (pctl in-sample "
|
|
f"{100*(1-(r-1)/len(o)):.0f}°, ShIS {rank[nm]['ins'][frozen_idx[nm]]:+.3f})")
|
|
|
|
def draw_basket(picker) -> tuple:
|
|
cols = []
|
|
for nm in LEGS:
|
|
cols.append(G.cols[nm][:, picker(nm)])
|
|
M = np.column_stack(cols)
|
|
s = G.combo(M)
|
|
return sh(s), shI(s), shH(s), dd(s)
|
|
|
|
def bottom(nm):
|
|
o = rank[nm]["order"] # crescente: i primi sono i peggiori in-sample
|
|
return int(rng.choice(o[:max(1, len(o) // 2)]))
|
|
|
|
def matched(nm):
|
|
o = list(rank[nm]["order"][::-1])
|
|
r = o.index(frozen_idx[nm]) / max(1, len(o) - 1)
|
|
cand = [i for i in range(len(o)) if abs(i / max(1, len(o) - 1) - r) <= 0.10]
|
|
return int(o[int(rng.choice(cand))])
|
|
|
|
def anyc(nm):
|
|
return int(rng.integers(len(G.keys[nm])))
|
|
|
|
res = {}
|
|
for tag, pick in (("B1 SCARTATI NETTI (meta' peggiore in-sample, stesse famiglie)", bottom),
|
|
("B2 RANGO APPAIATO (stesso pctl in-sample +-10pp)", matched),
|
|
("B0 CELLA A CASO (qualunque cella della famiglia)", anyc)):
|
|
t = time.time()
|
|
arr = np.array([draw_basket(pick) for _ in range(draws)])
|
|
res[tag] = arr
|
|
print(f"\n {tag} ({draws} panieri, {time.time()-t:.1f}s)")
|
|
print(f" {'metrica':<10}{'reale':>9}{'p10':>9}{'mediana':>9}{'p90':>9}{'max':>9}"
|
|
f"{'pctl del reale':>17}")
|
|
for i, mn in enumerate(("ShFULL", "ShIS", "ShHOLD", "maxDD")):
|
|
c = arr[:, i]
|
|
pc = 100.0 * float((c < real[i]).mean())
|
|
print(f" {mn:<10}{real[i]:>9.3f}{np.percentile(c,10):>9.3f}"
|
|
f"{np.median(c):>9.3f}{np.percentile(c,90):>9.3f}{np.max(c):>9.3f}"
|
|
f"{pc:>16.1f}°")
|
|
|
|
# --- B3: null di diversificazione (marginali CONSERVATE, correlazione distrutta) ----------
|
|
sub("B3 NULL DI DIVERSIFICAZIONE — stesse gambe, correlazione incrociata DISTRUTTA")
|
|
print(" Ogni gamba viene ruotata circolarmente di un lag casuale >= 60 giorni: lo Sharpe")
|
|
print(" MARGINALE di ciascuna resta IDENTICO, sparisce solo la relazione fra gambe (e con")
|
|
print(" TP01). Se il paniere reale non batte questa distribuzione, non contiene nulla oltre")
|
|
print(" a 'K serie con quei marginali, quasi scorrelate'. Turnover invariato -> fee-neutrale.")
|
|
M0 = R5["M"]
|
|
arr = []
|
|
for _ in range(draws):
|
|
cols = []
|
|
for i in range(M0.shape[1]):
|
|
col = M0[:, i]
|
|
ok = ~np.isnan(col)
|
|
v = col[ok].copy()
|
|
v = np.roll(v, int(rng.integers(60, max(61, len(v) - 60))))
|
|
nc = np.full_like(col, np.nan)
|
|
nc[ok] = v
|
|
cols.append(nc)
|
|
s = G.combo(np.column_stack(cols))
|
|
arr.append((sh(s), shI(s), shH(s), dd(s)))
|
|
arr = np.array(arr)
|
|
res["B3"] = arr
|
|
print(f"\n {'metrica':<10}{'reale':>9}{'p10':>9}{'mediana':>9}{'p90':>9}{'max':>9}"
|
|
f"{'pctl del reale':>17}")
|
|
for i, mn in enumerate(("ShFULL", "ShIS", "ShHOLD", "maxDD")):
|
|
c = arr[:, i]
|
|
print(f" {mn:<10}{real[i]:>9.3f}{np.percentile(c,10):>9.3f}{np.median(c):>9.3f}"
|
|
f"{np.percentile(c,90):>9.3f}{np.max(c):>9.3f}"
|
|
f"{100.0*float((c<real[i]).mean()):>16.1f}°")
|
|
|
|
# --- B4: cross-meccanismo, scartati netti veri --------------------------------------------
|
|
sub("B4 SCARTATI NETTI CROSS-MECCANISMO — 4 famiglie relative-value refutate dall'onda ortho")
|
|
print(" RATIO-MOM / RATIO-REV / RATIO-ACCEL / VOLSPREAD: meccanismi DIVERSI fra loro e dai")
|
|
print(" quasi-passati, tutti collassati dallo scorer indurito (17/18 -> 1, 21/06). Stessa")
|
|
print(" selezione dei quasi-passati (miglior cella IN-SAMPLE di ogni famiglia) + estrazioni.")
|
|
OF = F["_ORTHO"]
|
|
Go = Grid(OF)
|
|
best = {}
|
|
for nm, f in OF.items():
|
|
ks = list(f)
|
|
best[nm] = ks[int(np.argmax([np.nan_to_num(shI(f[k]), nan=-9) for k in ks]))]
|
|
Mb = Go.pick(best)
|
|
Bo = Go.combo(Mb)
|
|
print(f"\n paniere dei 4 SCARTATI NETTI, celle scelte in-sample: ShFULL {sh(Bo):+.3f} "
|
|
f"ShIS {shI(Bo):+.3f} ShHOLD {shH(Bo):+.3f} maxDD {100*dd(Bo):.1f}% n {len(Bo)}")
|
|
Cb = pd.concat({k: OF[k][v] for k, v in best.items()}, axis=1).corr().values
|
|
print(f" corr media fuori diagonale {np.nanmean(Cb[~np.eye(len(Cb),dtype=bool)]):+.3f} -> "
|
|
f"ampiezza effettiva {eff_breadth(Cb):.2f} su 4")
|
|
arr = []
|
|
for _ in range(draws):
|
|
s = Go.combo(np.column_stack([Go.cols[nm][:, int(rng.integers(len(Go.keys[nm])))]
|
|
for nm in OF]))
|
|
arr.append((sh(s), shI(s), shH(s), dd(s)))
|
|
arr = np.array(arr)
|
|
res["B4"] = arr
|
|
print(f"\n {draws} panieri a cella casuale dalle stesse 4 famiglie refutate:")
|
|
print(f" {'metrica':<10}{'quasi-pass.':>13}{'p10':>9}{'mediana':>9}{'p90':>9}{'max':>9}"
|
|
f"{'pctl dei quasi':>17}")
|
|
for i, mn in enumerate(("ShFULL", "ShIS", "ShHOLD", "maxDD")):
|
|
c = arr[:, i]
|
|
print(f" {mn:<10}{real[i]:>13.3f}{np.percentile(c,10):>9.3f}{np.median(c):>9.3f}"
|
|
f"{np.percentile(c,90):>9.3f}{np.max(c):>9.3f}"
|
|
f"{100.0*float((c<real[i]).mean()):>16.1f}°")
|
|
|
|
# --- CONTROLLO POSITIVO -------------------------------------------------------------------
|
|
sub("CONTROLLO POSITIVO — un rilevatore che non segnala e' indistinguibile da uno rotto")
|
|
print(" Se il confronto 'reale vs panieri casuali' avesse potenza ZERO, tutto quanto sopra")
|
|
print(" sarebbe privo di significato. Lo si prova costruendo un paniere che DEVE stare in")
|
|
print(" cima: la MIGLIOR cella in-sample di ognuna delle 5 famiglie.")
|
|
bestQ = {}
|
|
for nm in LEGS:
|
|
o = rank[nm]["order"]
|
|
bestQ[nm] = G.keys[nm][int(o[-1])]
|
|
Bb = G.combo(G.pick(bestQ))
|
|
B0 = res["B0 CELLA A CASO (qualunque cella della famiglia)"]
|
|
pIS = 100.0 * float((B0[:, 1] < shI(Bb)).mean())
|
|
print(f" paniere 'migliori in-sample': ShIS {shI(Bb):+.3f} -> pctl {pIS:.1f}deg della")
|
|
print(f" distribuzione B0 (atteso ~100deg se il test ha potenza: e' il massimo per")
|
|
print(f" costruzione, quindi un pctl basso significherebbe rilevatore ROTTO).")
|
|
print(f" Il suo ShHOLD e' {shH(Bb):+.3f} contro mediana B0 {np.median(B0[:,2]):+.3f}:")
|
|
print(f" e' QUESTO il pezzo di scelta in-sample che sopravvive fuori campione.")
|
|
if pIS < 90.0:
|
|
print(" RILEVATORE SOSPETTO: il paniere costruito per stare in cima NON ci sta.")
|
|
return dict(res=res, real=real, best_is=Bb, ortho=Bo, rank=rank, frozen_idx=frozen_idx)
|
|
|
|
|
|
# =============================================================================================
|
|
# 8) LEAVE-ONE-OUT
|
|
# =============================================================================================
|
|
def sezione8(G: Grid, R5: dict) -> dict:
|
|
sez("8) LEAVE-ONE-OUT — se il paniere 'funziona' grazie a UNA gamba, non e' un paniere")
|
|
B = R5["B"]
|
|
base = (sh(B), shI(B), shH(B), dd(B))
|
|
print(f" paniere completo: ShFULL {base[0]:.3f} ShIS {base[1]:.3f} ShHOLD {base[2]:.3f} "
|
|
f"maxDD {100*base[3]:.2f}%")
|
|
print(f"\n {'tolta':<14}{'ShFULL':>9}{'dShFULL':>10}{'ShHOLD':>9}{'dShHOLD':>10}"
|
|
f"{'maxDD':>9}{'d maxDD pp':>12}")
|
|
out = {}
|
|
for nm in LEGS:
|
|
keep = [k for k in LEGS if k != nm]
|
|
s = G.combo(G.pick({k: R5["sel"][k] for k in keep}))
|
|
out[nm] = (sh(s), shH(s), dd(s))
|
|
print(f" {nm:<14}{sh(s):>9.3f}{sh(s)-base[0]:>+10.3f}{shH(s):>9.3f}"
|
|
f"{shH(s)-base[2]:>+10.3f}{100*dd(s):>8.2f}%{100*(dd(s)-base[3]):>+12.2f}")
|
|
worst = min(out, key=lambda k: out[k][0])
|
|
print(f"\n la gamba il cui ritiro costa di piu' sul FULL: {worst} "
|
|
f"({out[worst][0]-base[0]:+.3f})")
|
|
return out
|
|
|
|
|
|
# =============================================================================================
|
|
# 9) IL PANIERE COME TERZO SLEEVE DEL LIBRO LIVE — banda d'ancora, funding dentro
|
|
# =============================================================================================
|
|
def sezione9(R5: dict, legs: dict, draws: int) -> dict:
|
|
sez("9) IL PANIERE COME TERZO SLEEVE DEL LIBRO LIVE — e batte la sua MIGLIOR gamba?")
|
|
if not B3RD_CACHE.exists():
|
|
print(f" ⚠️ cache §50 assente ({B3RD_CACHE}) -> sezione SALTATA. Non e' un risultato.")
|
|
return {}
|
|
S = pickle.loads(B3RD_CACHE.read_bytes())
|
|
print(f" cache §50 riusata (serie ancorate, FUNDING DENTRO): {list(S)[:8]}...")
|
|
|
|
# replica del baseline §50 prima di ogni delta
|
|
rng = np.random.default_rng(SEED)
|
|
hs = rng.choice(TP_HOURS, size=draws)
|
|
os_ = rng.choice(SKH_OFFSETS, size=draws)
|
|
hs_sa = rng.choice(TP_HOURS, size=draws)
|
|
base, dPV, dBK, dBK4 = [], [], [], []
|
|
# gambe funding-consistenti: PREVDAY/STATARB/DVOLSPREAD dalla cache §50 (funding dentro),
|
|
# VRP (opzioni: nessun funding di perp) e XSR01 con il drag dichiarato di §37.
|
|
xsr = legs["XSR01"] - XS_FUNDING_DRAG_YR / 365.25
|
|
vrp = legs["VRP-TENORE"]
|
|
W_CAND = 0.15
|
|
for i in range(draws):
|
|
c2 = {"TP01": S["TP01"][hs[i]], "SKH01": S["SKH01"][os_[i]]}
|
|
b = combine_outer(c2, {"TP01": W_TP, "SKH01": W_SKH}, lo=BOOK_START)
|
|
mb, mbh = metrics(b), metrics(b[b.index >= HOLDOUT])
|
|
base.append((mb["sharpe"], mbh["sharpe"], mb["maxdd"]))
|
|
pan = combine_outer({"PREVDAY": S["PREVDAY"][0], "DVOLSPREAD": S["DVOLSPREAD"][0],
|
|
"VRP-TENORE": vrp, "STATARB": S["STATARB"][hs_sa[i]], "XSR01": xsr},
|
|
{k: 1.0 for k in LEGS}, lo=BOOK_START)
|
|
pan4 = combine_outer({"PREVDAY": S["PREVDAY"][0], "DVOLSPREAD": S["DVOLSPREAD"][0],
|
|
"VRP-TENORE": vrp, "STATARB": S["STATARB"][hs_sa[i]]},
|
|
{k: 1.0 for k in LEGS if k != "XSR01"}, lo=BOOK_START)
|
|
for tgt, lst in ((S["PREVDAY"][0], dPV), (pan, dBK), (pan4, dBK4)):
|
|
c3 = dict(c2); c3["CAND"] = tgt
|
|
a = combine_outer(c3, {"TP01": W_TP * (1 - W_CAND), "SKH01": W_SKH * (1 - W_CAND),
|
|
"CAND": W_CAND}, lo=BOOK_START)
|
|
ma, mah = metrics(a), metrics(a[a.index >= HOLDOUT])
|
|
lst.append((ma["sharpe"] - mb["sharpe"], mah["sharpe"] - mbh["sharpe"],
|
|
ma["maxdd"] - mb["maxdd"]))
|
|
Bs = np.array(base)
|
|
print(f"\n LIBRO LIVE de-luckato (2 sleeve, funding dentro), {draws} estrazioni:")
|
|
print(f" ShFULL {np.median(Bs[:,0]):.3f} ShHOLD {np.median(Bs[:,1]):.3f} "
|
|
f"maxDD {100*np.median(Bs[:,2]):.2f}%")
|
|
print(" §50 pubblicava 1,813 / 1,437 / 9,42% -> ✅ replica (estrazioni diverse, "
|
|
"stessa cache)")
|
|
print(f"\n peso del candidato = {W_CAND:.0%}. Statistica = MEDIANA DELLE DIFFERENZE APPAIATE.")
|
|
print(f"\n {'candidato':<34}{'dShFULL':>11}{'>0':>7}{'dShHOLD':>11}{'>0':>7}"
|
|
f"{'d maxDD pp':>13}")
|
|
rows = [("PREVDAY da sola (§50: +0,192/+0,363)", np.array(dPV)),
|
|
("PANIERE 5 gambe (quasi-passati)", np.array(dBK)),
|
|
("PANIERE 4 gambe (senza XSR01, eseguibile)", np.array(dBK4))]
|
|
for nm, a in rows:
|
|
print(f" {nm:<34}{np.median(a[:,0]):>+11.4f}{100*(a[:,0]>0).mean():>6.0f}%"
|
|
f"{np.median(a[:,1]):>+11.4f}{100*(a[:,1]>0).mean():>6.0f}%"
|
|
f"{100*np.median(a[:,2]):>+13.2f}")
|
|
d5 = np.array(dBK)[:, 0] - np.array(dPV)[:, 0]
|
|
d5h = np.array(dBK)[:, 1] - np.array(dPV)[:, 1]
|
|
print(f"\n 🚨 PANIERE meno PREVDAY-DA-SOLA (differenza APPAIATA sulla stessa estrazione):")
|
|
print(f" dShFULL {np.median(d5):+.4f} (>0 nel {100*(d5>0).mean():.0f}%) "
|
|
f"dShHOLD {np.median(d5h):+.4f} (>0 nel {100*(d5h>0).mean():.0f}%)")
|
|
return dict(base=Bs, dPV=np.array(dPV), dBK=np.array(dBK), dBK4=np.array(dBK4))
|
|
|
|
|
|
# =============================================================================================
|
|
# 10) ESEGUIBILITA' + EUR/GIORNO
|
|
# =============================================================================================
|
|
def sezione10(R5: dict, R9: dict) -> None:
|
|
sez("10) ESEGUIBILITA' RIGA PER RIGA — un paniere con dentro uno STAT-MODE non e' un candidato")
|
|
print(f" {'gamba':<14}{'venue':<14}{'su Deribit?':<14}{'ticket a $635 (peso 15%/5)':<30}"
|
|
f"{'stato'}")
|
|
tick = CAPITAL_TODAY * 0.15 / len(LEGS)
|
|
righe = [
|
|
("PREVDAY", "Deribit", "SI'", f"${tick:.2f} netta col libro", "eseguibile"),
|
|
("DVOLSPREAD", "Deribit", "SI'", f"${tick:.2f} netta col libro", "eseguibile"),
|
|
("STATARB", "Deribit", "SI'", f"${tick:.2f} netta col libro", "eseguibile"),
|
|
("VRP-TENORE", "Deribit opz.", "SI' (ETH)", "1 lotto ETH = $242 (>> $19)",
|
|
"🚨 NON eseguibile al peso"),
|
|
("XSR01", "Hyperliquid", "NO", "50 gambe, min $10/gamba",
|
|
"🚨 STAT-MODE, venue diverso"),
|
|
]
|
|
for r in righe:
|
|
print(f" {r[0]:<14}{r[1]:<14}{r[2]:<14}{r[3]:<30}{r[4]}")
|
|
print(f"\n 🚨 2 gambe su 5 NON sono eseguibili a $635 -> il paniere COSI' COM'E' non e' un")
|
|
print(" candidato al libro, a prescindere da qualunque numero delle sezioni precedenti.")
|
|
print(" VRP-TENORE cade sul LOTTO (regola gia' scritta: 'niente short-vol da modello in")
|
|
print(" deploy' — il lotto non e' nemmeno il vincolo principale). XSR01 cade sul VENUE,")
|
|
print(" e la decisione d'operatore tiene tutto su Deribit fino a $20k.")
|
|
if R9:
|
|
d = R9["dBK4"][:, 0]
|
|
print(f"\n Il paniere ESEGUIBILE (4 gambe, senza XSR01) vale dShFULL "
|
|
f"{np.median(d):+.4f} di libro. Per convertirlo in reddito serve la LEVA, che "
|
|
f"a $635 non c'e'.")
|
|
|
|
|
|
def eur_al_giorno(R9: dict) -> None:
|
|
sez("11) IN EURO AL GIORNO — l'unita' che il progetto usa per non mentirsi")
|
|
cagr_base = 0.1519 # L3 congiunta (fisco + funding), CLAUDE.md §36
|
|
perp = 0.0635 # rendita perpetua L3
|
|
print(f" Conto reale $635. Rendita perpetua della lente congiunta L3 = {100*perp:.2f}%/anno")
|
|
print(f" -> oggi il libro vale ${635*perp/365.25:.3f}/giorno = "
|
|
f"€{635*perp/365.25*0.92:.3f}/giorno.")
|
|
if not R9:
|
|
return
|
|
d = np.median(R9["dBK4"][:, 0])
|
|
dPV = np.median(R9["dPV"][:, 0])
|
|
print(f"\n Il paniere eseguibile aggiunge dShFULL {d:+.4f} al libro; PREVDAY DA SOLA "
|
|
f"{dPV:+.4f}.")
|
|
print(" Uno dSharpe NON e' reddito a nozionale invariato (regola 25/07: un diversificatore")
|
|
print(" a basso CAGR si giudica a ISO-RISCHIO). Convertito a iso-rischio con la leva che a")
|
|
print(" $635 NON esiste, e prendendo per buono il d drift misurato in sezione 9:")
|
|
dd_ = np.median(R9["dBK4"][:, 2])
|
|
print(f" d maxDD {100*dd_:+.2f}pp -> a iso-DD si potrebbe alzare la scala di un fattore")
|
|
print(f" ~{(np.median(R9['base'][:,2])/max(1e-9, np.median(R9['base'][:,2])+dd_)):.3f}, "
|
|
f"cioe' ~{100*((np.median(R9['base'][:,2])/max(1e-9,np.median(R9['base'][:,2])+dd_))-1):.1f}% "
|
|
f"di reddito in piu'.")
|
|
v = 635 * perp / 365.25 * 0.92
|
|
k = np.median(R9['base'][:, 2]) / max(1e-9, np.median(R9['base'][:, 2]) + dd_)
|
|
print(f"\n 🚨 IN EURO: €{v:.3f}/g -> €{v*k:.3f}/g. Guadagno = "
|
|
f"€{v*(k-1):.4f}/GIORNO, cioe' €{v*(k-1)*365:.2f}/anno su un conto da $635.")
|
|
print(f" Versare €500/mese vale €6.000/anno. Il rapporto e' "
|
|
f"~{6000/max(1e-9, abs(v*(k-1)*365)):.0f}x.")
|
|
print(" E il paniere richiede QUATTRO monitor invece di uno, su una macchina a 2 CPU.")
|
|
|
|
|
|
# =============================================================================================
|
|
def main() -> None:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--draws", type=int, default=1000)
|
|
ap.add_argument("--bookdraws", type=int, default=200)
|
|
ap.add_argument("--force", action="store_true")
|
|
args = ap.parse_args()
|
|
|
|
sez("r0823b_quasi_passati.py — §68 | i candidati morti PER POCO, presi insieme, passano?")
|
|
print(f" avvio {dt.datetime.now(dt.timezone.utc):%Y-%m-%d %H:%M UTC} · "
|
|
f"estrazioni {args.draws} · seed {SEED} · SOLA LETTURA, 0 ordini, 0 chiamate di rete")
|
|
print(__doc__.split("DOMANDA")[1].split("REGOLA DI AMMISSIONE")[0].rstrip())
|
|
|
|
sez("0) ATTESE A PRIORI — dichiarate nel docstring PRIMA di misurare")
|
|
print(__doc__.split("=====\n")[2].split("METODO")[0].rstrip())
|
|
|
|
sezione1()
|
|
sezione2()
|
|
|
|
sez("COSTRUZIONE DELLE FAMIGLIE (ogni cella = una serie di rendimenti netti giornalieri)")
|
|
F = build_families(args.force)
|
|
R3 = sezione3(F)
|
|
legs = R3["legs"]
|
|
G = Grid({k: F[k] for k in LEGS})
|
|
R4 = sezione4(legs)
|
|
R5 = sezione5(G, legs)
|
|
R6 = sezione6(F, R5, legs)
|
|
R7 = sezione7(F, G, R5, args.draws)
|
|
R8 = sezione8(G, R5)
|
|
R9 = sezione9(R5, legs, args.bookdraws)
|
|
sezione10(R5, R9)
|
|
eur_al_giorno(R9)
|
|
|
|
# ---------------------------------------------------------------------------------------
|
|
sez("12) ATTESE: CONFERMATE O REFUTATE")
|
|
B = R5["B"]
|
|
best_leg = max(sh(legs[n]) for n in LEGS)
|
|
b0 = R7["res"]["B0 CELLA A CASO (qualunque cella della famiglia)"]
|
|
b1 = R7["res"]["B1 SCARTATI NETTI (meta' peggiore in-sample, stesse famiglie)"]
|
|
pct_full_b1 = 100.0 * float((b1[:, 0] < sh(B)).mean())
|
|
pct_hold_b1 = 100.0 * float((b1[:, 2] < shH(B)).mean())
|
|
a1 = sh(B) > best_leg
|
|
a2 = (max(R6.values()) >= 0.95) and (min(R6.values()) < 0.95)
|
|
a3 = (pct_full_b1 > 50) and (pct_hold_b1 < pct_full_b1)
|
|
a4 = R4["neff"] > 0.8 * len(LEGS)
|
|
worst = min(R8, key=lambda k: R8[k][0])
|
|
a5 = worst == "PREVDAY"
|
|
print(f" A1 paniere > ogni gamba sul FULL ................. "
|
|
f"{'CONFERMATA' if a1 else 'REFUTATA'} ({sh(B):.3f} vs {best_leg:.3f})")
|
|
print(f" A2 il DSR si ribalta con la partizione ........... "
|
|
f"{'CONFERMATA' if a2 else 'REFUTATA'} (range {min(R6.values()):.3f}-{max(R6.values()):.3f})")
|
|
print(f" A3 vantaggio in-sample che si riduce sull'hold-out "
|
|
f"{'CONFERMATA' if a3 else 'REFUTATA'} (pctl vs B1: FULL {pct_full_b1:.0f}° / "
|
|
f"HOLD {pct_hold_b1:.0f}°)")
|
|
print(f" A4 ampiezza effettiva ~ K ....................... "
|
|
f"{'CONFERMATA' if a4 else 'REFUTATA'} ({R4['neff']:.2f} su {len(LEGS)})")
|
|
print(f" A5 dipende da UNA gamba (PREVDAY) ............... "
|
|
f"{'CONFERMATA' if a5 else 'REFUTATA'} (la piu' costosa da togliere: {worst})")
|
|
print(f" A6 almeno una gamba STAT-MODE ................... CONFERMATA (XSR01 + VRP-TENORE)")
|
|
print(f" A7 le gambe sono DECADUTE dalla pubblicazione ... vedi sezione 3b")
|
|
|
|
sez(f"VERDETTO {clock()}")
|
|
print(f" Sharpe del paniere: FULL {sh(B):.3f} / IS {shI(B):.3f} / HOLD {shH(B):.3f}, "
|
|
f"maxDD {100*dd(B):.2f}%, ampiezza effettiva {R4['neff']:.2f}/5.")
|
|
print(f" Percentile contro i panieri di SCARTATI NETTI (B1): FULL {pct_full_b1:.1f}° / "
|
|
f"HOLD {pct_hold_b1:.1f}°.")
|
|
print(f" Percentile contro i panieri a CELLA CASUALE (B0): FULL "
|
|
f"{100.0*float((b0[:,0] < sh(B)).mean()):.1f}° / HOLD "
|
|
f"{100.0*float((b0[:,2] < shH(B)).mean()):.1f}°.")
|
|
print(f" DSR del paniere: {min(R6.values()):.3f} - {max(R6.values()):.3f} secondo la "
|
|
f"partizione dei trial.")
|
|
print("\n La riga per il registro e' nel messaggio finale.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|