Files
PythagorasGoal/scripts/live/venue_watch.py
T
Adriano Dal Pastro de83909db9 un flag sconosciuto non e' l'azione di default: guardia su 18 script, indice USDE orario, pulizia
MISURATO oggi durante la revisione: `trades_db.py --help` non stampava l'uso, cadeva in sync()
e riscriveva meta.ultimo_sync. Nessuno dei 18 script di scripts/live/ usava argparse: un flag
sbagliato era il ramo else. Su journal.py avrebbe scritto pagina e riga di DB, su analista.py
avrebbe speso una chiamata al modello e mandato un Telegram.

- src/live/cli.valida: prima istruzione di ogni __main__, prima di connect()/sync/rete.
  --help -> 0 con l'uso; flag ignoto, valore mancante o posizionale -> 2 con l'elenco dei
  previsti (P4). NIENTE argparse: cambierebbe messaggi, codici d'uscita e --help di script
  che il cron gia' chiama.
- 18 script cablati (i 3 che scrivono + 14 + cc01), flag invariati.
- tests/test_cli_flag.py (30): elenco DERIVATO dalla cartella (P1), valida come prima
  istruzione, uso che documenta i flag, e i flag che il CRON usa davvero restano accettati
  (P15/P16); end-to-end su --help e flag ignoto con trades.db non toccato (M15).
  Verificato a mano: monitor_health --quiet, trades_db --sync --quiet, book_execute dry-run.

Debito §5.15, primo passo: balance_watch (orario) registra `usde_usdc` a ogni campione — None
con la ragione se illeggibile, mai 1,0. Il cablaggio nel bound quando la serie ha storia.

Pulizia dalla revisione: tests/helpers.carica_script al posto della 15a copia del loader
importlib (5 file del libro live); il fill di prova via upsert_fills invece di un INSERT che
lasciava verified NULL; asserzioni non ancorate al padding; movimenti_capitale accetta le
righe gia' lette (una SELECT invece di due ai due lati di una scrittura del cron).

Test 910 verdi (+52). Diario 2026-09-02c.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDJsH3iDSaBns3ccpPBwiu
2026-09-02 15:49:40 +00:00

113 lines
5.5 KiB
Python

