usde: da patch d'emergenza a struttura — config, modulo unico, sorveglianza
- config/live.json sezione `usde` (unica autorita', P1): indice, haircut 10%, tetto allerta quota 50%, soglie depeg 0.99/0.95 coi criteri dichiarati (P6) - src/live/usde.py: config + catena di prezzo (indice pubblico -> ticker -> 1.0 dichiarato) + valutazione PURA; shadow._collaterale_usde ora deriva da qui - scripts/live/usde_watch.py + cron_usde.sh (12:35 UTC, dopo la finestra reward): reward per delta netto trade (P12: senza inventare attribuzioni), depeg (crit ripetuto, resto a transizione, P9), quota anche per deriva passiva (N4); applica il verdetto di eligibilita' pre-registrato (>=1 reward entro 29/08) - serie data/live/usde_watch.jsonl sotto monitor_health (max 30h, P5: un watch fermo non deve leggersi come "va tutto bene"); baseline 14:17Z registrata - GATE USDE-01 in CLAUDE.md §4; test 775 (+17 in tests/test_usde_watch.py) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B9UyJLHzR7EJzxR3iQ3RN1
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
"""Il collaterale USDE ha tre sorveglianze (reward/depeg/quota) e una valutazione condivisa.
|
||||
|
||||
Blindano: (a) le proprieta' della valutazione in src/live/usde.py (le stesse gia' provate
|
||||
end-to-end in test_shadow_usde.py, qui sulla funzione PURA che shadow e usde_watch condividono);
|
||||
(b) il rilevatore di reward per delta — che NON inventa attribuzioni quando i trade non sono
|
||||
leggibili (P12); (c) il verdetto di eligibilita', che applica la regola pre-registrata il 26/08
|
||||
PRIMA dell'esito; (d) le condizioni di allerta con le soglie DERIVATE dalla config (P1)."""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "live"))
|
||||
|
||||
import usde_watch as W # noqa: E402
|
||||
from src.live import usde as U # noqa: E402
|
||||
|
||||
CFG = {"haircut": 0.10, "quota_max_frac": 0.50, "depeg_warn": 0.99, "depeg_crit": 0.95}
|
||||
|
||||
|
||||
# ------------------------------ valutazione (pura, condivisa) ------------------------------
|
||||
|
||||
def test_valuta_depeg_passa_nel_sizing():
|
||||
usd, nota = U.valuta(500.0, 0.97)
|
||||
assert abs(usd - 485.0) < 1e-9 and "0.9700" in nota
|
||||
|
||||
|
||||
def test_valuta_sopra_la_pari_clampa():
|
||||
usd, _ = U.valuta(500.0, 1.02)
|
||||
assert abs(usd - 500.0) < 1e-9
|
||||
|
||||
|
||||
def test_valuta_prezzo_illeggibile_e_1_dichiarato_mai_0():
|
||||
usd, nota = U.valuta(500.0, None)
|
||||
assert abs(usd - 500.0) < 1e-9 and "non leggibile" in nota
|
||||
|
||||
|
||||
def test_cfg_viene_dalla_config_di_produzione():
|
||||
c = U.cfg()
|
||||
assert c["fonte"] == "config/live.json" # la sezione esiste davvero in config
|
||||
assert 0 < c["depeg_crit"] < c["depeg_warn"] < 1.0
|
||||
assert 0 < c["quota_max_frac"] <= 1.0
|
||||
|
||||
|
||||
# ------------------------------ rilevatore di reward ------------------------------
|
||||
|
||||
def test_baseline_senza_prev_non_attribuisce_nulla():
|
||||
an = W.analizza(None, 500.0, 0.0)
|
||||
assert an["delta"] is None and not an["reward_rilevato"]
|
||||
|
||||
|
||||
def test_reward_rilevato_dal_delta_senza_trade():
|
||||
an = W.analizza({"eq_usde": 500.0}, 500.0548, 0.0)
|
||||
assert an["reward_rilevato"] and abs(an["reward_stimato"] - 0.0548) < 1e-9
|
||||
assert an["con_trade"] is False
|
||||
|
||||
|
||||
def test_delta_con_trade_nel_mezzo_si_netta_e_si_dichiara():
|
||||
# comprati altri 100 USDE + reward 0.05: il reward emerge al netto, ma con_trade lo dice
|
||||
an = W.analizza({"eq_usde": 500.0}, 600.05, 100.0)
|
||||
assert an["reward_rilevato"] and abs(an["reward_stimato"] - 0.05) < 1e-9
|
||||
assert an["con_trade"] is True
|
||||
|
||||
|
||||
def test_trade_illeggibili_il_delta_NON_si_attribuisce():
|
||||
# P12: meglio un giorno di latenza che un reward inventato
|
||||
an = W.analizza({"eq_usde": 500.0}, 500.0548, None)
|
||||
assert an["delta"] is not None and an["reward_stimato"] is None
|
||||
assert not an["reward_rilevato"]
|
||||
|
||||
|
||||
def test_rumore_di_lettura_sotto_eps_non_e_un_reward():
|
||||
an = W.analizza({"eq_usde": 500.0}, 500.0000001, 0.0)
|
||||
assert not an["reward_rilevato"]
|
||||
|
||||
|
||||
# ------------------------------ verdetto (regola pre-registrata 26/08) ------------------------------
|
||||
|
||||
def _rec(**kw):
|
||||
base = dict(reward_rilevato=False, data="2026-08-27T12:35:00Z")
|
||||
base.update(kw)
|
||||
return base
|
||||
|
||||
|
||||
def test_verdetto_in_attesa_prima_della_scadenza():
|
||||
now = datetime(2026, 8, 28, 12, 35, tzinfo=timezone.utc)
|
||||
v, _ = W.verdetto([_rec()], now)
|
||||
assert v == "IN_ATTESA"
|
||||
|
||||
|
||||
def test_verdetto_idoneo_al_primo_reward():
|
||||
now = datetime(2026, 8, 28, 12, 35, tzinfo=timezone.utc)
|
||||
v, motivo = W.verdetto([_rec(), _rec(reward_rilevato=True, reward_stimato=0.0548,
|
||||
data="2026-08-28T12:35:00Z")], now)
|
||||
assert v == "IDONEO" and "2026-08-28" in motivo
|
||||
|
||||
|
||||
def test_verdetto_non_idoneo_solo_DOPO_la_finestra_del_29():
|
||||
prima = datetime(2026, 8, 29, 12, 0, tzinfo=timezone.utc)
|
||||
dopo = datetime(2026, 8, 29, 12, 35, tzinfo=timezone.utc)
|
||||
assert W.verdetto([_rec()], prima)[0] == "IN_ATTESA" # anticiparlo = selezione sull'esito
|
||||
assert W.verdetto([_rec()], dopo)[0] == "NON_IDONEO"
|
||||
|
||||
|
||||
def test_un_reward_gia_visto_resta_idoneo_anche_dopo_la_scadenza():
|
||||
dopo = datetime(2026, 9, 15, 0, 0, tzinfo=timezone.utc)
|
||||
v, _ = W.verdetto([_rec(reward_rilevato=True, reward_stimato=0.05)], dopo)
|
||||
assert v == "IDONEO"
|
||||
|
||||
|
||||
# ------------------------------ condizioni di allerta ------------------------------
|
||||
|
||||
def test_depeg_warn_e_crit_dalle_soglie_di_config():
|
||||
assert W.condizioni({"stato": "OK", "px": 0.985, "quota": 0.2}, CFG) == {"DEPEG_WARN"}
|
||||
assert W.condizioni({"stato": "OK", "px": 0.94, "quota": 0.2}, CFG) == {"DEPEG_CRIT"}
|
||||
assert W.condizioni({"stato": "OK", "px": 0.9999, "quota": 0.2}, CFG) == set()
|
||||
|
||||
|
||||
def test_quota_sopra_il_tetto_allerta_anche_per_deriva_passiva():
|
||||
assert W.condizioni({"stato": "OK", "px": 1.0, "quota": 0.51}, CFG) == {"QUOTA_OVER"}
|
||||
|
||||
|
||||
def test_blind_e_una_condizione_non_un_va_tutto_bene():
|
||||
assert W.condizioni({"stato": "BLIND", "px": None, "quota": None}, CFG) == {"BLIND"}
|
||||
|
||||
|
||||
def test_px_none_non_finge_un_depeg():
|
||||
# indice illeggibile: la valutazione degrada a 1.0 dichiarato, l'allerta depeg NON scatta
|
||||
assert W.condizioni({"stato": "OK", "px": None, "quota": 0.2}, CFG) == set()
|
||||
|
||||
|
||||
# ------------------------------ persistenza jsonl ------------------------------
|
||||
|
||||
def test_leggi_jsonl_roundtrip(tmp_path):
|
||||
import json
|
||||
p = tmp_path / "usde_watch.jsonl"
|
||||
p.write_text(json.dumps({"ts": 1, "eq_usde": 500.0}) + "\n"
|
||||
+ json.dumps({"ts": 2, "eq_usde": 500.05}) + "\n")
|
||||
rows = W.leggi(p)
|
||||
assert len(rows) == 2 and rows[-1]["eq_usde"] == 500.05
|
||||
Reference in New Issue
Block a user