monitor: advance() consuma solo barre CHIUSE — serie rigenerate, guardia PREMATURO, 4 debiti chiusi

S5.1 RIPARATO E RIGENERATO. Filtro condiviso src/live/paper_guard.py (barra
open-labeled chiusa = ts + cadenza <= adesso) importato da tutti e 6 i monitor;
serie rigenerate dallo stesso start_ts con scripts/live/paper_regen.py (evidenza
in *.pre_regen_20260826.*): statarb +1,95 -> -1,61 (il ribaltamento del gate
27/09 previsto dall'audit), dvolspread -14,73 -> -4,41, xsr -4,98 -> -2,72,
prevday invariato. Nessuna data di gate si sposta. Guardia cablata in
monitor_health: stato PREMATURO (ultima barra che chiude dopo l'mtime, grazia
5 min, open_labeled=False per collect_chain) — sul dato vivo segnala i 5 rotti
e tace sui 2 sani; dopo la rigenerazione 7/7 OK. paper_portfolio non rigenerato
(GTAA su ADJUSTED_LAST: replay != serie registrata, P12), tolta la coda non
chiusa. D6 pagata di nuovo nel fix: asi8 in pandas 3 e' in us, non ns —
blindata con test su tre risoluzioni.

S5.12 ESTESO: conftest devia anche trades.db (wrapper su connect: il default
e' catturato alla definizione) e docs/journal/; book_executions.jsonl
sorvegliato con impronta inizio/fine suite.

S5.5 FATTO: test_leva_massima cancellato con nota (misurava frac*n_asset:
con una chiave di scala avrebbe continuato a passare smettendo di controllare).

S5.9 INDAGATO E RIPARATO (r0826_skh_band_drift): il dato regge (taglio 02/07
riproduce l'audit 1,6376, in-sample identico su ogni taglio); la deriva era la
finestra hold-out — e la sola settimana 15-22/08 vale +0,35 di Sharpe hold-out.
Il test ora taglia il feed al 02/07 e verifica la riproduzione stretta.

Suite: 751 passati, 0 falliti.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B9UyJLHzR7EJzxR3iQ3RN1
This commit is contained in:
Adriano Dal Pastro
2026-08-26 10:12:23 +00:00
parent a51844875b
commit d597fc64e8
20 changed files with 872 additions and 54 deletions
+32 -6
View File
@@ -41,13 +41,14 @@ from __future__ import annotations
import csv
import json
from dataclasses import dataclass
from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
from pathlib import Path
import numpy as np
MIN_COVERAGE = 0.80 # sotto: la serie ha buchi -> non ci si decide sopra un gate
MIN_BARS_PER_JUDGE = 2 # con una barra sola non esiste ne' coda ne' copertura
GRAZIA_PREMATURO_MIN = 5.0 # tolleranza fra chiusura della barra e mtime del file (clock/IO)
@dataclass(frozen=True)
@@ -60,6 +61,11 @@ class MonitorSpec:
calendar: str = "crypto" # crypto (365g) | equity (giorni di borsa)
max_age_h: float = 48.0 # oltre: FERMO
gate: str | None = None
# True = i timestamp sono APERTURE di barra (convenzione resample_tf/ccxt/HL) -> vale il
# controllo PREMATURO. False = timbri di esecuzione (es. collect_chain scrive l'ora del
# giro): il timbro coincide con la scrittura per costruzione e il controllo mentirebbe
# SEMPRE (P14: una guardia piu' stretta del contratto insegna a ignorare la sezione).
open_labeled: bool = True
MONITORS: tuple[MonitorSpec, ...] = (
@@ -78,7 +84,8 @@ MONITORS: tuple[MonitorSpec, ...] = (
# fissa ma una serie IRRECUPERABILE — un'ora non raccolta non si recupera da nessuna fonte,
# quindi la soglia d'eta' e' stretta (3h = due giri persi).
MonitorSpec("collect_chain", "chain_collect", "runs.jsonl", 1.0, max_age_h=3.0,
gate="serie irrecuperabile — la catena opzioni non si ricostruisce a posteriori"),
gate="serie irrecuperabile — la catena opzioni non si ricostruisce a posteriori",
open_labeled=False),
)
@@ -130,14 +137,30 @@ def age_hours(last: datetime, now: datetime, calendar: str) -> float:
return raw
def assess(spec: MonitorSpec, stamps: list[datetime], now: datetime) -> dict:
def assess(spec: MonitorSpec, stamps: list[datetime], now: datetime,
mtime: datetime | None = None) -> dict:
"""Verdetto su un monitor. Gli stati NON sono due: 'assente' e 'troppo giovane per un
giudizio' non sono 'OK', e non devono poter passare per tali."""
giudizio' non sono 'OK', e non devono poter passare per tali.
`mtime` = quando il file di serie e' stato scritto l'ultima volta. Serve al controllo
PREMATURO (r0822_monitor_audit): se l'ultima barra registrata CHIUDE dopo che il file e'
stato scritto, il monitor ha consumato una barra parziale — e' il difetto che ha fatto
registrare 4/41/2 minuti di mercato al giorno a 3 monitor con gate. Freschezza e copertura
NON possono vederlo: una serie fresca, completa e SBAGLIATA le passa entrambe."""
base = dict(name=spec.name, gate=spec.gate, n_bars=len(stamps),
age_h=None, coverage=None, expected=None)
if not stamps:
return {**base, "status": "ASSENTE",
"why": "nessuna serie leggibile: il monitor non ha mai scritto, o lo stato e' perso"}
if mtime is not None and spec.open_labeled:
chiude = stamps[-1] + timedelta(hours=spec.cadence_h)
ritardo_min = (chiude - mtime).total_seconds() / 60.0
if ritardo_min > GRAZIA_PREMATURO_MIN:
return {**base, "age_h": age_hours(stamps[-1], now, spec.calendar),
"status": "PREMATURO",
"why": (f"l'ultima barra chiude alle {chiude:%Y-%m-%d %H:%M}Z ma il file e' "
f"stato scritto {ritardo_min/60:.1f}h PRIMA: registrata una barra "
"parziale — la serie va rigenerata, non solo riparata")}
age = age_hours(stamps[-1], now, spec.calendar)
if len(stamps) < MIN_BARS_PER_JUDGE:
return {**base, "age_h": age, "status": "NUOVO",
@@ -159,8 +182,11 @@ def check_all(root: Path, now: datetime | None = None,
now = now or datetime.now(timezone.utc)
out = []
for spec in monitors:
stamps = read_stamps(root / "data" / spec.directory / spec.series)
out.append(assess(spec, stamps, now))
f = root / "data" / spec.directory / spec.series
stamps = read_stamps(f)
mtime = (datetime.fromtimestamp(f.stat().st_mtime, tz=timezone.utc)
if f.exists() else None)
out.append(assess(spec, stamps, now, mtime=mtime))
return out