"""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 # ============================================================================================= # IL RENDIMENTO OSSERVATO, E LA DECISIONE DI QUOTA RINVIATA # ============================================================================================= # Il 2026-08-29 il gate USDE-01 si e' chiuso IDONEO (primo reward il 28/08, +0.052055 USDE) e la # decisione di QUOTA e' stata rinviata dall'operatore a lunedi' 31/08: su UNA finestra il tasso # implicito e' 3,80% annuo, su DUE e' 1,90% — cioe' tutta la differenza che conta. def _lettura(ts_ms, eq, reward=None, con_trade=False): return dict(ts=ts_ms, eq_usde=eq, reward_stimato=reward, reward_rilevato=bool(reward and reward > 1e-3), con_trade=con_trade) G = 86_400_000 def test_una_finestra_che_NON_paga_abbassa_la_stima_invece_di_sparire(): """P5: il silenzio in una serie di ritorni si legge come zero — e qui e' proprio uno zero. Il denominatore e' il TEMPO, non il numero di finestre pagate.""" recs = [_lettura(0, 500.0), _lettura(G, 500.0, 0.0), _lettura(2 * G, 500.052055, 0.052055)] r = W.rendimento(recs) assert r["finestre"] == 2 and r["pagate"] == 1 # 0.052055 su 500 in 2 giorni -> ~1.90%, non il 3.80% della sola finestra che ha pagato assert 0.018 < r["apr"] < 0.021, r def test_la_finestra_sola_darebbe_il_doppio(): """Il controllo positivo: se si guardasse solo il pagamento, il numero raddoppierebbe. E' la ragione per cui l'APR si stampa sempre con il suo n.""" r = W.rendimento([_lettura(G, 500.0), _lettura(2 * G, 500.052055, 0.052055)]) assert 0.037 < r["apr"] < 0.040, r def test_le_letture_con_trade_nel_mezzo_si_escludono(): """P12: quel delta non e' solo reward, e ripararlo in silenzio sarebbe un'invenzione.""" recs = [_lettura(0, 500.0), _lettura(G, 600.0, 100.0, con_trade=True), _lettura(2 * G, 600.05, 0.05)] r = W.rendimento(recs) assert r["finestre"] == 1 and r["tot"] == 0.05 def test_troppo_poco_tempo_non_si_annualizza(): """Un'ora di osservazione moltiplicata per 365 e' un numero, non una misura.""" r = W.rendimento([_lettura(0, 500.0), _lettura(3_600_000, 500.05, 0.05)]) assert r["apr"] is None and "non si annualizza" in r["nota"] def test_nessuna_finestra_misurabile_non_e_zero(): r = W.rendimento([_lettura(0, 500.0)]) assert r["apr"] is None and r["finestre"] == 0 def test_la_quota_si_decide_da_lunedi_e_solo_se_IDONEO(): from datetime import datetime, timezone def q(giorno, v): return W.quota_da_decidere(datetime.fromisoformat(giorno + "T12:35:00+00:00"), v) assert W.DECISIONE_QUOTA_DAL == "2026-08-31" assert q("2026-08-30", "IDONEO") is False, "il rinvio dell'operatore non e' rispettato" assert q("2026-08-31", "IDONEO") is True, "lunedi' la domanda non viene posta" assert q("2026-09-30", "IDONEO") is True, "smette di chiedere: la decisione si dimentica" assert q("2026-08-31", "IN_ATTESA") is False, "chiede la quota su un conto non idoneo" assert q("2026-08-31", "NON_IDONEO") is False