"""USDE come collaterale a rendimento — autorita' unica su config, prezzo e valutazione. PERCHE' UN MODULO. Il 2026-08-26 il test di eligibilita' ($500 USDC->USDE) ha scoperto che `shadow._equity` leggeva solo il conto USDC: -24% fantasma, falso 'USCITA DI FONDI' e vendite indesiderate al giro successivo. La riparazione e' nata dentro shadow.py; qui diventa struttura: shadow (equity ORARIA del book) e scripts/live/usde_watch.py (sorveglianza GIORNALIERA) derivano entrambi da questo modulo, che a sua volta legge la sezione `usde` di `config/live.json` (P1: il bersaglio si deriva, non si ridichiara). LE PROPRIETA' DELLA VALUTAZIONE (blindate in tests/test_shadow_usde.py e test_usde_watch.py): * il prezzo viene dall'indice pubblico `usde_usdc` (mediana multi-exchange, clamp +-0.5% per fonte) — MAI dal book interno: il 10/10/2025 Binance marco' USDe a $0.65 sull'oracle del proprio book mentre l'indice mondo diceva ~$0.99; * un depeg sotto la pari PASSA nel sizing (il rischio vero e' il mark-down del collaterale); * sopra la pari si clampa a 1.0 (conservativo); * prezzo non leggibile -> 1.0 DICHIARATO, mai 0 (contare 0 ricrea il falso -24%; P5: il fallback si sceglie sul danno). """ from __future__ import annotations import json from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parents[2] CFG_PATH = PROJECT_ROOT / "config" / "live.json" SPOT = "USDE_USDC" # coppia spot Deribit (fee 0, spread osservato ~3 bps il 26/08) # Default = i valori decisi il 2026-08-26 (r0826_usde_scenari + verifica sul venue). Usati SOLO # se config/live.json non ha la sezione `usde`: la provenienza sta nel campo `fonte` di cfg(), # cosi' un report dice quale delle due configurazioni sta guardando (P7). _DEFAULTS = { "index_name": "usde_usdc", "haircut": 0.10, "quota_max_frac": 0.50, "depeg_warn": 0.99, "depeg_crit": 0.95, } def cfg() -> dict: """Sezione `usde` di config/live.json; default dichiarati se assente. Mai solleva.""" try: raw = json.loads(CFG_PATH.read_text()).get("usde") except Exception: raw = None out = dict(_DEFAULTS) if isinstance(raw, dict): out.update(raw) out["fonte"] = "config/live.json" else: out["fonte"] = "default (sezione `usde` assente in config)" return out def index_url(index_name: str | None = None) -> str: return ("https://www.deribit.com/api/v2/public/get_index_price?index_name=" f"{index_name or cfg()['index_name']}") def prezzo_indice(url: str | None = None, timeout: float = 5.0) -> float | None: """Indice pubblico Deribit (tokenless). None se non leggibile o fuori banda. Mai solleva.""" try: import requests r = requests.get(url or index_url(), timeout=timeout).json() px = float(r["result"]["index_price"]) return px if 0.0 < px < 2.0 else None except Exception: return None def prezzo(client=None, url: str | None = None) -> tuple[float | None, str]: """Catena di prezzo: indice pubblico -> ticker spot via gateway -> None. -> (px, fonte).""" px = prezzo_indice(url=url) if px is not None: return px, "indice" if client is not None: try: px = float(client.ticker(SPOT).get("last_price") or 0) or None if px is not None and 0.0 < px < 2.0: return px, "ticker spot" except Exception: pass return None, "non leggibile" def valuta(eq_usde: float, px: float | None) -> tuple[float, str]: """PURA. -> (valore USD del collaterale USDE, nota di provenienza).""" if eq_usde <= 0: return 0.0, "" if px is None or not (0.0 < px < 2.0): return eq_usde, f"USDE {eq_usde:,.0f} valutato 1.0000 (indice non leggibile)" p = min(px, 1.0) return eq_usde * p, f"USDE {eq_usde:,.0f} @ {p:.4f}"