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:
Adriano Dal Pastro
2026-08-26 15:11:44 +00:00
parent 631e854b29
commit e5052a690f
9 changed files with 575 additions and 30 deletions
+11 -27
View File
@@ -12,6 +12,7 @@ import pandas as pd
from src.backtest.harness import load
from src.live.deribit import DISASTER_LABEL, INSTRUMENT, DeribitRead, build_rebalance_order
from src.live import usde as _usde
from src.strategies.trend_portfolio import CANONICAL, TrendPortfolio, resample_1d
PROJECT_ROOT = Path(__file__).resolve().parents[2]
@@ -88,42 +89,25 @@ def _positions(client):
return pos, note, err
USDE_INDEX_URL = "https://www.deribit.com/api/v2/public/get_index_price?index_name=usde_usdc"
# L'autorita' su config/prezzo/valutazione USDE e' src/live/usde.py (che legge la sezione
# `usde` di config/live.json). L'URL resta un attributo di modulo perche' i test lo patchano
# (tests/test_shadow_usde.py) per forzare il fallback al ticker senza toccare la rete.
USDE_INDEX_URL = _usde.index_url()
def _collaterale_usde(client) -> tuple[float, str]:
"""Valore USD dell'USDE tenuto come collaterale (test di eligibilita' 2026-08-26).
PERCHE' ESISTE. `_equity` leggeva SOLO il conto USDC: convertire $500 in USDE ha fatto
sparire il 24% dell'equity dalla vista del book — falso 'USCITA DI FONDI' e vendite
indesiderate al giro successivo. L'equity del conto e' il TOTALE cross-collateral.
IL PREZZO viene dall'indice pubblico `usde_usdc` (mediana multi-exchange con clamp
±0,5% per fonte) — NON dal book interno ne' da 1.0 cablato: il 10/10/2025 su Binance
l'oracle sul proprio book ha marcato USDe a $0,65 mentre l'indice mondo diceva ~$0,99.
Sotto la pari il prezzo PASSA (un depeg vero deve ridurre il sizing, non sparire);
sopra la pari si clampa a 1.0 (conservativo). Se l'indice non e' leggibile: fallback al
last del ticker via gateway, poi 1.0 DICHIARATO — contare 0 ricreerebbe esattamente il
falso -24% che questa funzione elimina (P5: il fallback si sceglie sul danno)."""
"""Valore USD dell'USDE tenuto come collaterale. Il PERCHE' e le proprieta' (depeg che
PASSA nel sizing, clamp sopra la pari, fallback 1.0 dichiarato mai 0) stanno in
src/live/usde.py — difetto d'origine 2026-08-26: `_equity` leggeva solo il conto USDC,
-24% fantasma dopo la prima conversione."""
try:
eq = float(client.account_summary("USDE").get("equity") or 0)
except Exception:
return 0.0, ""
if eq <= 0:
return 0.0, ""
px = None
try:
import requests
r = requests.get(USDE_INDEX_URL, timeout=5).json()
px = float(r["result"]["index_price"])
except Exception:
try:
px = float(client.ticker("USDE_USDC").get("last_price") or 0) or None
except Exception:
px = None
if px is None or not (0.0 < px < 2.0):
return eq, f"USDE {eq:,.0f} valutato 1.0000 (indice non leggibile)"
return eq * min(px, 1.0), f"USDE {eq:,.0f} @ {min(px, 1.0):.4f}"
px, _fonte = _usde.prezzo(client, url=USDE_INDEX_URL)
return _usde.valuta(eq, px)
def _equity(client, marks):