ops(live): staleness-gate bloccante + report Telegram giornaliero

Presa in carico operativa del conto Deribit (delega dell'utente). Nessun cambio a
strategie, pesi o sizing: il libro resta TP01 0.75 + SKH01 0.25.

1) STALENESS-GATE (protezione di capitale, era un follow-up mai cablato)
Il 2026-07-14 alle 14:00 UTC il book ha COMPRATO ETH $75 con l'ultima barra del
feed certificato ferma al 2026-07-08: feed congelato da 6 giorni (diario
2026-07-15-feed-freeze). I due gate esistenti non potevano vederlo — il conto ERA
online e la posizione ERA leggibile: proteggono dai problemi di CONTO, non da un
feed morto. Il diario raccomandava un alert; qui il gate e' BLOCCANTE, coerente
con gli altri due ("non opero a cieco"), col disaster-SL on-book come rete su
eventuali posizioni gia' aperte.
- config/live.json: max_data_age_days = 2;
- book_execute: _data_age_days() + blocco PRIMA di costruire DeribitTrader
  (nessuna sessione autenticata aperta su dati morti) + alert Telegram con il
  comando di sblocco; data illeggibile => trattata come stantia;
- letto con cfg.get(default): una config priva della chiave ricade sulla soglia
  sicura invece di sollevare KeyError dentro il percorso con soldi veri;
- tests/test_book_staleness_gate.py: 8 casi, incluso il funzionale che riproduce
  la situazione del 14/07 (online + posizione leggibile + ordine pronto) e
  verifica che DeribitTrader NON venga costruito.
- tests/test_book_live.py: i 3 report finti avevano last_data="2026-07-01"
  hardcoded -> ora data fresca calcolata. Quei test riguardano skh_error /
  pos_error / eq_fallback, non la staleness: con la data fissa sarebbero marciti
  al superamento della soglia.

2) REPORT TELEGRAM GIORNALIERO (scripts/live/telegram_daily.py, in cron_daily.sh)
Gli alert esistenti scattano solo su ordine o errore: con il libro flat — stato
normale e corretto col trend giu' — significava silenzio per settimane,
indistinguibile da un sistema morto. Il report dice ogni giorno dove sta il conto,
perche' non opera e quanto manca perche' operi (con la convenzione TP01 corretta:
media dei SEGNI, quindi servono 2 orizzonti su 3, non basta il piu' breve).
Sola lettura: un test verifica che il modulo non possa inviare ordini.

Stato al commit: equity $596.92, flat e a target, disaster-SL -30% verificato
(placed @ $1,308.7 sull'ultima posizione). TP01 0.00 su entrambi: accensione a
BTC $78.680 (+22,7%) / ETH $2.370 (+27,2%).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-07-25 10:05:47 +00:00
parent 8a2fede2d5
commit 71b39c2c86
7 changed files with 375 additions and 4 deletions
+11 -3
View File
@@ -8,6 +8,14 @@ import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[1]
# Data FRESCA per i report finti: dal 2026-07-25 book_execute ha uno staleness-gate bloccante
# (feed piu' vecchio di 2 giorni -> non esegue). Questi test riguardano skh_error / pos_error /
# eq_fallback, NON la staleness: con una data fissa marcirebbero appena supera la soglia.
# Lo staleness-gate ha i suoi test dedicati in tests/test_book_staleness_gate.py.
def _fresh_bar() -> str:
import pandas as _pd
return str(_pd.Timestamp.now(tz="UTC").normalize().date())
sys.path.insert(0, str(PROJECT_ROOT))
from src.live.book import W_SKH, W_TP01, book_net_target, build_book_order
@@ -299,7 +307,7 @@ def test_book_execute_surfaces_skh_error(monkeypatch, capsys):
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
canned = dict(
last_data="2026-07-01", online=True, real_equity=600.0, equity=600.0, eq_basis="test",
last_data=_fresh_bar(), online=True, real_equity=600.0, equity=600.0, eq_basis="test",
cap_per_asset=300.0, skh_error="RuntimeError: feed 5m giu",
assets=[dict(asset="BTC", instrument="BTC_USDC-PERPETUAL", tp_frac=0.0, skh_sign=0,
skh_state="flat", net_target=0.0, position_usd=0.0, mark=60000.0, order=None)],
@@ -361,7 +369,7 @@ def test_book_execute_halts_on_unreadable_position(monkeypatch, capsys):
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
canned = dict(
last_data="2026-07-01", online=True, real_equity=598.0, equity=598.0, eq_basis="mainnet USDC",
last_data=_fresh_bar(), online=True, real_equity=598.0, equity=598.0, eq_basis="mainnet USDC",
cap_per_asset=300.0, skh_error=None,
pos_error="posizione non leggibile, assunta FLAT: BTC (RuntimeError: api 500)",
assets=[dict(asset="BTC", instrument="BTC_USDC-PERPETUAL", tp_frac=1.0, skh_sign=1,
@@ -422,7 +430,7 @@ def test_book_execute_eq_fallback_warns_but_proceeds(monkeypatch, capsys):
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
canned = dict(
last_data="2026-07-01", online=True, real_equity=None, equity=2000.0,
last_data=_fresh_bar(), online=True, real_equity=None, equity=2000.0,
eq_basis="paper capital (ipotetico)", cap_per_asset=300.0, skh_error=None, pos_error=None,
eq_fallback="equity reale non leggibile (conto flat) -> sizing su paper_cap $2,000",
assets=[dict(asset="BTC", instrument="BTC_USDC-PERPETUAL", tp_frac=0.0, skh_sign=0,