"""cc01_regime_watch registra ogni giro misurato, e monitor_health lo sorveglia (#12). PERCHE' ESISTE. Dal 03/09 al 16/09/2026 lo script e' morto 14 notti di fila con ModuleNotFoundError (#10) e nessuno se n'e' accorto: `cron_daily.sh` va avanti dopo un errore, e `monitor_health` guarda solo le serie che i monitor scrivono — cc01 non ne scriveva. Ora ogni giro misurato lascia una riga, e un cc01 fermo diventa FERMO come ogni altro sorvegliante. Niente rete: il funding di Hyperliquid e' finto. """ from __future__ import annotations import importlib.util import json import sys from datetime import datetime, timedelta, timezone from pathlib import Path import numpy as np ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from src.live.monitor_health import MONITORS, check_all # noqa: E402 def _carica(): sp = importlib.util.spec_from_file_location("cc01_regime_watch", ROOT / "scripts" / "live" / "cc01_regime_watch.py") m = importlib.util.module_from_spec(sp) sp.loader.exec_module(m) return m W = _carica() SPEC = next((m for m in MONITORS if m.name == "cc01_regime_watch"), None) def _funding_annuo(ann: float, ore: int = 24 * 35): """Una serie oraria costante che annualizza a `ann`.""" return lambda coin, days=35: np.full(ore, ann / W.HOURS_PER_YEAR) def test_e_registrato_in_monitor_health_sul_file_che_scrive(): # P1: il bersaglio della sorveglianza si deriva dal codice sorvegliato, non si ridichiara assert SPEC is not None, "cc01_regime_watch non e' in monitor_health.MONITORS" assert ROOT / "data" / SPEC.directory / SPEC.series == W.RUNS assert SPEC.cadence_h == 24.0 and SPEC.open_labeled is False and SPEC.max_age_h <= 30.0 def test_un_giro_misurato_scrive_una_riga(tmp_path, monkeypatch): runs = tmp_path / "runs.jsonl" monkeypatch.setattr(W, "RUNS", runs) monkeypatch.setattr(W, "fetch_recent_funding", _funding_annuo(0.12)) W.main() righe = [json.loads(r) for r in runs.read_text().splitlines()] assert len(righe) == 1 r = righe[0] ora_ms = datetime.now(timezone.utc).timestamp() * 1000 assert abs(r["ts"] - ora_ms) < 60_000, "ts in millisecondi epoch, come legge monitor_health" assert r["stato"] == "WARN" assert abs(r["BTC"]["ann30"] - 0.12) < 1e-9 and abs(r["ETH"]["ann7"] - 0.12) < 1e-9 def test_un_giro_senza_dati_non_scrive_e_non_dice_quiet(tmp_path, monkeypatch, capsys): # un giro che non ha misurato non e' una barra: se scrivesse, un cc01 cieco resterebbe «OK» runs = tmp_path / "runs.jsonl" monkeypatch.setattr(W, "RUNS", runs) monkeypatch.setattr(W, "fetch_recent_funding", _funding_annuo(0.12, ore=10)) W.main() assert not runs.exists() out = capsys.readouterr().out assert "regime QUIET" not in out and "nessuna riga registrata" in out def test_monitor_health_legge_le_righe_di_cc01(tmp_path, monkeypatch): # integrazione: le righe scritte da cc01 sono barre per monitor_health, e un cc01 fermo e' FERMO runs = tmp_path / "data" / SPEC.directory / SPEC.series runs.parent.mkdir(parents=True) monkeypatch.setattr(W, "RUNS", runs) monkeypatch.setattr(W, "fetch_recent_funding", _funding_annuo(0.05)) W.main() ieri = json.loads(runs.read_text()) ieri["ts"] -= 24 * 3600 * 1000 runs.write_text(json.dumps(ieri) + "\n" + runs.read_text()) adesso = datetime.now(timezone.utc) stato = {r["name"]: r for r in check_all(tmp_path, now=adesso + timedelta(minutes=1), monitors=(SPEC,))} assert stato["cc01_regime_watch"]["status"] == "OK" fermo = check_all(tmp_path, now=adesso + timedelta(hours=31), monitors=(SPEC,))[0] assert fermo["status"] == "FERMO"