Files
PythagorasGoal/tests/test_cc01_regime_watch.py
Adriano 31f5915a41 ombra di TradingBook documentata al §1; cc01_regime_watch registra i giri e sta in monitor_health (Fixes #11, Fixes #12)
- CLAUDE.md §1 OMBRA: cron :44 sulla stessa crontab, legge il conto con la STESSA chiave live di
  cerbero-mcp (verificato per hash), scrive solo nel suo albero (3 giri misurati, nessun file di
  data/ o logs/ di qui cambiato), non invia per assenza di codice e non per permesso. §12 rimanda.
- cc01: ogni giro con entrambi gli asset misurati scrive data/cc01_regime/runs.jsonl (ts ms,
  stato, funding 30g/7g); se non vede, non scrive e lo dice. monitor_health: MonitorSpec
  giornaliera, max_age 30h, percorso incrociato con cc01.RUNS da un test (P1).
- tests/test_cc01_regime_watch.py (4, rossi prima). Suite completa: 1074 passed.
Autorizzato da Adriano il 16/09 dopo la revisione settimanale.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 21:10:12 +00:00

91 lines
3.7 KiB
Python

"""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"