#!/usr/bin/env python
"""venue_watch.py — runner ORARIO del tripwire di venue. Allerta su Telegram, NON blocca nulla.
Vedi `src/live/venue_watch.py` per la taratura (100 bps / 4h a segno costante, zero falsi allarmi
su 8 anni, controllo positivo su Bitfinex 2018-19) e per il RUNBOOK a un allarme.
uv run python scripts/live/venue_watch.py # un giro, stampa il report
uv run python scripts/live/venue_watch.py --quiet # solo in caso di allarme (per il cron)
"""
from __future__ import annotations
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from src.live.deribit import check_specs # noqa: E402
from src.live.notifier import notify, ultimo_errore # noqa: E402
from src.live.venue_watch import (PERSIST_HOURS, THRESHOLD_BPS, # noqa: E402
run_once)
def _titolo(rep: dict) -> str:
"""⚠️ Il titolo dice cosa FARE, non solo cosa e' successo: chi lo legge sul telefono deve
sapere il primo passo senza aprire il repo (runbook completo in src/live/venue_watch.py).
⚠️ Dal 19/08 DIPENDE dalla gravita'. Prima era sempre il 🚨 col prelievo di prova, anche per
un blocco da manutenzione annunciata: il 18/08 sono usciti quattro messaggi identici che
chiedevano di prelevare, tutti dopo che il book aveva gia' ripreso. Un allarme massimo speso
per un evento atteso e' un allarme che non verra' letto il giorno che e' vero."""
if rep.get("severity") == "warn":
return "⚠️ VENUE WATCH — Deribit non operativa, ma spiegata. Nessuna azione"
return "🚨 VENUE WATCH — possibile stress su Deribit. PRIMO PASSO: prelievo di prova"
def _manda(rep: dict) -> tuple[bool, str]:
"""Il sender iniettato in `run_once`: e' lui a decidere se i marcatori 'gia' detto' si
scrivono. `tentativi=3` perche' su questo percorso il 6,9% degli invii si perde, e qui
perderne uno significa perdere l'episodio intero."""
ok = notify(_titolo(rep), {f"alert {i+1}": a for i, a in enumerate(rep["alerts"])},
tentativi=3)
if ok:
return True, "ok"
return False, (ultimo_errore() or "motivo non registrato")
def main() -> int:
quiet = "--quiet" in sys.argv
rep = run_once(sender=_manda)
if not quiet:
print("=" * 78)
print(" VENUE WATCH — scarto Deribit vs consenso USD indipendente")
print("=" * 78)
print(f" soglia: {THRESHOLD_BPS:.0f} bps persistenti {PERSIST_HOURS}h a segno costante")
lock = rep["platform_locked"]
print(f" public/status locked: {rep.get('locked_raw') or 'non leggibile'}"
+ (f" (da {rep['lock_hours']}h"
+ (", manutenzione dichiarata" if rep.get("maintenance") else "") + ")"
if lock else ""))
for a, lvl in rep["levels"].items():
d = rep["detail"][a]
bps = f"{d['bps']:+7.1f} bps" if d["bps"] is not None else " n/d "
print(f" {a:<4} {lvl:<6} {bps} ({d['n_refs']} referenze, "
f"spread {d['ref_spread_bps']:.1f} bps)" if d["ref_spread_bps"] is not None
else f" {a:<4} {lvl:<6} {bps} ({d['n_refs']} referenze)")
# --- specifiche contratto: la tabella dichiarata si e' scostata dal venue?
# ⚠️ Gira qui e non nel book: e' un controllo di SOLA LETTURA e non deve stare sul percorso
# che manda ordini. Il 18/08 Deribit ha cambiato tick e size dei perpetual lineari USDC e
# niente se n'era accorto: e' andata bene perche' i cambi erano riduzioni.
spec = check_specs()
if spec["divergenze"]:
rifiuto = [d for d in spec["divergenze"] if d["rischio"] == "rifiuto"]
righe = {f"{d['strumento']} {d['campo']}": f"dichiarato {d['dichiarato']} / "
f"venue {d['venue']} -> {d['rischio']}"
for d in spec["divergenze"]}
if rifiuto:
righe["azione"] = ("il dichiarato e' PIU' FINE del venue: gli ordini verranno "
"RIFIUTATI. Correggi _CONTRACT in src/live/deribit.py adesso.")
notify("🚨 SPEC CONTRATTO — Deribit rifiutera' gli ordini", righe)
else:
righe["azione"] = ("il dichiarato e' piu' GROSSO del venue: gli ordini restano "
"conformi, si perde solo granularita'. Aggiorna _CONTRACT "
"in src/live/deribit.py.")
notify("⚠️ SPEC CONTRATTO — la tabella dichiarata e' vecchia", righe)
for k, v in righe.items():
print(f" SPEC: {k}: {v}")
if rep["alerts"]:
# L'invio l'ha gia' fatto `run_once` attraverso `_manda`: qui si stampa e si riporta
# l'ESITO. Un invio fallito si vede nel log del cron invece di sparire — e lo stato su
# disco e' stato disfatto, quindi l'ora prossima ci riprova da solo.
for a in rep["alerts"]:
print(f" ALERT: {a}")
print(f" telegram: {rep.get('invio')}")
return 2 if rep.get("severity") == "alert" else 0
return 0
USO = """uso: venue_watch.py [--quiet]
venue_watch.py — runner ORARIO del tripwire di venue. Allerta su Telegram, NON blocca nulla.
uv run python scripts/live/venue_watch.py # un giro, stampa il report
uv run python scripts/live/venue_watch.py --quiet # solo in caso di allarme (per il cron)
Dettaglio nel docstring in testa al file."""
if __name__ == "__main__":
from src.live.cli import valida
valida("venue_watch.py", USO, flag=("--quiet",), con_valore=())
raise SystemExit(main())