de83909db9
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
113 lines
5.5 KiB
Python
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())
|