live: l'equity del book e' il TOTALE cross-collateral — USDE valutato all'indice pubblico
Trovato eseguendo il test di eligibilita' USDE ($500 convertiti alle 13:04Z, fill 1.0003 fee 0): shadow._equity leggeva solo il conto USDC -> il giro successivo avrebbe visto -24,3%, mandato un falso "USCITA DI FONDI" e venduto ~$140 di posizioni. Riparato prima del giro delle 13:47: _collaterale_usde() valuta l'USDE all'indice pubblico usde_usdc (mediana multi-exchange, lezione Binance 10/10/2025), depeg passa nel sizing, clamp a 1.0 sopra la pari, fallback 1.0 dichiarato (mai 0: il fallback si sceglie sul danno, P5). 6 test nuovi in tests/test_shadow_usde.py. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B9UyJLHzR7EJzxR3iQ3RN1
This commit is contained in:
@@ -49,4 +49,26 @@ la decisione di quota (con un gate: quota massima, chi la decide, cosa la riapre
|
|||||||
L'esecuzione è dell'operatore (app: Spot → USDE/USDC) o autorizzata esplicitamente via gateway.
|
L'esecuzione è dell'operatore (app: Spot → USDE/USDC) o autorizzata esplicitamente via gateway.
|
||||||
Il libro non c'entra: nessun codice del percorso soldi viene toccato dal test.
|
Il libro non c'entra: nessun codice del percorso soldi viene toccato dal test.
|
||||||
|
|
||||||
*Esito del test: (da scrivere quando eseguito)*
|
## ESECUZIONE DEL TEST (stesso giorno, 13:04 UTC — autorizzata dall'operatore: "fai test")
|
||||||
|
|
||||||
|
Ordine via gateway, limit marcabile con banda di sicurezza sul prezzo (0,995-1,005):
|
||||||
|
**500 USDE @ 1,0003, filled, fee 0** (`USDE_USDC-8977542793`). Costo totale ~$0,15.
|
||||||
|
Prima finestra utile di reward: ~**28/08 12:00 UTC** (il calcolo usa il minimo di equity
|
||||||
|
USDE della finestra); verifica finale il **29/08**: ≥1 voce reward nel Transaction Log →
|
||||||
|
idoneo; 0 voci → si riconverte e la pista si chiude.
|
||||||
|
|
||||||
|
🚨 **DIFETTO TROVATO E RIPARATO DURANTE IL TEST — il test valeva gia' per questo.**
|
||||||
|
`shadow._equity` leggeva SOLO il conto USDC: dopo la conversione il book avrebbe visto
|
||||||
|
$1.556 (−24,3%) al giro delle 13:47 → falso 💰 "USCITA DI FONDI" **e vendita indesiderata
|
||||||
|
di ~$140 di posizioni** (i target scalano con l'equity). Riparato in 35 minuti, prima del
|
||||||
|
giro: `_collaterale_usde()` valuta l'USDE all'**indice pubblico** `usde_usdc` (mediana
|
||||||
|
multi-exchange — la lezione Binance 10/10), con tre proprieta' blindate da 6 test nuovi
|
||||||
|
(`tests/test_shadow_usde.py`): un **depeg PASSA nel sizing** (mai nascosto), sopra la pari
|
||||||
|
si **clampa a 1,0**, indice illeggibile → **1,0 dichiarato, mai 0** (contare 0 ricreerebbe
|
||||||
|
il falso −24%: il fallback si sceglie sul danno, P5). Verificato live: equity vista dal
|
||||||
|
book $2.055,88 ("mainnet USDC + USDE 500 @ 0.9999").
|
||||||
|
*Un test da $500 progettato per misurare l'eligibilita' ha scoperto che il book era cieco
|
||||||
|
al cross-collateral: se la prima conversione fosse stata fatta a quota vera, l'errore
|
||||||
|
sarebbe costato caro. I test piccoli esistono per questo.*
|
||||||
|
|
||||||
|
*Esito eligibilita': (da scrivere entro il 29/08)*
|
||||||
|
|||||||
@@ -88,12 +88,53 @@ def _positions(client):
|
|||||||
return pos, note, err
|
return pos, note, err
|
||||||
|
|
||||||
|
|
||||||
|
USDE_INDEX_URL = "https://www.deribit.com/api/v2/public/get_index_price?index_name=usde_usdc"
|
||||||
|
|
||||||
|
|
||||||
|
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)."""
|
||||||
|
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}"
|
||||||
|
|
||||||
|
|
||||||
def _equity(client, marks):
|
def _equity(client, marks):
|
||||||
if client is None:
|
if client is None:
|
||||||
return None, "offline"
|
return None, "offline"
|
||||||
try:
|
try:
|
||||||
eq = float(client.account_summary("USDC").get("equity") or 0)
|
eq = float(client.account_summary("USDC").get("equity") or 0)
|
||||||
if eq > 1:
|
if eq > 1:
|
||||||
|
usde_usd, usde_note = _collaterale_usde(client)
|
||||||
|
if usde_usd > 0:
|
||||||
|
return eq + usde_usd, f"mainnet USDC + {usde_note}"
|
||||||
return eq, "mainnet USDC"
|
return eq, "mainnet USDC"
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
"""L'equity del book e' il TOTALE cross-collateral, non il solo conto USDC.
|
||||||
|
|
||||||
|
Il difetto (2026-08-26, trovato DURANTE il test di eligibilita' USDE): convertiti $500 in
|
||||||
|
USDE, `_equity` leggeva solo il conto USDC -> il book avrebbe visto -24%, mandato un falso
|
||||||
|
'USCITA DI FONDI' e VENDUTO ~$140 di posizioni al giro successivo. La riparazione valuta
|
||||||
|
l'USDE all'indice pubblico; questi test blindano le proprieta' che contano:
|
||||||
|
un depeg PASSA nel sizing (non viene nascosto), sopra la pari si clampa, e il fallback
|
||||||
|
a indice illeggibile e' 1.0 dichiarato (contare 0 ricreerebbe il falso -24%).
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
sys.path.insert(0, str(ROOT))
|
||||||
|
|
||||||
|
from src.live import shadow as S # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
class _Client:
|
||||||
|
def __init__(self, usdc=1556.0, usde=0.0, ticker_last=None, usde_raises=False):
|
||||||
|
self._usdc, self._usde = usdc, usde
|
||||||
|
self._last, self._raises = ticker_last, usde_raises
|
||||||
|
|
||||||
|
def account_summary(self, cur):
|
||||||
|
if cur == "USDE":
|
||||||
|
if self._raises:
|
||||||
|
raise RuntimeError("gateway giu'")
|
||||||
|
return {"equity": self._usde}
|
||||||
|
return {"equity": self._usdc}
|
||||||
|
|
||||||
|
def ticker(self, instrument):
|
||||||
|
if self._last is None:
|
||||||
|
raise RuntimeError("ticker non disponibile")
|
||||||
|
return {"last_price": self._last}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _niente_indice_pubblico(monkeypatch):
|
||||||
|
# l'indice pubblico non si interroga nei test: URL invalido -> fallback al ticker finto
|
||||||
|
monkeypatch.setattr(S, "USDE_INDEX_URL", "http://127.0.0.1:1/nope")
|
||||||
|
|
||||||
|
|
||||||
|
def test_senza_usde_nulla_cambia():
|
||||||
|
eq, src = S._equity(_Client(usdc=2056.0, usde=0.0), {})
|
||||||
|
assert eq == 2056.0 and src == "mainnet USDC"
|
||||||
|
|
||||||
|
|
||||||
|
def test_l_usde_entra_nel_totale_al_prezzo_di_mercato():
|
||||||
|
eq, src = S._equity(_Client(usdc=1556.0, usde=500.0, ticker_last=0.9999), {})
|
||||||
|
assert abs(eq - (1556.0 + 500.0 * 0.9999)) < 1e-9
|
||||||
|
assert "USDE" in src and "0.9999" in src
|
||||||
|
|
||||||
|
|
||||||
|
def test_un_depeg_PASSA_nel_sizing_non_viene_nascosto():
|
||||||
|
# 10/10/2025: il rischio vero e' il mark-down del collaterale in un crash.
|
||||||
|
eq, src = S._equity(_Client(usdc=1556.0, usde=500.0, ticker_last=0.97), {})
|
||||||
|
assert abs(eq - (1556.0 + 500.0 * 0.97)) < 1e-9
|
||||||
|
|
||||||
|
|
||||||
|
def test_sopra_la_pari_si_clampa_a_uno():
|
||||||
|
eq, _ = S._equity(_Client(usdc=1556.0, usde=500.0, ticker_last=1.02), {})
|
||||||
|
assert abs(eq - (1556.0 + 500.0)) < 1e-9
|
||||||
|
|
||||||
|
|
||||||
|
def test_indice_illeggibile_vale_uno_DICHIARATO_mai_zero():
|
||||||
|
# contare 0 ricreerebbe il falso -24% (alert + vendite): il fallback e' 1.0 con nota
|
||||||
|
eq, src = S._equity(_Client(usdc=1556.0, usde=500.0, ticker_last=None), {})
|
||||||
|
assert abs(eq - 2056.0) < 1e-9
|
||||||
|
assert "non leggibile" in src
|
||||||
|
|
||||||
|
|
||||||
|
def test_gateway_usde_giu_non_rompe_la_lettura_usdc():
|
||||||
|
eq, src = S._equity(_Client(usdc=2056.0, usde=500.0, usde_raises=True), {})
|
||||||
|
assert eq == 2056.0 and src == "mainnet USDC"
|
||||||
Reference in New Issue
Block a user