d597fc64e8
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
98 lines
3.8 KiB
Python
98 lines
3.8 KiB
Python
"""paper_guard: solo barre CHIUSE nei forward-monitor (riparazione del difetto §5.1).
|
|
|
|
Il difetto: `advance()` consumava la barra del giorno in corso e 4 monitor su 6 registravano
|
|
minuti di mercato spacciati per giornate. La riparazione e' un filtro condiviso; qui si prova
|
|
il filtro nei due versi, la sua indipendenza dalla risoluzione dei timestamp (D6 — pagata
|
|
DI NUOVO alla prima stesura di questo stesso modulo), e che ogni monitor lo usi davvero.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from src.live.paper_guard import ( # noqa: E402
|
|
MS_1D, MS_1H, chiusa, indice_chiuso, nuove_chiuse)
|
|
|
|
NOW = int(pd.Timestamp("2026-08-26T00:35:00Z").value // 10**6)
|
|
OGGI = int(pd.Timestamp("2026-08-26T00:00:00Z").value // 10**6)
|
|
IERI = OGGI - MS_1D
|
|
|
|
|
|
def test_la_barra_del_giorno_in_corso_non_e_chiusa():
|
|
assert chiusa(IERI, MS_1D, NOW)
|
|
assert not chiusa(OGGI, MS_1D, NOW)
|
|
|
|
|
|
def test_nuove_chiuse_salta_la_parziale_e_prende_la_chiusa():
|
|
ts = [IERI - MS_1D, IERI, OGGI]
|
|
assert nuove_chiuse(ts, IERI - MS_1D, MS_1D, NOW) == [1]
|
|
# il giro successivo (dopo la mezzanotte seguente) la prende, una volta sola
|
|
assert nuove_chiuse(ts, IERI, MS_1D, NOW + MS_1D) == [2]
|
|
|
|
|
|
def test_nuove_chiuse_su_griglia_oraria():
|
|
h0 = int(pd.Timestamp("2026-08-26T00:00:00Z").value // 10**6)
|
|
ts = [h0 - 2 * MS_1H, h0 - MS_1H, h0] # l'ultima e' l'ora IN CORSO alle 00:35
|
|
assert nuove_chiuse(ts, h0 - 3 * MS_1H, MS_1H, NOW) == [0, 1]
|
|
|
|
|
|
def test_al_confine_esatto_la_barra_e_chiusa():
|
|
# ts + cadenza == adesso: la barra e' finita in questo istante -> chiusa (<=, non <)
|
|
assert chiusa(IERI, MS_1D, OGGI)
|
|
|
|
|
|
def test_indice_chiuso_e_indipendente_dalla_risoluzione():
|
|
"""D6: `asi8`/`view('int64')` cambiano scala con la risoluzione dell'indice (pandas 3 usa
|
|
'us'), e un intero nella scala sbagliata passa senza eccezioni. La prima stesura di
|
|
`indice_chiuso` e' caduta ESATTAMENTE qui: dava chiusa la barra del giorno in corso."""
|
|
for unit in ("ns", "us", "s"):
|
|
ix = pd.DatetimeIndex(
|
|
np.array(["2026-08-25T00:00:00", "2026-08-26T00:00:00"], dtype=f"datetime64[{unit}]")
|
|
).tz_localize("UTC")
|
|
m = indice_chiuso(ix, MS_1D, NOW)
|
|
assert list(m) == [True, False], (unit, list(m))
|
|
|
|
|
|
def test_indice_naive_e_trattato_come_utc():
|
|
ix = pd.DatetimeIndex(["2026-08-25", "2026-08-26"])
|
|
assert list(indice_chiuso(ix, MS_1D, NOW)) == [True, False]
|
|
|
|
|
|
# ===========================================================================
|
|
# guardia sul SORGENTE: ogni monitor deve usare il filtro, non ridichiararlo
|
|
# ===========================================================================
|
|
# Stessa forma della guardia su cron_daily.sh: il difetto e' rientrato nel progetto ogni volta
|
|
# che una regola viveva solo nella memoria di chi l'aveva scritta (P1).
|
|
|
|
MONITOR_1D = ("paper_xsr", "paper_statarb", "paper_dvolspread")
|
|
MONITOR_IDX = ("paper_portfolio", "paper_combo")
|
|
|
|
|
|
def _src(nome: str) -> str:
|
|
return (ROOT / "scripts" / "live" / f"{nome}.py").read_text()
|
|
|
|
|
|
def test_i_monitor_1d_usano_nuove_chiuse():
|
|
for nome in MONITOR_1D:
|
|
s = _src(nome)
|
|
assert "PG.nuove_chiuse(" in s, f"{nome}: advance() non filtra le barre chiuse"
|
|
assert re.search(r"new = \[i for i in range\(len\(ts\)\) if ts\[i\] > st", s) is None, \
|
|
f"{nome}: la vecchia riga che consumava la barra parziale e' ancora nel sorgente"
|
|
|
|
|
|
def test_prevday_filtra_le_ore_chiuse():
|
|
s = _src("paper_prevday")
|
|
assert "PG.chiusa(" in s and "MS_1H" in s
|
|
|
|
|
|
def test_i_monitor_a_indice_usano_indice_chiuso():
|
|
for nome in MONITOR_IDX:
|
|
assert "PG.indice_chiuso(" in _src(nome), f"{nome}: advance() non filtra i giorni chiusi"
|