research(wave-0822): GATE-RECON — il difetto ribalta STATARB (+1,96 -> -2,02), la premessa della rigenerazione e' confermata per via diretta
This commit is contained in:
@@ -696,6 +696,7 @@ stessi hanno nominato senza poterli eseguire**. Stesso contratto di consegna, st
|
|||||||
| 28 | DEPEG | il falsificatore che l'autore di 21 ha nominato contro se stesso: il depeg USDT contamina il ranking? | **SOPRAVVIVE** — il venue sposta 0,01 di Sharpe |
|
| 28 | DEPEG | il falsificatore che l'autore di 21 ha nominato contro se stesso: il depeg USDT contamina il ranking? | **SOPRAVVIVE** — il venue sposta 0,01 di Sharpe |
|
||||||
| 29 | PROP-RECAL | J rifatto con XS01 dalla storia lunga: il canale funded si apre? | **LEAD ridimensionato ~5x** — 42% → **7,8%** |
|
| 29 | PROP-RECAL | J rifatto con XS01 dalla storia lunga: il canale funded si apre? | **LEAD ridimensionato ~5x** — 42% → **7,8%** |
|
||||||
| 30 | LISTINO | `GATE PROP-01` gamba (a): la firm lista >=10 delle 13 gambe con short? | **PASS 13/13** — verificato sul venue, non sul sito |
|
| 30 | LISTINO | `GATE PROP-01` gamba (a): la firm lista >=10 delle 13 gambe con short? | **PASS 13/13** — verificato sul venue, non sul sito |
|
||||||
|
| 31 | GATE-RECON | cosa direbbero i 3 gate pre-registrati se le serie forward fossero riparate | **1 gate su 3 ribaltato** — e la premessa e' CONFERMATA |
|
||||||
| 22 | MAKER | l'esecuzione passiva e' una fonte di ritorno, al netto del costo di non essere eseguiti? | **SCARTATO** — il segno dipende da `<` contro `<=` |
|
| 22 | MAKER | l'esecuzione passiva e' una fonte di ritorno, al netto del costo di non essere eseguiti? | **SCARTATO** — il segno dipende da `<` contro `<=` |
|
||||||
| 23 | BOCPD | un rilevatore di cambio di regime vero batte il miglior lookback COSTANTE? | **SCARTATO** — filone chiuso definitivamente |
|
| 23 | BOCPD | un rilevatore di cambio di regime vero batte il miglior lookback COSTANTE? | **SCARTATO** — filone chiuso definitivamente |
|
||||||
| 24 | CRITICO | cosa NON e' stato misurato, e quale singola misura mancante vale di piu' | **3 correzioni all'ondata**, 1 regola mia ritirata |
|
| 24 | CRITICO | cosa NON e' stato misurato, e quale singola misura mancante vale di piu' | **3 correzioni all'ondata**, 1 regola mia ritirata |
|
||||||
@@ -1361,3 +1362,72 @@ prezzo esatto del taglio $100k sono **primari-dichiarativi + secondari concordi*
|
|||||||
📌 **Conseguenza sul gate:** la gamba (a) e' **CHIUSA con un PASS**. Restano la **(b)** — banda
|
📌 **Conseguenza sul gate:** la gamba (a) e' **CHIUSA con un PASS**. Restano la **(b)** — banda
|
||||||
d'ancora di SKH01 sotto la lente prop, che un agente sta girando ora invece di aspettare il 31/10 — e
|
d'ancora di SKH01 sotto la lente prop, che un agente sta girando ora invece di aspettare il 31/10 — e
|
||||||
la **(c)**, che va **ricalcolata** con la quota come deposito rimborsabile invece che come spesa.
|
la **(c)**, che va **ricalcolata** con la quota come deposito rimborsabile invece che come spesa.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 31 — GATE-RECON (cosa direbbero i gate con le serie riparate) — **sola lettura, verificata**
|
||||||
|
|
||||||
|
`scripts/research/r0822c_gate_recon.py` (719 righe, ~25 s). **Zero celle di selezione**: e' un audit,
|
||||||
|
tutte le soglie **lette dai file pre-registrati**, nessun parametro scelto dall'agente.
|
||||||
|
✅ **Sola lettura provata**: md5 + mtime di **ogni** file `data/paper_*` invariati dopo la corsa.
|
||||||
|
|
||||||
|
| gate | serie | n | Sharpe | maxDD | vol ann. |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| **STATARB** | **attuale** | 54 | **+1,96** | **2,24%** | 7,88% |
|
||||||
|
| **STATARB** | **riparata** | 53 | **−2,02** | **15,19%** | **31,42%** |
|
||||||
|
| XSR01 | attuale | 28 | −6,55 | 0,28% | 0,46% |
|
||||||
|
| XSR01 | riparata | 27 | −0,99 | 0,82% | 2,78% |
|
||||||
|
| DVOLSPREAD | attuale | 28 | −15,82 | 0,93% | 0,79% |
|
||||||
|
| DVOLSPREAD | riparata | 27 | −5,21 | 7,11% | 19,36% |
|
||||||
|
|
||||||
|
Minuti di mercato registrati al giorno: **3,9 / 41,2 / 2,5** = ✅ replica indipendente dei 4/41/2
|
||||||
|
pubblicati stamattina.
|
||||||
|
|
||||||
|
**`STATARB 27/09 — riparata: RITIRO · attuale: CANDIDATO AL DEPLOY`**
|
||||||
|
**`XSR01 23/10 — riparata: RITIRO · attuale: RITIRO`**
|
||||||
|
**`DVOLSPREAD 24/10 — riparata: KILL · attuale: KILL`**
|
||||||
|
*(Non sono i verdetti dei gate: sono i criteri letti **oggi**, su finestre incomplete — 54/90, 28/90,
|
||||||
|
28/91 barre. Anticipare un gate pre-registrato e' selezione sull'hold-out, e l'agente lo dichiara.)*
|
||||||
|
|
||||||
|
✅ **LA PREMESSA SU CUI POGGIA TUTTO E' ORA CONFERMATA PER VIA DIRETTA, non piu' di traverso:**
|
||||||
|
`data/_feed_backup/*.prebuild.bak` conserva il **vintage pre-riscrittura** di stanotte →
|
||||||
|
**0 barre chiuse cambiate su 1.761.259** (BTC/ETH 1h+5m). L'unica riga cambiata per file e' **l'ultima
|
||||||
|
del vintage, che era aperta** (BTC 21/08: close 73732,5→73690, volume 342→492 = **il difetto stesso,
|
||||||
|
in chiaro**). Su 62 cicli `paper_prevday` fa 1427/1488 identiche, con tasso di divergenza **piatto
|
||||||
|
nell'eta'** (2,2-5,1%, nessuna crescita) e concentrato all'ora del cron.
|
||||||
|
⚠️ **Limite dichiarato:** `hl_*_1d` e `dvol_*` **non hanno vintage su disco** → per XSR01 e
|
||||||
|
DVOLSPREAD l'evidenza resta **indiretta**.
|
||||||
|
|
||||||
|
**Prove d'identita' obbligatorie prima di ogni confronto:** **I1** pannello letto ora == quello del
|
||||||
|
cron di stanotte (`max|d| = 0`, 3/3, contro lo `state.json` scritto alle 00:35); **I2** barre gia'
|
||||||
|
chiuse identiche al bit (statarb 6/54, xsr 1/28, **dvolspread 0/28 → per quel monitor la prova I2 NON
|
||||||
|
ESISTE, dichiarato**); **I3** troncare l'INPUT == affettare l'output (`max|d| = 0`, 3/3).
|
||||||
|
|
||||||
|
🚨 **(1) IL VETO D'INTEGRITA' DI DVOLSPREAD NON SCATTA: 28 attive / 28 = 100% in ENTRAMBE le lenti.**
|
||||||
|
Una serie che misura **2 minuti di mercato al giorno** passa al **100%** un veto progettato apposta per
|
||||||
|
non far decidere su dati mancanti. **Non e' un problema di taratura:** il veto conta *se il DVOL
|
||||||
|
c'era*, non *quanto dura la barra su cui c'era* — **assi ortogonali**. La stessa soglia 0,80 applicata
|
||||||
|
alla quota di barre **chiuse** leggerebbe **0%** e vieterebbe. **Variante nuova di una regola vecchia:
|
||||||
|
una barra presente non e' una giornata presente.**
|
||||||
|
🚨 **(2) Il caso peggiore NON e' quello coi numeri piu' assurdi.** XSR01 (|Sh| ×6,6) e DVOLSPREAD
|
||||||
|
(×3,0) dicono **lo stesso verdetto** in entrambe le lenti; **STATARB ha |Sharpe| quasi identico
|
||||||
|
(1,96 contro 2,02) e cambia SEGNO** — ed e' il monitor del gate **piu' vicino**. Un controllo che
|
||||||
|
cercasse *"numeri troppo belli"* **lo lascerebbe passare**. 📌 **La firma da cercare non e' il
|
||||||
|
|Sharpe| ma la vol forward molto sotto quella del backtest** (7,88% contro 31,42%).
|
||||||
|
📌 **(3) L'asimmetria del 25/07 su STATARB:** la diagnostica pre-registrata (statica sempre-short)
|
||||||
|
**non e' affetta** — si ricalcola dal **pannello**, non dalla serie registrata (varia di −0,01 fra le
|
||||||
|
due finestre contro −3,97 della strategia). **Al gate si confronterebbe un benchmark GIUSTO con un
|
||||||
|
numero di strategia SBAGLIATO**, e il margine sembrerebbe **+5,83 invece di +1,87**. Il segno del
|
||||||
|
margine sopravvive: il difetto ribalta il gate, non questa diagnostica.
|
||||||
|
|
||||||
|
📌 **QUANTO COSTA ASPETTARE — l'unico argomento quantitativo su *quando* riparare.** Con
|
||||||
|
**rigenerazione**: **zero barre perse a qualunque data**; si paga solo il rischio che la riparazione
|
||||||
|
non arrivi. **Senza**: riparare **oggi** lascia **36/90** barre vere a STATARB (62/90 a XSR01, 63/91 a
|
||||||
|
DVOLSPREAD); **alla vigilia, 1**. E la riparazione parziale **non contamina la finestra, la accorcia**
|
||||||
|
— una barra troncata porta ~1/370 della varianza *e* del rendimento di una vera, quindi non pesa:
|
||||||
|
riparando oggi, il 27/09 si leggerebbe una finestra **piu' corta ma VERA** (SE(Sharpe) ~3,2 invece di
|
||||||
|
~2,0). **Riparare alla vigilia no.**
|
||||||
|
|
||||||
|
⚠️ `implausible_sharpe` **girato per la prima volta sulle serie forward**: chiede n≥30, quindi **2
|
||||||
|
serie su 3 non sono giudicabili**, e su statarb dice "ok" **in entrambe le lenti** — **il gate non
|
||||||
|
vede un segno ribaltato**.
|
||||||
|
|||||||
@@ -0,0 +1,719 @@
|
|||||||
|
"""GATE-RECON — cosa direbbero i tre gate pre-registrati se le serie forward fossero giuste.
|
||||||
|
|
||||||
|
IL FATTO DA CUI NASCE (filone MONITOR-AUDIT, 22/08, gia' misurato e NON riparato per scelta).
|
||||||
|
`fetch_hyperliquid` e `resample_tf` scrivono la barra del giorno IN CORSO; `advance()` la consuma
|
||||||
|
e porta `last_ts` su di essa -> le ore restanti non entrano in nessun rendimento. Quattro monitor
|
||||||
|
su sei registrano una FRAZIONE del giorno (2, 4, 19 e 41 minuti su 1440). Tre decisioni
|
||||||
|
pre-registrate leggono serie costruite cosi':
|
||||||
|
STATARB 2026-09-27 r0724_statarb_deploy_gate (Sh>=0.5, maxDD<10%, haircut<0.5pp)
|
||||||
|
XSR01 2026-10-23 r0725_xsr_deploy_gate (Sh>=1.0 E haircut$5000<=40%)
|
||||||
|
DVOLSPREAD 2026-10-24 kill / 2027-01-24 decisione (soglie dentro paper_dvolspread)
|
||||||
|
|
||||||
|
QUESTO SCRIPT NON RIPARA E NON DECIDE. La riparazione e' una decisione dell'operatore e i gate
|
||||||
|
hanno una data: anticiparli sarebbe selezione-sul-forward, il bias che questo progetto ha gia'
|
||||||
|
pagato due volte. La consegna e' un DELTA, non un verdetto:
|
||||||
|
"alla data di oggi, con la serie riparata il criterio direbbe Y; con la serie com'e', Z"
|
||||||
|
e la distanza fra Y e Z e' la misura del danno del difetto — l'unica cosa che serve per decidere
|
||||||
|
SE e QUANDO riparare. Sola lettura su tutto cio' che e' produzione: i moduli di `scripts/live/`
|
||||||
|
si importano senza chiamarne `main()`, e `_append` viene sostituito con un raccoglitore in memoria
|
||||||
|
PRIMA di qualunque chiamata ad `advance()` -> nessun file di `data/` viene aperto in scrittura.
|
||||||
|
|
||||||
|
METODO. La serie RIPARATA non e' una reimplementazione: e' il codice di produzione rieseguito
|
||||||
|
dall'inception sui dati di oggi, TOLTA l'ultima barra (l'unica ancora aperta). Tutto il resto del
|
||||||
|
pannello e' fatto di barre chiuse, quindi il replay E' la serie che il monitor avrebbe registrato
|
||||||
|
se avesse aspettato la chiusura. Questo poggia su due condizioni, entrambe verificate qui:
|
||||||
|
(P1) il feed non riscrive le barre gia' chiuse -> sezione 0;
|
||||||
|
(P2) il pannello che leggo ora e' quello che il cron ha letto stanotte, e troncare l'ULTIMA
|
||||||
|
barra non cambia nessuna barra precedente -> sezione 1 (tre prove d'identita').
|
||||||
|
Senza (P2) starei ricostruendo una strategia diversa invece della stessa strategia riparata, ed e'
|
||||||
|
il modo piu' facile di sbagliare questo lavoro.
|
||||||
|
|
||||||
|
COSA MI ASPETTAVO PRIMA DI MISURARE (registrato qui perche' sia falsificabile):
|
||||||
|
* la premessa (P1) vera ma verificabile solo di traverso -> **sbagliato, ed e' la sorpresa
|
||||||
|
utile**: `data/_feed_backup/*.prebuild.bak` conserva il vintage PRE-riscrittura di stanotte,
|
||||||
|
quindi la premessa si verifica DIRETTAMENTE su ~914.000 barre, senza passare da una strategia;
|
||||||
|
* i tre gate tutti ribaltati -> **sbagliato, e la ragione conta**: solo STATARB si ribalta.
|
||||||
|
XSR01 e DVOLSPREAD dicono "ritiro/kill" su ENTRAMBE le lenti, ma con numeri che differiscono
|
||||||
|
di 3-7x e che a 28 barre non sono letture di gate su nessuna delle due;
|
||||||
|
* il veto d'integrita' di DVOLSPREAD (l'unico dei tre che ha una guardia) capace di accorgersene
|
||||||
|
-> **confermato che NON se ne accorge**, e per una ragione strutturale: conta se il DVOL C'ERA,
|
||||||
|
non quanto dura la barra su cui c'era.
|
||||||
|
|
||||||
|
nice -n 19 timeout 900 uv run python scripts/research/r0822c_gate_recon.py
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import importlib.util
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from datetime import date
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
RAW = ROOT / "data" / "raw"
|
||||||
|
BAK = ROOT / "data" / "_feed_backup"
|
||||||
|
for _p in (ROOT, ROOT / "scripts" / "research", ROOT / "scripts" / "research" / "alt",
|
||||||
|
ROOT / "scripts" / "research" / "ortho"):
|
||||||
|
sys.path.insert(0, str(_p))
|
||||||
|
|
||||||
|
ANN = np.sqrt(365.0)
|
||||||
|
TODAY = date.today()
|
||||||
|
H = "=" * 100
|
||||||
|
# Incidente del feed 2026-07-09 -> 2026-07-15 (`rebuild_history` abortito 7 giri, EPERM sul backup,
|
||||||
|
# gia' riparato): col feed fermo i monitor non trovavano barre nuove e al ripristino ne hanno
|
||||||
|
# recuperate in blocco, GIA' CHIUSE. Sono le uniche barre corrette di `paper_statarb` e vanno
|
||||||
|
# escluse da ogni stima del difetto, o gli si attribuisce un'attenuazione che viene da altro.
|
||||||
|
FREEZE = (pd.Timestamp("2026-07-09", tz="UTC"), pd.Timestamp("2026-07-16", tz="UTC"))
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------------------------
|
||||||
|
# infrastruttura di sola lettura (stessa di r0822_monitor_audit: nessuna reimplementazione)
|
||||||
|
# --------------------------------------------------------------------------------------------
|
||||||
|
_MODS: dict[str, object] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def live(name: str):
|
||||||
|
"""Esegue un modulo di `scripts/live/` per averne le DEFINIZIONI. Mai `main()`."""
|
||||||
|
if name in _MODS:
|
||||||
|
return _MODS[name]
|
||||||
|
spec = importlib.util.spec_from_file_location(f"_ro_{name}", ROOT / "scripts" / "live" / f"{name}.py")
|
||||||
|
m = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(m)
|
||||||
|
_MODS[name] = m
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
class Sink:
|
||||||
|
"""Sostituisce `_append`: raccoglie in memoria invece di scrivere. E' l'unica ragione per cui
|
||||||
|
si puo' rieseguire il codice di produzione TALE E QUALE restando in sola lettura."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.rows: list[tuple[str, dict]] = []
|
||||||
|
|
||||||
|
def __call__(self, path, rec):
|
||||||
|
self.rows.append((Path(path).name, dict(rec)))
|
||||||
|
|
||||||
|
def of(self, fname: str) -> list[dict]:
|
||||||
|
return [r for n, r in self.rows if n == fname]
|
||||||
|
|
||||||
|
|
||||||
|
def read_jsonl(p: Path) -> list[dict]:
|
||||||
|
return [json.loads(x) for x in p.read_text().splitlines() if x.strip()] if p.exists() else []
|
||||||
|
|
||||||
|
|
||||||
|
def mets(a) -> dict:
|
||||||
|
"""Sharpe (365), maxDD e ritorno cumulato di una serie di rendimenti netti per barra."""
|
||||||
|
a = np.asarray(a, float)
|
||||||
|
a = a[np.isfinite(a)]
|
||||||
|
if len(a) < 2 or a.std() == 0:
|
||||||
|
return dict(n=len(a), sh=float("nan"), dd=float("nan"), tot=float("nan"),
|
||||||
|
mu=float("nan"), sd=float("nan"))
|
||||||
|
eq = np.cumprod(1.0 + a)
|
||||||
|
pk = np.maximum.accumulate(eq)
|
||||||
|
return dict(n=len(a), sh=float(a.mean() / a.std() * ANN),
|
||||||
|
dd=float(np.max((pk - eq) / pk)), tot=float(eq[-1] - 1.0),
|
||||||
|
mu=float(a.mean() * 365.0), sd=float(a.std() * ANN))
|
||||||
|
|
||||||
|
|
||||||
|
def se_sharpe(n: int) -> float:
|
||||||
|
"""Errore standard grezzo di uno Sharpe annualizzato su n barre giornaliere (~sqrt(365/n))."""
|
||||||
|
return float(np.sqrt(365.0 / max(n, 1)))
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================================
|
||||||
|
# 0 — LA PREMESSA: rigenerare non perde la finestra SOLO se le barre chiuse non vengono riscritte
|
||||||
|
# ============================================================================================
|
||||||
|
def sezione_0() -> dict:
|
||||||
|
print(H)
|
||||||
|
print(" 0 — PREMESSA: il feed riscrive le barre gia' CHIUSE?")
|
||||||
|
print(H)
|
||||||
|
print("""
|
||||||
|
Perche' e' la premessa di tutto: la raccomandazione del filone MONITOR-AUDIT e' "riparare
|
||||||
|
`advance()` + RIGENERARE", cioe' ricalcolare la finestra forward dall'inception sui dati di
|
||||||
|
oggi. Se il feed riscrivesse le barre chiuse, quella rigenerazione produrrebbe una serie
|
||||||
|
DIVERSA da quella che si sarebbe registrata giorno per giorno -> la finestra andrebbe persa e
|
||||||
|
la conclusione cambierebbe. E' stata verificata due volte di traverso (via strategie); qui si
|
||||||
|
verifica una terza volta e in modo DIRETTO.""")
|
||||||
|
|
||||||
|
out = {}
|
||||||
|
print("\n 0a. VERIFICA DIRETTA sul dato grezzo — vintage PRE-riscrittura contro oggi")
|
||||||
|
print(" `rebuild_history` copia il parquet in data/_feed_backup/*.prebuild.bak PRIMA di")
|
||||||
|
print(" sovrascriverlo: sul disco c'e' il file com'era ieri sera. Nessuna strategia in mezzo.")
|
||||||
|
tot_ch, tot_div = 0, 0
|
||||||
|
for a in ("btc", "eth"):
|
||||||
|
for tf in ("1h", "5m"):
|
||||||
|
new_p, old_p = RAW / f"{a}_{tf}.parquet", BAK / f"{a}_{tf}.parquet.prebuild.bak"
|
||||||
|
if not (new_p.exists() and old_p.exists()):
|
||||||
|
print(f" {a}_{tf}: vintage assente -> NON MISURATO")
|
||||||
|
continue
|
||||||
|
new = pd.read_parquet(new_p)
|
||||||
|
old = pd.read_parquet(old_p)
|
||||||
|
m = old.merge(new, on="timestamp", suffixes=("_o", "_n"))
|
||||||
|
cols = ["open", "high", "low", "close", "volume"]
|
||||||
|
diff = np.zeros(len(m), bool)
|
||||||
|
for c in cols:
|
||||||
|
diff |= (m[f"{c}_o"].values != m[f"{c}_n"].values)
|
||||||
|
last_old = int(old["timestamp"].max())
|
||||||
|
chiuse = m["timestamp"].values < last_old # nel vecchio file l'ultima riga era APERTA
|
||||||
|
nch, nd = int(chiuse.sum()), int((diff & chiuse).sum())
|
||||||
|
tot_ch += nch
|
||||||
|
tot_div += nd
|
||||||
|
print(f" {a}_{tf}: {nch:,} barre gia' chiuse nel vintage -> DIVERSE OGGI: {nd}"
|
||||||
|
f" (righe cambiate in tutto: {int(diff.sum())})")
|
||||||
|
if diff.sum() == 1:
|
||||||
|
i = int(np.where(diff)[0][0])
|
||||||
|
ts = pd.Timestamp(int(m["timestamp"].iloc[i]), unit="ms", tz="UTC")
|
||||||
|
print(f" l'unica riga cambiata e' {ts} = l'ULTIMA del vintage, che era "
|
||||||
|
f"aperta: close {m['close_o'].iloc[i]:g} -> {m['close_n'].iloc[i]:g}, "
|
||||||
|
f"volume {m['volume_o'].iloc[i]:,.0f} -> {m['volume_n'].iloc[i]:,.0f}")
|
||||||
|
out["chiuse"], out["div"] = tot_ch, tot_div
|
||||||
|
print(f"\n TOTALE: {tot_div} barre chiuse cambiate su {tot_ch:,} "
|
||||||
|
f"-> la riscrittura notturna e' un NO-OP sulle barre chiuse.")
|
||||||
|
print(" ⚠ questo e' UN ciclo di riscrittura (ieri->oggi). La stessa riga mostra anche il")
|
||||||
|
print(" DIFETTO: l'ultima riga del vintage era una barra parziale, ed e' esattamente")
|
||||||
|
print(" quella che i monitor consumano.")
|
||||||
|
|
||||||
|
print("\n 0b. VERIFICA su 62 CICLI — tasso di divergenza per ETA' della barra (paper_prevday)")
|
||||||
|
print(" Statistica diversa da quella del 22/08 (che contava le identiche): se il feed")
|
||||||
|
print(" riscrivesse le barre chiuse, il tasso CRESCEREBBE con l'eta' (piu' riscritture")
|
||||||
|
print(" subite). Se il difetto e' solo la barra parziale al momento della registrazione,")
|
||||||
|
print(" il tasso e' PIATTO nell'eta' e vale ~1/24.")
|
||||||
|
pp = live("paper_prevday")
|
||||||
|
st = json.loads((ROOT / "data" / "paper_prevday" / "state.json").read_text())
|
||||||
|
rec = read_jsonl(ROOT / "data" / "paper_prevday" / "returns.jsonl")
|
||||||
|
dfs = pp.build_bars()
|
||||||
|
S = st["start_ts"]
|
||||||
|
pos = {a: pp.pb.current_target(dfs[a][dfs[a]["timestamp"] <= S]) for a in pp.ASSETS}
|
||||||
|
sink = Sink()
|
||||||
|
pp._append = sink
|
||||||
|
st0 = dict(start_ts=S, last_ts=S, n_bars=0, pos_modeled=pos, pos_real=dict(pos),
|
||||||
|
cap_modeled=pp.MODELED_CAPITAL, cap_real=pp.REAL_CAPITAL,
|
||||||
|
peak_modeled=pp.MODELED_CAPITAL, peak_real=pp.REAL_CAPITAL,
|
||||||
|
dd_modeled=0.0, dd_real=0.0, n_trades=0)
|
||||||
|
pp.advance(st0, dfs)
|
||||||
|
rep = {int(r["ts"]): float(r["net_modeled"]) for r in sink.of("returns.jsonl")}
|
||||||
|
pairs = [(int(r["ts"]), float(r["net_modeled"]), rep[int(r["ts"])])
|
||||||
|
for r in rec if int(r["ts"]) in rep]
|
||||||
|
T = pd.DatetimeIndex(pd.to_datetime([p[0] for p in pairs], unit="ms", utc=True))
|
||||||
|
A = np.array([p[1] for p in pairs])
|
||||||
|
B = np.array([p[2] for p in pairs])
|
||||||
|
dv = np.abs(A - B) > 1e-6
|
||||||
|
now = pd.Timestamp.now(tz="UTC")
|
||||||
|
age = (now - T).days
|
||||||
|
d = pd.DataFrame(dict(age=age, div=dv))
|
||||||
|
d["b"] = pd.cut(d.age, [-1, 7, 14, 21, 30, 45, 60, 999],
|
||||||
|
labels=["0-7g", "8-14g", "15-21g", "22-30g", "31-45g", "46-60g", ">60g"])
|
||||||
|
g = d.groupby("b", observed=True).agg(n=("div", "size"), div=("div", "sum"))
|
||||||
|
g["tasso"] = g["div"] / g["n"]
|
||||||
|
print(f" {len(A)} barre orarie appaiate, {int(dv.sum())} divergenti "
|
||||||
|
f"({dv.mean() * 100:.2f}%; identiche {len(A) - int(dv.sum())}/{len(A)})")
|
||||||
|
print(" " + " ".join(f"{ix}:{r.tasso * 100:4.1f}%" for ix, r in g.iterrows()))
|
||||||
|
hh = pd.Series(T.hour[dv]).value_counts().sort_index()
|
||||||
|
print(" ora UTC delle divergenti: " + ", ".join(f"{int(h):02d}:00 x{int(n)}" for h, n in hh.items())
|
||||||
|
+ " <- il cron gira alle 00:30")
|
||||||
|
out["prevday_rate_flat"] = (float(g["tasso"].min()), float(g["tasso"].max()))
|
||||||
|
|
||||||
|
print("\n 0c. LIMITE DICHIARATO — cosa NON e' verificato direttamente")
|
||||||
|
print(" Il vintage prebuild esiste solo per BTC/ETH (lo scrive `rebuild_history`).")
|
||||||
|
print(" `hl_*_1d.parquet` (che alimenta XSR01) e `dvol_*.parquet` NON hanno vintage su")
|
||||||
|
print(" disco: per loro l'evidenza resta indiretta (le barre che i monitor hanno letto")
|
||||||
|
print(" gia' chiuse coincidono al bit dopo settimane di riscritture — sezione 1).")
|
||||||
|
print("\n -> PREMESSA CONFERMATA sul feed BTC/ETH, per via diretta e su 62 cicli.")
|
||||||
|
print(" Rigenerare NON perde la finestra forward; nessuna data di gate si sposta.")
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================================
|
||||||
|
# 1 — RICOSTRUZIONE DELLE TRE SERIE + PROVE D'IDENTITA'
|
||||||
|
# ============================================================================================
|
||||||
|
def _ident_pannello(nome: str, salvato, calcolato) -> float:
|
||||||
|
d = float(np.max(np.abs(np.asarray(salvato, float) - np.asarray(calcolato, float))))
|
||||||
|
print(f" I1 pannello == quello del cron di stanotte ({nome}): max|d| = {d:g}"
|
||||||
|
f" {'OK' if d == 0.0 else '*** DIVERSO ***'}")
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def ric_statarb() -> dict:
|
||||||
|
print("\n 1a. paper_statarb — gate STATARB 2026-09-27")
|
||||||
|
ps = live("paper_statarb")
|
||||||
|
st = json.loads((ROOT / "data" / "paper_statarb" / "state.json").read_text())
|
||||||
|
rec = read_jsonl(ROOT / "data" / "paper_statarb" / "returns.jsonl")
|
||||||
|
j = ps.build_joint("1d")
|
||||||
|
ts, dt, pos, sr = ps._signal(j)
|
||||||
|
_ident_pannello("pos_modeled", [st["pos_modeled"]], [pos[-1]])
|
||||||
|
i0 = int(np.where(ts == st["start_ts"])[0][0])
|
||||||
|
|
||||||
|
def replay(frame):
|
||||||
|
sink = Sink()
|
||||||
|
ps._append = sink
|
||||||
|
t2, _, p2, _ = ps._signal(frame)
|
||||||
|
k = int(np.where(t2 == st["start_ts"])[0][0])
|
||||||
|
s0 = dict(start_ts=st["start_ts"], last_ts=st["start_ts"], n_bars=0,
|
||||||
|
pos_modeled=float(p2[k]), pos_real=float(p2[k]),
|
||||||
|
cap_modeled=ps.MODELED_CAPITAL, cap_real=ps.REAL_CAPITAL,
|
||||||
|
peak_modeled=ps.MODELED_CAPITAL, peak_real=ps.REAL_CAPITAL,
|
||||||
|
dd_modeled=0.0, dd_real=0.0, n_trades=0)
|
||||||
|
ps.advance(s0, frame)
|
||||||
|
return sink.of("returns.jsonl")
|
||||||
|
|
||||||
|
full = replay(j)
|
||||||
|
trunc = replay(j.iloc[:-1].reset_index(drop=True))
|
||||||
|
a = np.array([float(r["net_modeled"]) for r in full[:-1]])
|
||||||
|
b = np.array([float(r["net_modeled"]) for r in trunc])
|
||||||
|
print(f" I3 troncare l'INPUT == affettare l'OUTPUT: n {len(a)}/{len(b)}, "
|
||||||
|
f"max|d| = {float(np.max(np.abs(a - b))):g} (l'ultima barra non retroagisce)")
|
||||||
|
return dict(mod=ps, rec=rec, rep=full, panel=j, extra=dict(pos=pos, ts=ts))
|
||||||
|
|
||||||
|
|
||||||
|
def ric_xsr() -> dict:
|
||||||
|
print("\n 1b. paper_xsr — gate XSR01 2026-10-23")
|
||||||
|
px = live("paper_xsr")
|
||||||
|
st = json.loads((ROOT / "data" / "paper_xsr" / "state.json").read_text())
|
||||||
|
rec = read_jsonl(ROOT / "data" / "paper_xsr" / "returns.jsonl")
|
||||||
|
sink = Sink()
|
||||||
|
px._append = sink
|
||||||
|
ts, dt, W, R, rb, syms = px.build_panel()
|
||||||
|
_ident_pannello("w[modeled]", st["modeled"]["w"], W[-1])
|
||||||
|
s0 = dict(start_ts=st["start_ts"], last_ts=st["start_ts"], n_bars=0, syms=st["syms"],
|
||||||
|
modeled=px._book(px.MODELED_CAPITAL, len(st["syms"])),
|
||||||
|
reals={n: px._book(c, len(st["syms"])) for c, n in px.REAL_BOOKS})
|
||||||
|
px.advance(s0)
|
||||||
|
full = sink.of("returns.jsonl")
|
||||||
|
|
||||||
|
# I3: troncare l'INPUT (l'ultima riga di OGNI parquet HL) e ricostruire da capo
|
||||||
|
orig = px.load_hl
|
||||||
|
|
||||||
|
def load_trunc(sym):
|
||||||
|
return orig(sym).iloc[:-1]
|
||||||
|
|
||||||
|
px.load_hl = load_trunc
|
||||||
|
sink2 = Sink()
|
||||||
|
px._append = sink2
|
||||||
|
try:
|
||||||
|
ts2, _, W2, _, _, syms2 = px.build_panel()
|
||||||
|
s2 = dict(start_ts=st["start_ts"], last_ts=st["start_ts"], n_bars=0, syms=st["syms"],
|
||||||
|
modeled=px._book(px.MODELED_CAPITAL, len(st["syms"])),
|
||||||
|
reals={n: px._book(c, len(st["syms"])) for c, n in px.REAL_BOOKS})
|
||||||
|
px.advance(s2)
|
||||||
|
tr = sink2.of("returns.jsonl")
|
||||||
|
a = np.array([float(r["net_modeled"]) for r in full[:-1]])
|
||||||
|
b = np.array([float(r["net_modeled"]) for r in tr])
|
||||||
|
dmax = float(np.max(np.abs(a - b))) if len(a) == len(b) else float("nan")
|
||||||
|
print(f" I3 troncare l'INPUT == affettare l'OUTPUT: n {len(a)}/{len(b)}, max|d| = {dmax:g}")
|
||||||
|
finally:
|
||||||
|
px.load_hl = orig
|
||||||
|
return dict(mod=px, rec=rec, rep=full)
|
||||||
|
|
||||||
|
|
||||||
|
def ric_dvolspread() -> dict:
|
||||||
|
print("\n 1c. paper_dvolspread — kill 2026-10-24 / decisione 2027-01-24")
|
||||||
|
pv = live("paper_dvolspread")
|
||||||
|
st = json.loads((ROOT / "data" / "paper_dvolspread" / "state.json").read_text())
|
||||||
|
rec = read_jsonl(ROOT / "data" / "paper_dvolspread" / "returns.jsonl")
|
||||||
|
P = pv._panel()
|
||||||
|
_ident_pannello("wb_modeled", [st["wb_modeled"]], [P["wb"][-1]])
|
||||||
|
i0 = int(np.where(P["ts"] == st["start_ts"])[0][0])
|
||||||
|
|
||||||
|
def replay(Q):
|
||||||
|
sink = Sink()
|
||||||
|
pv._append = sink
|
||||||
|
k = int(np.where(Q["ts"] == st["start_ts"])[0][0])
|
||||||
|
s0 = dict(start_ts=st["start_ts"], last_ts=st["start_ts"], n_bars=0, n_active=0,
|
||||||
|
n_flat_signal=0, n_flat_nodata=0,
|
||||||
|
wb_modeled=float(Q["wb"][k]), wb_real=float(Q["wb"][k]),
|
||||||
|
cap_modeled=pv.MODELED_CAPITAL, cap_real=pv.REAL_CAPITAL,
|
||||||
|
peak_modeled=pv.MODELED_CAPITAL, peak_real=pv.REAL_CAPITAL,
|
||||||
|
dd_modeled=0.0, dd_real=0.0, n_flips=0, frozen=pv.FROZEN)
|
||||||
|
out = pv.advance(s0, Q)
|
||||||
|
return sink.of("returns.jsonl"), out
|
||||||
|
|
||||||
|
full, st_full = replay(P)
|
||||||
|
Q = {k: (v[:-1] if hasattr(v, "__len__") else v) for k, v in P.items()}
|
||||||
|
trunc, st_tr = replay(Q)
|
||||||
|
a = np.array([float(r["net_modeled"]) for r in full[:-1]])
|
||||||
|
b = np.array([float(r["net_modeled"]) for r in trunc])
|
||||||
|
print(f" I3 troncare l'INPUT == affettare l'OUTPUT: n {len(a)}/{len(b)}, "
|
||||||
|
f"max|d| = {float(np.max(np.abs(a - b))):g}")
|
||||||
|
return dict(mod=pv, rec=rec, rep=full, st_rep=st_tr, st_rec=st)
|
||||||
|
|
||||||
|
|
||||||
|
def sezione_1() -> dict:
|
||||||
|
print("\n" + H)
|
||||||
|
print(" 1 — RICOSTRUZIONE + PROVE D'IDENTITA' (senza queste, il numero e' un'altra strategia)")
|
||||||
|
print(H)
|
||||||
|
print("""
|
||||||
|
Tre prove, ognuna chiude un modo diverso di sbagliare:
|
||||||
|
I1 il pannello che leggo ORA e' quello che il cron ha letto stanotte (i parquet hanno mtime
|
||||||
|
00:30-00:35 e nulla li riscrive fino al prossimo giro): si verifica contro lo `state.json`
|
||||||
|
del monitor, che e' stato scritto DA LUI alle 00:35.
|
||||||
|
I2 sulle barre che il monitor ha letto GIA' CHIUSE la ricostruzione coincide AL BIT: e' la
|
||||||
|
parte non affetta dal difetto, ed e' l'unico posto dove i due numeri devono coincidere.
|
||||||
|
I3 troncare l'ULTIMA barra dell'INPUT da' lo stesso risultato che affettare l'output: nessuna
|
||||||
|
barra precedente dipende dall'ultima (se dipendesse, la 'riparazione' sarebbe un altro
|
||||||
|
segnale, non lo stesso).""")
|
||||||
|
S = dict(statarb=ric_statarb(), xsr=ric_xsr(), dvol=ric_dvolspread())
|
||||||
|
|
||||||
|
print("\n I2 — barre che il monitor ha letto CHIUSE, e coincidenza al bit:")
|
||||||
|
for k, lab in (("statarb", "paper_statarb"), ("xsr", "paper_xsr"), ("dvol", "paper_dvolspread")):
|
||||||
|
rec, rep = S[k]["rec"], S[k]["rep"]
|
||||||
|
d = {int(r["ts"]): float(r["net_modeled"]) for r in rep}
|
||||||
|
pr = [(int(r["ts"]), float(r["net_modeled"]), d[int(r["ts"])]) for r in rec if int(r["ts"]) in d]
|
||||||
|
A = np.array([p[1] for p in pr])
|
||||||
|
B = np.array([p[2] for p in pr])
|
||||||
|
T = pd.DatetimeIndex(pd.to_datetime([p[0] for p in pr], unit="ms", utc=True))
|
||||||
|
eq = np.abs(A - B) <= 1e-6
|
||||||
|
S[k]["pairs"] = (T, A, B)
|
||||||
|
gg = sorted({str(x.date()) for x in T[eq]})
|
||||||
|
note = f" ({gg[0]} -> {gg[-1]})" if gg else ""
|
||||||
|
print(f" {lab:<18} {int(eq.sum())}/{len(A)} identiche{note}")
|
||||||
|
if k == "statarb":
|
||||||
|
print(" quelle 6 sono le barre recuperate dopo l'incidente del feed 09-15/07:")
|
||||||
|
print(" registrate il 16/07 e riprodotte oggi al bit dopo ~37 riscritture")
|
||||||
|
print(" notturne -> conferma indipendente della premessa della sezione 0.")
|
||||||
|
if k == "dvol":
|
||||||
|
print(" *** ZERO barre non affette: per questo monitor la prova I2 NON ESISTE.")
|
||||||
|
print(" Restano I1 (pannello identico, max|d|=0), I3 (troncamento innocuo) e il")
|
||||||
|
print(" fatto strutturale che il replay chiama `advance()` di produzione senza")
|
||||||
|
print(" una riga riscritta. Va detto: e' una prova piu' debole delle altre due.")
|
||||||
|
return S
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================================
|
||||||
|
# 2 — I TRE GATE, ALLA LETTERA
|
||||||
|
# ============================================================================================
|
||||||
|
def _riga(lab, m, extra=""):
|
||||||
|
print(f" {lab:<22} n={m['n']:>3} Sharpe {m['sh']:+7.2f} maxDD {m['dd'] * 100:6.2f}% "
|
||||||
|
f"ritorno {m['tot'] * 100:+6.2f}% vol {m['sd'] * 100:5.2f}%{extra}")
|
||||||
|
|
||||||
|
|
||||||
|
def gate_dvolspread(S: dict) -> tuple[str, str]:
|
||||||
|
print("\n" + H)
|
||||||
|
print(" 2a — DVOLSPREAD (kill 2026-10-24, decisione 2027-01-24) — PER PRIMO: e' l'unico dei")
|
||||||
|
print(" tre che ha gia' un VETO D'INTEGRITA', e la domanda e' se il difetto lo fa scattare")
|
||||||
|
print(H)
|
||||||
|
pv, rec, rep = S["dvol"]["mod"], S["dvol"]["rec"], S["dvol"]["rep"]
|
||||||
|
A = np.array([float(r["net_modeled"]) for r in rec])
|
||||||
|
B = np.array([float(r["net_modeled"]) for r in rep])[:-1] # RIPARATA = solo barre chiuse
|
||||||
|
mA, mB = mets(A), mets(B)
|
||||||
|
print(f"\n criterio kill (pre-registrato 26/07): Sharpe forward MODELED < {pv.KILL_SHARPE:+.2f}"
|
||||||
|
f" -> RITIRO")
|
||||||
|
_riga("serie ATTUALE", mA)
|
||||||
|
_riga("serie RIPARATA", mB)
|
||||||
|
vA = "KILL" if mA["sh"] < pv.KILL_SHARPE else "vivo"
|
||||||
|
vB = "KILL" if mB["sh"] < pv.KILL_SHARPE else "vivo"
|
||||||
|
print(f" -> criterio kill: serie attuale {vA} · serie riparata {vB} "
|
||||||
|
f"{'STESSO VERDETTO' if vA == vB else '*** VERDETTO RIBALTATO ***'}")
|
||||||
|
print(f" ⚠ ma a n={mA['n']} barre SE(Sharpe) ~ {se_sharpe(mA['n']):.2f}: nessuna delle due")
|
||||||
|
print(" e' una lettura di gate, e la data del kill e' fra "
|
||||||
|
f"{(date(2026, 10, 24) - TODAY).days} giorni.")
|
||||||
|
|
||||||
|
print("\n IL VETO D'INTEGRITA' (`n_active + n_flat_signal >= 80% delle barre`):")
|
||||||
|
stA, stB = S["dvol"]["st_rec"], S["dvol"]["st_rep"]
|
||||||
|
fA = (stA["n_active"] + stA["n_flat_signal"]) / max(stA["n_bars"], 1)
|
||||||
|
fB = (stB["n_active"] + stB["n_flat_signal"]) / max(stB["n_bars"], 1)
|
||||||
|
print(f" serie ATTUALE : attive {stA['n_active']} · flat-da-segnale {stA['n_flat_signal']}"
|
||||||
|
f" · flat-SENZA-DATO {stA['n_flat_nodata']} -> valide {fA:.0%} "
|
||||||
|
f"(soglia {pv.MIN_ACTIVE_FRAC:.0%}) -> {'VETO' if fA < pv.MIN_ACTIVE_FRAC else 'PASSA'}")
|
||||||
|
print(f" serie RIPARATA: attive {stB['n_active']} · flat-da-segnale {stB['n_flat_signal']}"
|
||||||
|
f" · flat-SENZA-DATO {stB['n_flat_nodata']} -> valide {fB:.0%} -> "
|
||||||
|
f"{'VETO' if fB < pv.MIN_ACTIVE_FRAC else 'PASSA'}")
|
||||||
|
print("""
|
||||||
|
📌 IL RISULTATO E' SUL VETO, NON SUL MONITOR. Una serie che misura DUE MINUTI di mercato al
|
||||||
|
giorno passa al 100% un veto d'integrita' progettato apposta per non far decidere su dati
|
||||||
|
mancanti. Non e' un difetto di taratura: e' che il veto e' costruito sulla domanda giusta
|
||||||
|
per un altro guasto. Conta *se il DVOL c'era* (e c'era, 28/28) e non puo' vedere *quanto
|
||||||
|
dura la barra su cui c'era* — sono due assi ortogonali. La stessa soglia 0.80 applicata alla
|
||||||
|
quota di barre CHIUSE leggerebbe 0/28 = 0% e vieterebbe la decisione.
|
||||||
|
E' la stessa forma gia' pagata dal progetto tre volte: 'una riga presente non e' un dato
|
||||||
|
presente'. Qui la variante nuova e': *una barra presente non e' una GIORNATA presente*.""")
|
||||||
|
|
||||||
|
print("\n criteri (b) marginale ADDS / (c) deflated-Sharpe / (d) weights_tilt_null: NON GIRATI,")
|
||||||
|
print(" e il motivo e' una misura, non pigrizia: si valutano sul campione ESTESO (in-sample")
|
||||||
|
print(f" attivo 1949 barre + forward). Oggi il forward pesa {mA['n']}/{1949 + mA['n']} = "
|
||||||
|
f"{100 * mA['n'] / (1949 + mA['n']):.1f}% del campione; alla decisione del 24/01/2027 ne")
|
||||||
|
print(" pesera' ~8%. -> il difetto NON puo' ribaltare (c): puo' ribaltare solo (a), che e'")
|
||||||
|
print(" calcolato sulla sola finestra forward ed e' interamente determinato da lui.")
|
||||||
|
return vA, vB
|
||||||
|
|
||||||
|
|
||||||
|
def gate_statarb(S: dict) -> tuple[str, str]:
|
||||||
|
print("\n" + H)
|
||||||
|
print(" 2b — STATARB (decisione 2026-09-27) — il gate su cui il difetto MORDE")
|
||||||
|
print(H)
|
||||||
|
rec, rep = S["statarb"]["rec"], S["statarb"]["rep"]
|
||||||
|
gate = importlib.import_module("r0724_statarb_deploy_gate")
|
||||||
|
A = np.array([float(r["net_modeled"]) for r in rec])
|
||||||
|
Ar = np.array([float(r["net_real"]) for r in rec])
|
||||||
|
B = np.array([float(r["net_modeled"]) for r in rep])[:-1]
|
||||||
|
Br = np.array([float(r["net_real"]) for r in rep])[:-1]
|
||||||
|
mA, mB = mets(A), mets(B)
|
||||||
|
hc = lambda m, r: abs(float((np.prod(1 + m) - np.prod(1 + r)) * 100))
|
||||||
|
hA, hB = hc(A, Ar), hc(B, Br)
|
||||||
|
print(f"\n soglie pre-registrate 24/07: Sharpe >= {gate.SH_DEPLOY} · maxDD < {gate.DD_MAX:.0%}"
|
||||||
|
f" · fill-haircut < {gate.HAIRCUT_MAX_PP} pp")
|
||||||
|
_riga("serie ATTUALE", mA, f" haircut {hA:.3f} pp")
|
||||||
|
_riga("serie RIPARATA", mB, f" haircut {hB:.3f} pp")
|
||||||
|
|
||||||
|
def verdetto(sh, dd, h):
|
||||||
|
if sh >= gate.SH_DEPLOY and dd < gate.DD_MAX and h < gate.HAIRCUT_MAX_PP:
|
||||||
|
return "CANDIDATO AL DEPLOY"
|
||||||
|
if sh >= 0:
|
||||||
|
return "ESTENSIONE (unica, al 2026-12-26)"
|
||||||
|
return "RITIRO"
|
||||||
|
vA, vB = verdetto(mA["sh"], mA["dd"], hA), verdetto(mB["sh"], mB["dd"], hB)
|
||||||
|
for lab, sh, dd, h, v in (("ATTUALE", mA["sh"], mA["dd"], hA, vA),
|
||||||
|
("RIPARATA", mB["sh"], mB["dd"], hB, vB)):
|
||||||
|
print(f" {lab:<9} Sharpe>={gate.SH_DEPLOY}: {'SI' if sh >= gate.SH_DEPLOY else 'NO':<3}"
|
||||||
|
f" | maxDD<{gate.DD_MAX:.0%}: {'SI' if dd < gate.DD_MAX else 'NO':<3}"
|
||||||
|
f" | haircut<{gate.HAIRCUT_MAX_PP}pp: {'SI' if h < gate.HAIRCUT_MAX_PP else 'NO':<3}"
|
||||||
|
f" -> {v}")
|
||||||
|
print(f"\n -> DUE CRITERI SU TRE SI RIBALTANO. Sharpe {mA['sh']:+.2f} -> {mB['sh']:+.2f} "
|
||||||
|
f"(attraversa 0 e la soglia 0.5); maxDD {mA['dd']:.1%} -> {mB['dd']:.1%} (attraversa la")
|
||||||
|
print(" guardia del 10%). Il terzo (haircut) non si ribalta e non poteva: e' un")
|
||||||
|
print(" rapporto fra due libri che subiscono lo STESSO troncamento.")
|
||||||
|
|
||||||
|
print("\n DIAGNOSTICA PRE-REGISTRATA IL 25/07 (statica sempre-short a pari vol-target):")
|
||||||
|
bA = gate._bench_static_short(rec)
|
||||||
|
bB = gate._bench_static_short(rep[:-1])
|
||||||
|
if bA and bB:
|
||||||
|
print(f" benchmark sulla finestra ATTUALE : {bA[0]:+.2f} su {bA[1]} barre")
|
||||||
|
print(f" benchmark sulla finestra RIPARATA: {bB[0]:+.2f} su {bB[1]} barre")
|
||||||
|
print(f" STATARB - benchmark: attuale {mA['sh'] - bA[0]:+.2f} "
|
||||||
|
f"riparata {mB['sh'] - bB[0]:+.2f}")
|
||||||
|
print("""
|
||||||
|
📌 ASIMMETRIA CHE VALE PIU' DEI NUMERI: il benchmark NON e' affetto dal difetto — e'
|
||||||
|
ricalcolato dal pannello, non letto dalla serie registrata (varia di %+.2f fra le due
|
||||||
|
finestre, contro %+.2f della strategia). Alla data del gate l'operatore metterebbe quindi
|
||||||
|
a confronto un benchmark GIUSTO con un numero di strategia SBAGLIATO, e la diagnostica
|
||||||
|
sembrerebbe piu' favorevole di quanto sia: +%.2f invece di +%.2f di margine.
|
||||||
|
Il segno del margine sopravvive comunque (STATARB batte la statica in entrambe le lenti):
|
||||||
|
cio' che il difetto ribalta e' il gate, non questa diagnostica.""" %
|
||||||
|
(bB[0] - bA[0], mB["sh"] - mA["sh"], mA["sh"] - bA[0], mB["sh"] - bB[0]))
|
||||||
|
else:
|
||||||
|
print(" benchmark NON calcolabile -> non girato.")
|
||||||
|
return vA, vB
|
||||||
|
|
||||||
|
|
||||||
|
def gate_xsr(S: dict) -> tuple[str, str]:
|
||||||
|
print("\n" + H)
|
||||||
|
print(" 2c — XSR01 (decisione 2026-10-23)")
|
||||||
|
print(H)
|
||||||
|
rec, rep = S["xsr"]["rec"], S["xsr"]["rep"]
|
||||||
|
gate = importlib.import_module("r0725_xsr_deploy_gate")
|
||||||
|
k5 = "net_REAL-$5000"
|
||||||
|
A = np.array([float(r["net_modeled"]) for r in rec])
|
||||||
|
A5 = np.array([float(r[k5]) for r in rec])
|
||||||
|
B = np.array([float(r["net_modeled"]) for r in rep])[:-1]
|
||||||
|
B5 = np.array([float(r[k5]) for r in rep])[:-1]
|
||||||
|
mA, mB = mets(A), mets(B)
|
||||||
|
|
||||||
|
def haircut(m, r5):
|
||||||
|
tm = float(np.prod(1 + m) - 1)
|
||||||
|
t5 = float(np.prod(1 + r5) - 1)
|
||||||
|
return (tm - t5) / abs(tm) if tm != 0 else float("nan")
|
||||||
|
hA, hB = haircut(A, A5), haircut(B, B5)
|
||||||
|
print(f"\n soglie pre-registrate 25/07: Sharpe >= {gate.SH_DEPLOY} E haircut$5000 <= "
|
||||||
|
f"{gate.HAIRCUT_MAX:.0%} · ritiro sotto Sharpe {gate.SH_RETIRE}")
|
||||||
|
print(f" ⚠ se l'haircut sfonda -> RITIRO a prescindere dallo Sharpe (regola esplicita)")
|
||||||
|
_riga("serie ATTUALE", mA, f" haircut$5000 {hA * 100:+.1f}%")
|
||||||
|
_riga("serie RIPARATA", mB, f" haircut$5000 {hB * 100:+.1f}%")
|
||||||
|
|
||||||
|
def verdetto(sh, h):
|
||||||
|
if np.isfinite(h) and h > gate.HAIRCUT_MAX:
|
||||||
|
return "RITIRO (haircut)"
|
||||||
|
if sh >= gate.SH_DEPLOY:
|
||||||
|
return "CANDIDATO SLEEVE"
|
||||||
|
if sh >= gate.SH_RETIRE:
|
||||||
|
return "ESTENSIONE (unica, al 2027-01-21)"
|
||||||
|
return "RITIRO"
|
||||||
|
vA, vB = verdetto(mA["sh"], hA), verdetto(mB["sh"], hB)
|
||||||
|
print(f" ATTUALE -> {vA}\n RIPARATA -> {vB} "
|
||||||
|
f"{'STESSO VERDETTO' if vA == vB else '*** VERDETTO RIBALTATO ***'}")
|
||||||
|
print(f"\n -> il verdetto NON si ribalta, ma per un caso: entrambe stanno sotto "
|
||||||
|
f"{gate.SH_RETIRE}. Il")
|
||||||
|
print(f" numero pero' cambia di {abs(mA['sh'] / mB['sh']):.1f}x ({mA['sh']:+.2f} contro "
|
||||||
|
f"{mB['sh']:+.2f}), e con SE ~ {se_sharpe(mA['n']):.1f} nessuno dei due decide niente.")
|
||||||
|
print(" L'haircut e' l'unico criterio robusto al difetto (rapporto fra libri troncati")
|
||||||
|
print(" allo stesso modo) ed e' lontanissimo dalla guardia in entrambe le lenti.")
|
||||||
|
return vA, vB
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================================
|
||||||
|
# 3 — IL MECCANISMO: perche' il troncamento non e' rumore
|
||||||
|
# ============================================================================================
|
||||||
|
def sezione_3(S: dict) -> None:
|
||||||
|
print("\n" + H)
|
||||||
|
print(" 3 — PERCHE' IL DIFETTO NON E' RUMORE: comprime la VOL piu' della MEDIA")
|
||||||
|
print(H)
|
||||||
|
print("""
|
||||||
|
Un troncamento a k minuti su 1440 scala la media del rendimento circa come k e la deviazione
|
||||||
|
standard come sqrt(k) (random walk). Quindi il rapporto media/sd — cioe' lo Sharpe — viene
|
||||||
|
MOLTIPLICATO per ~sqrt(k/1440)... in valore assoluto no: il numeratore e' la media del pezzo
|
||||||
|
troncato, che non ha lo stesso SEGNO della media della giornata intera. Risultato: la serie
|
||||||
|
registrata produce letture di Sharpe piu' ESTREME e in una direzione che non e' quella vera.
|
||||||
|
Si vede in tabella: la vol registrata e' una frazione di quella vera, ma il |Sharpe| non e' piu'
|
||||||
|
piccolo — in due casi su tre e' molto piu' GRANDE.""")
|
||||||
|
print(f"\n {'monitor':<20} {'vol REG':>9} {'vol RIP':>9} {'min/giorno':>11} "
|
||||||
|
f"{'Sh REG':>8} {'Sh RIP':>8} nota")
|
||||||
|
for k, lab in (("statarb", "paper_statarb"), ("xsr", "paper_xsr"), ("dvol", "paper_dvolspread")):
|
||||||
|
T, A, B = S[k]["pairs"]
|
||||||
|
Bc = B[:-1] if len(B) == len(A) else B
|
||||||
|
# ⚠ l'INTERA finestra dell'incidente del feed (09-16/07) va esclusa dalla stima dei
|
||||||
|
# minuti/giorno, non le sole barre risultate identiche: durante il blocco il monitor non
|
||||||
|
# trovava barre nuove e al ripristino ne ha recuperate 7 in un colpo, alcune solo
|
||||||
|
# PARZIALMENTE riallineate. Escludendo le sole identiche la stima esce 4x piu' alta
|
||||||
|
# (16,7 invece di 3,9 min/giorno su statarb) — e' la stessa maschera del 22/08.
|
||||||
|
keep = ~((T >= FREEZE[0]) & (T <= FREEZE[1]))
|
||||||
|
mA, mB = mets(A), mets(Bc)
|
||||||
|
kA, kB = mets(A[keep]), mets(B[:len(A)][keep])
|
||||||
|
minuti = (kA["sd"] / kB["sd"]) ** 2 * 1440.0
|
||||||
|
nota = ("SEGNO OPPOSTO" if np.sign(mA["sh"]) != np.sign(mB["sh"])
|
||||||
|
else f"|Sh| x{abs(mA['sh']) / abs(mB['sh']):.1f}")
|
||||||
|
print(f" {lab:<20} {mA['sd'] * 100:8.2f}% {mB['sd'] * 100:8.2f}% {minuti:10.1f} "
|
||||||
|
f"{mA['sh']:+8.2f} {mB['sh']:+8.2f} {nota}"
|
||||||
|
+ (f" ({int((~keep).sum())} barre dell'incidente escluse)" if (~keep).any() else ""))
|
||||||
|
print("""
|
||||||
|
Il caso peggiore NON e' quello col |Sharpe| piu' gonfiato: e' `paper_statarb`, dove il
|
||||||
|
|Sharpe| e' quasi identico (1,96 contro 2,02) e cambia il SEGNO. Un controllo che cercasse
|
||||||
|
"numeri troppo belli" lo lascerebbe passare senza una parola: e' proprio il monitor che
|
||||||
|
alimenta il gate piu' vicino.
|
||||||
|
-> la firma da cercare non e' un |Sharpe| alto ma una VOL FORWARD molto sotto quella del
|
||||||
|
backtest: 7,9% contro ~31%, 0,5% contro ~2,8%, 0,8% contro ~19%.
|
||||||
|
⚠ i "min/giorno" sono una LETTURA del rapporto di varianza (assume random walk
|
||||||
|
sull'intervallo troncato), non un cronometro, e sono sensibili alla maschera: su statarb
|
||||||
|
3,9 escludendo l'incidente del feed, 16,7 escludendo le sole barre identiche, 92
|
||||||
|
includendo tutto. Si cita la prima, che e' quella del 22/08; l'ordine di grandezza e'
|
||||||
|
quello, la cifra no.
|
||||||
|
✅ Replica indipendente del filone MONITOR-AUDIT: 3,9 / 41,2 / 2,5 contro i 4 / 41 / 2
|
||||||
|
pubblicati stamattina, per un percorso scritto separatamente.""")
|
||||||
|
|
||||||
|
print("\n `implausible_sharpe` puntato sulle serie forward (mai fatto — nota del 22/08).")
|
||||||
|
print(" ⚠ il gate chiede n>=30: due serie forward su tre NON sono giudicabili, e questo")
|
||||||
|
print(" e' esso stesso un risultato — il gate esiste dal 26/07 ma non puo' sorvegliare")
|
||||||
|
print(" una finestra forward giovane, che e' esattamente quando serve.")
|
||||||
|
import altlib as al
|
||||||
|
for k, lab in (("statarb", "paper_statarb"), ("xsr", "paper_xsr"), ("dvol", "paper_dvolspread")):
|
||||||
|
T, A, B = S[k]["pairs"]
|
||||||
|
for nome, v in (("REGISTRATA", A), ("RIPARATA", B[:-1] if len(B) == len(A) else B)):
|
||||||
|
idx = T[:len(v)]
|
||||||
|
try:
|
||||||
|
r = al.implausible_sharpe(pd.Series(v, index=idx))
|
||||||
|
corto = any("troppo corto" in x for x in r["reasons"])
|
||||||
|
flag = ("SEGNALA" if r["implausible"]
|
||||||
|
else "n/giudic." if corto else "ok")
|
||||||
|
print(f" {lab:<18} {nome:<11} {flag:<10} {'; '.join(r['reasons'])[:66]}")
|
||||||
|
except Exception as e: # noqa: BLE001
|
||||||
|
print(f" {lab:<18} {nome:<11} non girato ({type(e).__name__})")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================================
|
||||||
|
# 4 — QUANTO COSTA ASPETTARE
|
||||||
|
# ============================================================================================
|
||||||
|
GATES = (
|
||||||
|
("STATARB", "statarb", date(2026, 9, 27)),
|
||||||
|
("XSR01", "xsr", date(2026, 10, 23)),
|
||||||
|
("DVOLSPREAD", "dvol", date(2026, 10, 24)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sezione_4(S: dict) -> None:
|
||||||
|
print("\n" + H)
|
||||||
|
print(" 4 — QUANTO COSTA ASPETTARE (l'unico argomento quantitativo su QUANDO riparare)")
|
||||||
|
print(H)
|
||||||
|
print("""
|
||||||
|
Due scenari, e la differenza fra loro e' tutto il contenuto della sezione:
|
||||||
|
RIPARA+RIGENERA : si corregge `advance()` e si RICALCOLA la finestra dall'inception.
|
||||||
|
Possibile perche' le barre chiuse non vengono riscritte (sezione 0).
|
||||||
|
RIPARA IN AVANTI : si corregge `advance()` e basta. Le barre gia' scritte restano sbagliate.""")
|
||||||
|
print(f"\n {'gate':<12} {'data':<12} {'barre al gate':>14} {'oggi':>6} "
|
||||||
|
f"{'corrette se si ripara OGGI':>28} {'se si ripara alla vigilia':>27}")
|
||||||
|
for nome, key, gd in GATES:
|
||||||
|
rec = S[key]["rec"]
|
||||||
|
first = pd.Timestamp(rec[0]["dt"][:10]).date()
|
||||||
|
n_gate = (gd - first).days + 1
|
||||||
|
n_oggi = len(rec)
|
||||||
|
print(f" {nome:<12} {str(gd):<12} {n_gate:>14} {n_oggi:>6} "
|
||||||
|
f"{f'{n_gate - n_oggi} ({(n_gate - n_oggi) / n_gate:.0%})':>28} "
|
||||||
|
f"{f'1 ({1 / n_gate:.0%})':>27}")
|
||||||
|
print("\n Con RIGENERAZIONE la colonna giusta e' un'altra: 100% a QUALUNQUE data di")
|
||||||
|
print(" riparazione. Il costo dell'attesa e' ZERO barre — cio' che si paga aspettando non")
|
||||||
|
print(" e' dato perso ma il RISCHIO che la riparazione non arrivi prima del gate.")
|
||||||
|
print(" Senza rigenerazione, invece, riparare oggi lascia comunque sbagliata la maggioranza")
|
||||||
|
print(" della finestra di STATARB, e riparare alla vigilia non serve a niente.")
|
||||||
|
|
||||||
|
print("\n SENSIBILITA' — quanto deve essere contaminata la finestra perche' il verdetto")
|
||||||
|
print(" cambi. Misurata sui dati di OGGI, non estrapolata: si costruisce la serie mista")
|
||||||
|
print(" `registrate[:k] + riparate[k:]` (= riparazione in avanti dal giorno k) e si legge")
|
||||||
|
print(" il criterio. k=0 e' la rigenerazione completa, k=n e' la serie di oggi.")
|
||||||
|
gate = importlib.import_module("r0724_statarb_deploy_gate")
|
||||||
|
rec, rep = S["statarb"]["rec"], S["statarb"]["rep"]
|
||||||
|
A = np.array([float(r["net_modeled"]) for r in rec])[:-1]
|
||||||
|
B = np.array([float(r["net_modeled"]) for r in rep])[:-1]
|
||||||
|
n = len(A)
|
||||||
|
print(f"\n STATARB — {n} barre chiuse; soglia deploy {gate.SH_DEPLOY}, ritiro sotto 0, "
|
||||||
|
f"guardia maxDD {gate.DD_MAX:.0%}")
|
||||||
|
print(f" {'corrette (coda)':>16} {'contaminate':>12} {'Sharpe':>8} {'maxDD':>8} verdetto")
|
||||||
|
verd = []
|
||||||
|
for k in range(0, n + 1):
|
||||||
|
m = mets(np.concatenate([A[:k], B[k:]]))
|
||||||
|
v = ("CANDIDATO" if (m["sh"] >= gate.SH_DEPLOY and m["dd"] < gate.DD_MAX)
|
||||||
|
else "ESTENSIONE" if m["sh"] >= 0 else "RITIRO")
|
||||||
|
verd.append((k, m, v))
|
||||||
|
# minimo numero di barre CORRETTE in coda che basta a leggere gia' il verdetto riparato
|
||||||
|
v_rip = verd[0][2]
|
||||||
|
min_ok = min((n - k for k, m, v in verd if v == v_rip), default=n)
|
||||||
|
for k, m, v in verd:
|
||||||
|
if k in (0, n // 4, n // 2, (3 * n) // 4, n - min_ok, n - min_ok + 1, n):
|
||||||
|
print(f" {n - k:>16} {f'{k} ({k / n:.0%})':>12} {m['sh']:+8.2f} "
|
||||||
|
f"{m['dd'] * 100:7.2f}% {v}")
|
||||||
|
n_res = 90 - len(S["statarb"]["rec"]) # barre che restano da qui al gate del 27/09
|
||||||
|
print(f"""
|
||||||
|
📌 LA RIPARAZIONE PARZIALE NON E' PROPORZIONALE. Su questo campione bastano
|
||||||
|
**{min_ok} barre corrette in coda su {n}** ({min_ok / n:.0%}) perche' il criterio legga gia' il
|
||||||
|
verdetto della serie riparata. Il meccanismo e' quello della sezione 3: una barra troncata a
|
||||||
|
~4 minuti su 1440 porta ~1/370 della varianza E ~1/370 del rendimento di una barra vera
|
||||||
|
(entrambi scalano con la frazione di giornata), quindi pesa quasi nulla sia nella media sia
|
||||||
|
nella deviazione della miscela. La maggioranza NUMERICA delle barre non comanda.
|
||||||
|
Detto meglio, ed e' la formulazione onesta: riparare in avanti senza rigenerare non
|
||||||
|
CONTAMINA la finestra, la ACCORCIA — il criterio finisce per leggere la strategia sulle sole
|
||||||
|
barre corrette. Riparando oggi, al 27/09 STATARB avrebbe {n_res} barre vere su 90: una
|
||||||
|
finestra piu' corta ma VERA, con SE(Sharpe) ~{se_sharpe(n_res):.1f} invece di ~{se_sharpe(90):.1f}.
|
||||||
|
Riparare alla vigilia (1 barra) no: li' il numero resta quello di oggi.
|
||||||
|
⚠ Il {min_ok}/{n} NON e' una costante — dipende da quali barre capitano in coda, e cambia col
|
||||||
|
campione: si legge come "poche", non come una soglia. E rigenerare resta strettamente
|
||||||
|
migliore, per due cose che l'accorciamento non da': un maxDD leggibile (la miscela ha un
|
||||||
|
percorso di equity che non e' mai esistito) e una finestra rileggibile con altri criteri.""")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================================
|
||||||
|
def main() -> None:
|
||||||
|
print(H)
|
||||||
|
print(" GATE-RECON — cosa direbbero i tre gate pre-registrati con le serie riparate")
|
||||||
|
print(f" {TODAY} SOLA LETTURA su produzione (nessun file di data/ aperto in scrittura)")
|
||||||
|
print(H)
|
||||||
|
sezione_0()
|
||||||
|
S = sezione_1()
|
||||||
|
v_dv = gate_dvolspread(S)
|
||||||
|
v_st = gate_statarb(S)
|
||||||
|
v_xs = gate_xsr(S)
|
||||||
|
sezione_3(S)
|
||||||
|
sezione_4(S)
|
||||||
|
|
||||||
|
print("\n" + H)
|
||||||
|
print(" RIEPILOGO — il DELTA, non la decisione (i gate hanno una data e non si anticipano)")
|
||||||
|
print(H)
|
||||||
|
print(f" STATARB 27/09 — serie riparata: {v_st[1]:<36} · serie attuale: {v_st[0]}")
|
||||||
|
print(f" XSR01 23/10 — serie riparata: {v_xs[1]:<36} · serie attuale: {v_xs[0]}")
|
||||||
|
print(f" DVOLSPREAD 24/10 — serie riparata: {v_dv[1]:<36} · serie attuale: {v_dv[0]}")
|
||||||
|
print("\n Nessuno di questi e' il verdetto del gate: sono i criteri letti OGGI, su una finestra")
|
||||||
|
print(" ancora incompleta. Servono a una sola decisione, che e' dell'operatore: riparare")
|
||||||
|
print(" `advance()` e RIGENERARE prima del 27/09, oppure sapere che quel giorno il criterio")
|
||||||
|
print(" verra' letto su una serie che misura 4 minuti di mercato al giorno.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user