usde: il venue mette un TETTO al 31,21% — e l'USDC paga gia' 3,40%

Richiesta dell'operatore: "porta in usde tutto il capitale che non viene
usato". Il margine consuma $11 su $2.063 e l'USDE e' cross-collateral: preso
alla lettera vale una quota ~99%, cioe' il 100% che il gate esclude. Portata
all'operatore con le cifre, ha scelto 70%.

Il 70% non e' un argmax (M8): e' il massimo compatibile col CUSCINO DI
REGOLAMENTO, il vincolo che r0830_usde_quota non aveva guardato. P&L e funding
dei perp USDC-lineari si regolano in USDC, non nel collaterale, quindi la
quota non la limita l'haircut ma il saldo USDC che deve reggere il disaster-SL
sulla massima esposizione: 2 x 0,5 x 0,30 = 30% dell'equity, che lascia il 70%.

ESEGUITO +144 USDE (500,1757 -> 644,175691), quota 24,24% -> 31,21%, fee 0.
Poi il muro.

(a) TETTO DEL VENUE, misurato e non documentato da nessuna parte. Provato a
saldo neutro: BUY 20 rifiutato, SELL 20 OK, BUY 20 OK, BUY 5 rifiutato, con
$1.408 disponibili. E' un tetto sul LIVELLO. Il messaggio del venue
(not_enough_funds_in_currency) e' fuorviante e ha fatto inseguire tre ipotesi
sbagliate: taglia (falso, ma min_trade_amount=1 e' vero), rate-limit (falso),
prezzo (vero in parte: il book REST pubblico e' in ritardo sul matching engine
e prezzavo l'ordine sull'INDICE invece che sul BOOK — l'indice marca il
collaterale, il book prezza lo scambio). La cronaca resta nel diario: chi
rilegge non deve rifare il giro.

(b) L'USDC PAGA 3,40%. public/get_currencies: USDE 4,1071%, USDC 3,4000%, e i
nostri reward USDE misurati (~4,5%/anno) confermano che quelle APR sono reali.
Il guadagno non e' il tasso, e' lo SPREAD: 0,71 punti, ~$4,6/anno sui $644 che
teniamo, non i $21 che il gate implicava. Il gate ha misurato il reward
dell'USDE e non ha mai chiesto cosa facesse l'USDC fermo: manca il
controfattuale, M1 in un'altra veste. NON dimostrato che sia accreditato sul
nostro conto — da verificare su un giorno senza trade.

Nuovo attrezzo scripts/live/usde_convert.py: dry-run di default, banda prezzo,
tetto hard di quota, cuscino di regolamento derivato da config. NON passa da
execution.ALLOWED, che resta ai soli due perp.

config: quota_target 0.70 registrato; quota_max_frac alzato a 0.85 e RIMESSO a
0.50 nella stessa sessione — la soglia larga presupponeva un 70% che non
esiste, e lasciarla avrebbe disarmato la guardia per uno scenario che non si e'
verificato.

Suite: 795 passati, 0 falliti.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-08-30 20:18:31 +00:00
parent 3cf884fa39
commit a45794d2cb
4 changed files with 432 additions and 7 deletions
+4 -3
View File
@@ -68,8 +68,9 @@ lorda massima 1,0x**, e nessun livello di capitale cambia il profilo di rischio
2 giorni (blocca) · `skh_feed_max_age_min` 30 (allerta, **non** blocca) · alert Telegram. 2 giorni (blocca) · `skh_feed_max_age_min` 30 (allerta, **non** blocca) · alert Telegram.
📌 **Dal 2026-08-26 l'equity del book e' il TOTALE cross-collateral** (USDC + USDE valutato 📌 **Dal 2026-08-26 l'equity del book e' il TOTALE cross-collateral** (USDC + USDE valutato
all'indice pubblico `usde_usdc`): il conto tiene **500 USDE** (**IDONEO** dal 28/08, primo reward all'indice pubblico `usde_usdc`): il conto tiene **644 USDE** (**IDONEO** dal 28/08; portati da 500 a
+0,052055 USDE; quota rinviata a lunedi' 31/08 — GATE USDE-01 in §4). Autorita' dei parametri: sezione `usde` di `config/live.json` 644 il 30/08 su decisione dell'operatore, **quota 31,21%** — e li' si e' fermato: 🚨 **il venue ha
un TETTO sull'USDE** e ogni acquisto oltre quel livello e' rifiutato. GATE USDE-01 in §4). Autorita' dei parametri: sezione `usde` di `config/live.json`
(haircut 10%, tetto ALLERTA quota 50%, soglie depeg 0,99/0,95 coi criteri in `_nota_usde`), letta (haircut 10%, tetto ALLERTA quota 50%, soglie depeg 0,99/0,95 coi criteri in `_nota_usde`), letta
da `src/live/usde.py` — shadow (sizing orario) e `usde_watch` (sorveglianza 12:35 UTC, cron da `src/live/usde.py` — shadow (sizing orario) e `usde_watch` (sorveglianza 12:35 UTC, cron
proprio, sotto `monitor_health`) derivano entrambi da li'. ⚠️ Il difetto d'origine: `shadow._equity` proprio, sotto `monitor_health`) derivano entrambi da li'. ⚠️ Il difetto d'origine: `shadow._equity`
@@ -171,7 +172,7 @@ sull'hold-out; cambiarne le soglie guardando l'esito, pure.
| gate | data | criterio | stato | | gate | data | criterio | stato |
|---|---|---|---| |---|---|---|---|
| **USDE-01 (eligibilita' reward)** | 2026-08-29 | ≥1 reward entro il 29/08 → IDONEO e si apre la decisione di QUOTA (dell'operatore; tetto allerta 50%, mai 100% — R1 emittente non recuperabile); zero → si riconverte e la pista si chiude | ✅ **CHIUSO IDONEO** il 2026-08-28 12:35Z: primo reward **+0,052055 USDE** rilevato per delta al netto dei trade, coi criteri pre-registrati il 26/08 PRIMA dell'esito. 📌 **La QUOTA e' RINVIATA a lunedi' 31/08 su decisione dell'operatore** (29/08). Tasso a 4 finestre (30/08): **3,26% annuo, banda bootstrap [1,13% 4,51%]** — 3/4 hanno pagato (0,00 · 3,80 · 4,51 · 4,51%), e la banda larga 3,4 punti **e' il risultato**: a n=4 il tasso non e' misurato. `usde_watch.rendimento()` lo stampa **col suo n**; `quota_da_decidere()` lo chiede ogni giorno da lunedi'. 🚨 **L'EV non puo' scegliere la quota** — e' lineare in q, quindi il suo argmax e' sempre un angolo (0% o 100%): ogni quota interna nasce da un criterio sulla **coda**, dichiarato non ottimizzato (M8). Il resto e' misurato (`r0830_usde_quota.py`, diario 30/08): **hazard di pareggio = l'APR** (EV-positiva sse Ethena sta sotto il 3,3%/anno, numero **assunto** come il `p` di Deribit) · l'haircut **non morde a nessuna quota** fino al 90%, nemmeno a leva 1,50x con USDE marcato a 0,95 (29× di cuscino): **il vincolo non e' il margine** · la resa di oggi vale **$16/anno**, meno di UN mese di versamento ⇒ la leva binding resta il **bonifico** · gia' a 24,2% l'evento emittente vale **3,1× il maxDD dell'intero libro**. 📌 **Aspettare costa $1,33**: alzare a 50% fra 4 settimane invece che subito lascia sul tavolo $1,33 di resa (conversione reversibile a ~6 bps) e porta il campione da **4 a 32 finestre**, stringendo la banda ~2,8× | | **USDE-01 (eligibilita' reward)** | 2026-08-29 | ≥1 reward entro il 29/08 → IDONEO e si apre la decisione di QUOTA (dell'operatore; tetto allerta 50%, mai 100% — R1 emittente non recuperabile); zero → si riconverte e la pista si chiude | ✅ **CHIUSO IDONEO** il 2026-08-28 12:35Z: primo reward **+0,052055 USDE** rilevato per delta al netto dei trade, coi criteri pre-registrati il 26/08 PRIMA dell'esito. **QUOTA DECISA il 30/08** (l'operatore ha anticipato il rinvio: *"porta in usde tutto il capitale che non viene usato"*): bersaglio **70%**, `usde.quota_target` in config. Tasso a 4 finestre (30/08): **3,26% annuo, banda bootstrap [1,13% 4,51%]** — 3/4 hanno pagato (0,00 · 3,80 · 4,51 · 4,51%), e la banda larga 3,4 punti **e' il risultato**: a n=4 il tasso non e' misurato. `usde_watch.rendimento()` lo stampa **col suo n**; `quota_da_decidere()` lo chiede ogni giorno da lunedi'. 🚨 **L'EV non puo' scegliere la quota** — e' lineare in q, quindi il suo argmax e' sempre un angolo (0% o 100%): ogni quota interna nasce da un criterio sulla **coda**, dichiarato non ottimizzato (M8). Il resto e' misurato (`r0830_usde_quota.py`, diario 30/08): **hazard di pareggio = l'APR** (EV-positiva sse Ethena sta sotto il 3,3%/anno, numero **assunto** come il `p` di Deribit) · l'haircut **non morde a nessuna quota** fino al 90%, nemmeno a leva 1,50x con USDE marcato a 0,95 (29× di cuscino): **il vincolo non e' il margine** · la resa di oggi vale **$16/anno**, meno di UN mese di versamento ⇒ la leva binding resta il **bonifico** · gia' a 24,2% l'evento emittente vale **3,1× il maxDD dell'intero libro**. 📌 **Aspettare costa $1,33**: alzare a 50% fra 4 settimane invece che subito lascia sul tavolo $1,33 di resa (conversione reversibile a ~6 bps) e porta il campione da **4 a 32 finestre**, stringendo la banda ~2,8×. 🚨 **DUE FATTI DEL 30/08 CHE CAMBIANO IL GATE** (diario `2026-08-30-usde-quota-tetto-venue`). **(a) TETTO DEL VENUE: la quota si ferma al 31,21%** (644,175691 USDE) e il 70% **non e' raggiungibile** — nemmeno il 50%. Provato a saldo neutro: BUY 20 rifiutato → SELL 20 OK → BUY 20 OK → BUY 5 rifiutato, con **$1.408 disponibili**. E' un tetto sul **livello**, non taglia/prezzo/liquidita'/cadenza; il messaggio del venue (`not_enough_funds_in_currency`) e' **fuorviante** e ha fatto inseguire tre ipotesi sbagliate. Formula non leggibile (debito #11: il gateway non espone book ne' `available_withdrawal_funds`). **(b) L'USDC PAGA 3,40%** (`public/get_currencies`: USDE 4,1071%, USDC 3,4000%; i nostri reward USDE misurati ~4,5%/anno confermano che quelle APR sono reali). ⇒ il guadagno non e' il tasso ma lo **spread: 0,71 punti**, **~$4,6/anno** sui $644 che teniamo — non i $21 che il gate implicava. Il gate ha misurato il reward dell'USDE e **non ha mai chiesto cosa facesse l'USDC fermo**: manca il controfattuale, ed e' M1 in un'altra veste (si giudica il **marginale**). ⚠️ Che quella APR sia accreditata **sul nostro conto** NON e' dimostrato: sull'USDE l'abbiamo vista arrivare per delta, sull'USDC il P&L copre $0,13/giorno e il gateway non espone il Transaction Log — **verificare su un giorno senza trade**. 📌 **Il vincolo che l'analisi del margine non aveva guardato**: P&L e funding dei perp USDC-lineari **si regolano in USDC**, quindi la quota massima difendibile non e' data dall'haircut ma dal **cuscino di regolamento** = disaster-SL sulla massima esposizione lorda (`n_asset × frac × disaster_sl_pct` = **30% dell'equity**), che lascia esattamente il **70%** — coincidenza per costruzione, zero slack (M8: non e' un argmax). Attrezzo: **`scripts/live/usde_convert.py`** (dry-run di default, guardie proprie; NON passa da `execution.ALLOWED`, che resta ai soli due perp) |
| **STATARB** | 2026-09-27 | soglie 24/07 (non toccate) + diagnostica statica sempre-short | ✅ monitor riparato e rigenerato il 26/08: la serie vera da' Sharpe **1,61** a oggi → coi criteri invariati punta al **RITIRO**. Si legge il 27/09, non prima | | **STATARB** | 2026-09-27 | soglie 24/07 (non toccate) + diagnostica statica sempre-short | ✅ monitor riparato e rigenerato il 26/08: la serie vera da' Sharpe **1,61** a oggi → coi criteri invariati punta al **RITIRO**. Si legge il 27/09, non prima |
| **XSR01** | 2026-10-23 | Sharpe ≥1,0 **E** haircut ≤40% — dal 26/08 il numero decisivo e' `r0826_xsr_haircut.py` a **pavimento $10**, citato con la frazione di ordini eseguiti — poi `weights_tilt_null` e **capitale ≥$20k** | ✅ **riscritto il 26/08 su decisione dell'operatore** (dichiarato prima dell'esito, stringe: diario `2026-08-26-xsr01-gate-riscritto`): capitale allineato a "100% Deribit fino a $20k"; serie forward rigenerata (31 barre vere, Sharpe 2,72). Sotto la lente RENDITA il sleeve resta **eguagliato da un conto remunerato** (📌 sotto) | | **XSR01** | 2026-10-23 | Sharpe ≥1,0 **E** haircut ≤40% — dal 26/08 il numero decisivo e' `r0826_xsr_haircut.py` a **pavimento $10**, citato con la frazione di ordini eseguiti — poi `weights_tilt_null` e **capitale ≥$20k** | ✅ **riscritto il 26/08 su decisione dell'operatore** (dichiarato prima dell'esito, stringe: diario `2026-08-26-xsr01-gate-riscritto`): capitale allineato a "100% Deribit fino a $20k"; serie forward rigenerata (31 barre vere, Sharpe 2,72). Sotto la lente RENDITA il sleeve resta **eguagliato da un conto remunerato** (📌 sotto) |
| **DVOLSPREAD** | kill 2026-10-24 se Sh<0,50; decisione 2027-01-24 | (a) Sharpe>0 (b) marginale ADDS+robust_oos+insample_edge (c) **DSR ≥0,95** (d) `weights_tilt_null`; **veto** se barre attive <80% → si estende, non si decide | ✅ serie rigenerata il 26/08 (4,41 a oggi, era 14,73 sulla serie rotta); il veto barre-attive ora conta barre VERE e la guardia PREMATURO sorveglia il monitor | | **DVOLSPREAD** | kill 2026-10-24 se Sh<0,50; decisione 2027-01-24 | (a) Sharpe>0 (b) marginale ADDS+robust_oos+insample_edge (c) **DSR ≥0,95** (d) `weights_tilt_null`; **veto** se barre attive <80% → si estende, non si decide | ✅ serie rigenerata il 26/08 (4,41 a oggi, era 14,73 sulla serie rotta); il veto barre-attive ora conta barre VERE e la guardia PREMATURO sorveglia il monitor |
+5 -4
View File
@@ -1,6 +1,6 @@
{ {
"_nota": "Config esecuzione LIVE del BOOK DERIBIT (TP01+SKH01 nettati in software). execution_enabled=true + --execute -> ordini REALI. ARMATO 2026-06-23: esecutore scripts/live/book_execute.py via cron ORARIO scripts/cron_book.sh (SKH01 e' a 230m). disaster-SL on-book -30% sulla posizione netta. Tutto flat all'arming -> nessun ordine finche' un segnale non arma.", "_nota": "Config esecuzione LIVE del BOOK DERIBIT (TP01+SKH01 nettati in software). execution_enabled=true + --execute -> ordini REALI. ARMATO 2026-06-23: esecutore scripts/live/book_execute.py via cron ORARIO scripts/cron_book.sh (SKH01 e' a 230m). disaster-SL on-book -30% sulla posizione netta. Tutto flat all'arming -> nessun ordine finche' un segnale non arma.",
"_nota_cap": "Cap notional per-asset DINAMICO (frontiera 2026-07-03): con max_notional_per_asset_frac=0.5 il cap = equity/2, cosi' cresce col capitale e un deposito non resta strozzato. AGGIORNATO 2026-07-26: max_notional_per_asset_usd alzato 300 -> 3000 in previsione del versamento (EUR 5.000 + 500/mese -> equity ~$6.050, equity/2 ~$3.025). \u26a0\ufe0f Alzarlo NON e' pericoloso perche' dal 2026-07-26 il cap di FALLBACK (equity reale non leggibile) e' min(questo valore, ultima_equity_reale_osservata * frac) \u2014 vedi src/live/book._cap e il watermark data/live/equity_seen.json. Senza quel legame, un cap da $3.000 su un conto da $597 avrebbe permesso $2.000 di nozionale lordo = 3.35x di leva nel momento peggiore. Questo rende inutile l'azione manuale 'al deposito alzare il cap' (pre-registrata 2026-07-02).", "_nota_cap": "Cap notional per-asset DINAMICO (frontiera 2026-07-03): con max_notional_per_asset_frac=0.5 il cap = equity/2, cosi' cresce col capitale e un deposito non resta strozzato. AGGIORNATO 2026-07-26: max_notional_per_asset_usd alzato 300 -> 3000 in previsione del versamento (EUR 5.000 + 500/mese -> equity ~$6.050, equity/2 ~$3.025). ⚠️ Alzarlo NON e' pericoloso perche' dal 2026-07-26 il cap di FALLBACK (equity reale non leggibile) e' min(questo valore, ultima_equity_reale_osservata * frac) vedi src/live/book._cap e il watermark data/live/equity_seen.json. Senza quel legame, un cap da $3.000 su un conto da $597 avrebbe permesso $2.000 di nozionale lordo = 3.35x di leva nel momento peggiore. Questo rende inutile l'azione manuale 'al deposito alzare il cap' (pre-registrata 2026-07-02).",
"execution_enabled": true, "execution_enabled": true,
"max_notional_per_asset_usd": 3000, "max_notional_per_asset_usd": 3000,
"max_notional_per_asset_frac": 0.5, "max_notional_per_asset_frac": 0.5,
@@ -10,11 +10,12 @@
"max_data_age_days": 2, "max_data_age_days": 2,
"_nota_skh_feed": "Freschezza del feed 5m usato per il segnale SKH01 (2026-07-26). fresh_5m ricade sul feed certificato IN SILENZIO se il fetch pubblico Deribit fallisce, e il certificato si rigenera 1x/giorno: senza controllo la latenza d'uscita di SKH01 passa da ~1h a ~1 giorno senza segnalazione. Sopra soglia book_execute ALLERTA e NON blocca (bloccare fermerebbe anche TP01, nettato sullo stesso strumento, per un guasto di rete). Diario 2026-07-26-t1-esecuzione-skh-live.md.", "_nota_skh_feed": "Freschezza del feed 5m usato per il segnale SKH01 (2026-07-26). fresh_5m ricade sul feed certificato IN SILENZIO se il fetch pubblico Deribit fallisce, e il certificato si rigenera 1x/giorno: senza controllo la latenza d'uscita di SKH01 passa da ~1h a ~1 giorno senza segnalazione. Sopra soglia book_execute ALLERTA e NON blocca (bloccare fermerebbe anche TP01, nettato sullo stesso strumento, per un guasto di rete). Diario 2026-07-26-t1-esecuzione-skh-live.md.",
"skh_feed_max_age_min": 30, "skh_feed_max_age_min": 30,
"_nota_usde": "Collaterale USDE a rendimento (test di eligibilita' 2026-08-26: 500 USDE, verdetto entro il 29/08 — diario 2026-08-26-usde-analisi). L'autorita' che LEGGE questa sezione e' src/live/usde.py; la usano shadow._collaterale_usde (equity oraria del book) e scripts/live/usde_watch.py (sorveglianza giornaliera 12:35 UTC). Criteri delle soglie, dichiarati (P6): depeg_warn 0.99 = fuori dalla banda operativa dello spot (~3 bps) e oltre il clamp +-0.5% per fonte dell'indice usde_usdc — a quel prezzo non e' rumore di book; depeg_crit 0.95 = meta' del buffer di haircut (10%) consumata. quota_max_frac 0.50 = tetto di ALLERTA sulla quota USDE/equity totale (N4: la quota e' l'unica leva contro il rischio emittente, -100% = 25 anni di resa, non recuperabile); la quota EFFETTIVA la decide l'operatore, e vale anche per deriva PASSIVA (il libro perde -> la quota sale da sola). haircut 0.10 verificato sul venue il 26/08 (USDC 0%): il 90% dell'USDE fa margine.", "_nota_usde": "Collaterale USDE a rendimento (test di eligibilita' 2026-08-26: 500 USDE, verdetto entro il 29/08 — diario 2026-08-26-usde-analisi). L'autorita' che LEGGE questa sezione e' src/live/usde.py; la usano shadow._collaterale_usde (equity oraria del book) e scripts/live/usde_watch.py (sorveglianza giornaliera 12:35 UTC). Criteri delle soglie, dichiarati (P6): depeg_warn 0.99 = fuori dalla banda operativa dello spot (~3 bps) e oltre il clamp +-0.5% per fonte dell'indice usde_usdc — a quel prezzo non e' rumore di book; depeg_crit 0.95 = meta' del buffer di haircut (10%) consumata. quota_target 0.70 = la quota DECISA dall'operatore il 2026-08-30 (la decisione di quota che il gate USDE-01 apriva; anticipata di un giorno sul rinvio al 31/08, su richiesta esplicita dell'operatore \"porta in usde tutto il capitale che non viene usato\"). Il 70% NON e' un argmax (M8): e' il massimo compatibile col CUSCINO DI REGOLAMENTO, cioe' il vincolo che r0830_usde_quota non aveva guardato — il P&L e il funding dei perp USDC-lineari si regolano in USDC, non nel collaterale, quindi a quota alta il saldo USDC va negativo alla prima perdita del libro e Deribit lo finanzia a interesse. Il cuscino richiesto e' il disaster-SL sulla massima esposizione lorda: n_asset x frac x disaster_sl_pct = 2 x 0.5 x 0.30 = 30% dell'equity, che lascia esattamente il 70%. quota_max_frac 0.50 = tetto di ALLERTA sulla quota USDE/equity totale (N4: la quota e' l'unica leva contro il rischio emittente, -100% = 25 anni di resa, non recuperabile). Alzato a 0.85 il 2026-08-30 in previsione della quota al 70% e RIMESSO A 0.50 lo stesso giorno: il 70% NON e' raggiungibile. 🚨 TETTO DEL VENUE SULL'USDE, misurato il 2026-08-30 e non documentato da nessuna parte: il conto si e' fermato a 644.175691 USDE (31,21% del totale) e ogni acquisto oltre quel livello viene rifiutato con `not_enough_funds_in_currency` AVENDO $1.408 disponibili. Provato a saldo neutro, quattro ordini di fila: BUY 20 rifiutato, SELL 20 accettato, BUY 20 riaccettato, BUY 5 rifiutato — e' un TETTO SUL LIVELLO, non un problema di taglia, prezzo, liquidita' o cadenza. La formula del tetto non e' leggibile da qui (`get_currencies` non espone un cap; il gateway non espone ne' il book ne' available_withdrawal_funds). Con la quota reale al 31% il tetto d'allerta 0.50 e' di nuovo quello giusto e MORDE: allargarlo a 0.85 avrebbe disarmato la guardia per uno scenario che non si e' verificato. Se il tetto del venue si alzera', il criterio per 0.85 e' 'meta' del cuscino di regolamento consumata' (USDC sotto il 15% dell'equity). haircut 0.10 verificato sul venue il 26/08 (USDC 0%): il 90% dell'USDE fa margine.",
"usde": { "usde": {
"index_name": "usde_usdc", "index_name": "usde_usdc",
"haircut": 0.10, "haircut": 0.1,
"quota_max_frac": 0.50, "quota_target": 0.7,
"quota_max_frac": 0.5,
"depeg_warn": 0.99, "depeg_warn": 0.99,
"depeg_crit": 0.95 "depeg_crit": 0.95
} }
@@ -0,0 +1,137 @@
# 2026-08-30 — "porta in USDE tutto il capitale che non viene usato": il venue dice 31%
**Richiesta dell'operatore**, verbatim: *"porta in usde tutto il capitale che non viene usato"*.
Arriva il 30/08, cioè **un giorno prima** della data a cui l'operatore stesso aveva rinviato la
decisione di quota (gate USDE-01, rinvio al 31/08). L'anticipo è esplicito e vale come decisione.
## 1. "Il capitale che non viene usato" non è una quantità piccola: è quasi tutto
Stato letto dal gateway alle 19:50Z: USDC **$1.563,09** (di cui **$11,36** impegnati a margine),
USDE 500,1757 → **$500,18**, totale **$2.063,26**, quota **24,2%**. Posizioni BTC $354,72 + ETH
$213,36 = **$568,08** lordi (0,275x).
Il margine consuma **$11 su $2.063**, e l'USDE è cross-collateral: convertirlo non lo toglie
dall'uso. Anche a piena esposizione del libro (lordo 1,0x) il margine sarebbe ~$41. Presa alla
lettera la richiesta è una quota **~99%** — il 100% che il gate esclude per nome ("mai 100%: R1
emittente non recuperabile"). Le due letture plausibili ("non a margine" e "non esposto dal libro")
convergono entrambe su ~97-99%: non è un'ambiguità che si risolve scegliendo, è una che si porta
all'operatore.
## 2. Il vincolo che `r0830_usde_quota` non aveva guardato: il cuscino di REGOLAMENTO
L'analisi del 30/08 aveva misurato il **margine** e concluso — correttamente — che l'haircut non
morde a nessuna quota fino al 90%, nemmeno a leva 1,50x con USDE a 0,95. Vero, e irrilevante:
il P&L e il funding dei perp **USDC-lineari si regolano in USDC**, non nel collaterale. Lo si vede
nel conto: `equity balance = $7,24` di floating, tutto nel secchio USDC.
A quota ~99% resterebbero **$41 di USDC** contro un libro che, col disaster-SL a 30% sulla massima
esposizione, può perderne **~$619**: il saldo va negativo e Deribit lo finanzia a interesse, che si
mangia la resa che si stava comprando. **Il vincolo non è il margine, ma non è nemmeno assente.**
Criterio dichiarato: cuscino USDC ≥ disaster-SL sulla massima esposizione lorda del libro =
`n_asset × frac × disaster_sl_pct` = 2 × 0,5 × 0,30 = **30% dell'equity** — tutto derivato da
`config/live.json` e `src/live/book.py`, mai ridichiarato (P1). Che lascia esattamente **70%**.
Non è un argmax (M8): la quota massima e il criterio coincidono per costruzione, e infatti il 70%
cade **esattamente sul limite, con zero slack**.
Tabella portata all'operatore (APR allora creduta 3,26% [1,13-4,51], n=4 — vedi §5, è sbagliata):
| quota | converti | USDC che resta | resa/anno | evento emittente vs maxDD libro |
|---|---|---|---|---|
| 24,2% (allora) | — | $1.563 | $16 | 3,1× |
| 50% | $531 | $1.032 | $34 | 6,3× |
| **70%** | **$944** | **$619** | **$47** | **8,8×** |
| ~99% | $1.522 | $41 | $66 | 12,3× |
**Scelta dell'operatore: 70%**, il massimo compatibile col cuscino.
## 3. Esecuzione: da 500 a 644 USDE, poi il muro
Nuovo attrezzo **`scripts/live/usde_convert.py`** (dry-run di default). Non passa da
`DeribitTrader`: `execution.ALLOWED` ammette solo i due perp del libro ed è il guardrail
anti-fat-finger del percorso soldi — **non si allarga** per farci entrare uno spot che col libro
non c'entra. Lo script porta le proprie guardie: banda prezzo [0,995 1,005], tetto HARD di quota
0,95, il cuscino di regolamento, dry-run.
Convertiti **+144 USDE** (500,1757 → **644,175691**), quota **24,24% → 31,21%**, tutti i fill a
1,0002-1,0003, **fee 0**. Poi ogni acquisto ha smesso di passare.
## 4. `not_enough_funds_in_currency` è un messaggio FUORVIANTE — la cronaca delle ipotesi sbagliate
Si tiene apposta, perché chi rilegge non deve rifare questo giro. Tre ipotesi, tutte plausibili,
tutte sbagliate:
1. **"È la taglia."** 943,98 rifiutato per `Invalid params` → vero: `min_trade_amount: 1` e
`contract_size: 1`, il passo è **intero** (confermato da `public/get_instrument`; il gateway
non espone quel tool, la risposta è arrivata dall'API pubblica). Quantizzato a intero: 933
rifiutato per **fondi**, con **$1.541 disponibili**. Falso indizio.
2. **"È il rate-limit."** 100 passava e 300 no; poi anche **1** rifiutato otto volte di fila.
Aggiunto backoff sul tempo fino a 90s: rifiutato lo stesso. Falso.
3. **"È il prezzo."** Diagnosi a tre ordini da 1 USDE: SELL @1,0000 OK, BUY **@1,0003 OK**,
BUY @1,0002 e BUY market rifiutati. Vero **in parte**: il book REST pubblico è in **ritardo**
sul matching engine, e un limite a `ask+1 tick` a volte non incrocia davvero. Difetto mio, e
istruttivo: prezzavo l'ordine sull'**indice** invece che sul **book**. Sono due prezzi con due
mestieri — l'indice **marca** il collaterale (`usde.valuta`, mediana multi-exchange: la lezione
Binance 10/10), il book **prezza** lo scambio. Corretto con 5 tick di margine e tetto duro
1,0010: il fill avviene al prezzo del libro, quindi il margine non si paga.
Ma corretto il prezzo, **200 e 800 continuavano a fallire con 4.552 di profondità all'ask**.
## 5. 🚨 Il fatto vero: un TETTO DEL VENUE sull'USDE, a 644,175691 (31,21%)
Esperimento **a saldo neutro**, quattro ordini di fila (20:14Z):
```
BUY 20 @1.0007 -> not_enough_funds_in_currency USDE 644.1757
SELL 20 @0.9996 -> OK USDE 624.1757
BUY 20 @1.0007 -> OK USDE 644.1757
BUY 5 @1.0007 -> not_enough_funds_in_currency USDE 644.1757
```
È un **tetto sul LIVELLO**: non taglia, non prezzo, non liquidità, non cadenza, non fondi
($1.408 disponibili). Lo stesso ordine che viene rifiutato passa subito dopo una vendita di pari
taglia. **La quota del 70% non è raggiungibile**, e nemmeno il 50%.
La formula del tetto **non è leggibile da qui**: `public/get_currencies` non espone un cap
(`in_cross_collateral_pool: true`, nient'altro), e il gateway non espone né `get_order_book`
`available_withdrawal_funds` — è il debito #11, il gateway è l'unico pezzo della catena che
possediamo e si ha solo quel che espone. Registrato come fatto misurato, non spiegato (D5: un buco
quantificato è un risultato, uno taciuto è un debito).
## 6. 🚨 E la resa non è quella che credevamo: **l'USDC paga 3,40%**
`public/get_currencies`, letta oggi:
| valuta | APR pubblicata |
|---|---|
| USDE | **4,1071%** |
| USDC | **3,4000%** |
I nostri reward USDE misurati (+0,052055 · +0,061815 · +0,061821 al giorno su 500 → **~4,5%/anno**)
**confermano** che la APR pubblicata di USDE è reale. Il che rende credibile anche l'altra riga.
Se l'USDC frutta già 3,40%, il guadagno del passaggio a USDE **non è il tasso: è lo spread**,
**0,71 punti**. Sui $644 che teniamo vale **~$4,6/anno**, non $21; sui $144 convertiti oggi,
**~$1,0/anno**. Il gate USDE-01 ha misurato con cura il reward dell'USDE e **non ha mai chiesto
cosa facesse l'USDC fermo**: è il controfattuale mancante — M1 in un'altra veste, si giudica il
**marginale**, non il livello. E cambia il verso della decisione: si prende un rischio emittente
**non recuperabile** (a 31,2% vale 3,9× il maxDD dell'intero libro) per **0,71 punti**.
⚠️ **Quello che NON è dimostrato:** che la APR USDC sia effettivamente accreditata *sul nostro
conto*. Sull'USDE l'abbiamo vista arrivare per delta; sull'USDC il P&L di trading copre $0,13/giorno
di interesse e il gateway non espone il Transaction Log. **Va verificato su un giorno senza trade
prima di trattarlo come misura** — qui è una APR pubblicata dal venue più una conferma indiretta,
non un reward osservato.
## 7. Cosa resta a terra
- **Conto**: USDC $1.419,61 · USDE 644,175691 ($644,18) · totale **$2.063,79** · quota **31,21%**.
Nessun ordine spot appeso (restano i due `tp01-disaster` reduce_only). Posizioni invariate.
Equity vista dal book **$2.063,86** ("mainnet USDC + USDE 644 @ 1.0000"): sizing corretto,
nessun falso "uscita di fondi".
- **`config/live.json`**: aggiunto `quota_target: 0.70` (la decisione dell'operatore, oggi bloccata
dal venue). `quota_max_frac` alzato a 0,85 e **rimesso a 0,50** nella stessa sessione: la soglia
larga presupponeva un 70% che non esiste, e lasciarla avrebbe **disarmato la guardia per uno
scenario che non si è verificato. Con la quota reale al 31%, il 50% morde di nuovo.**
- **Non fatto**: portare la quota al 70%. Il venue non lo consente. Non è una rinuncia
discrezionale, è un rifiuto misurato e riproducibile.
+286
View File
@@ -0,0 +1,286 @@
#!/usr/bin/env python
"""usde_convert.py — porta la QUOTA di collaterale USDE a un bersaglio dichiarato. INVIA ORDINI VERI.
PERCHE' ESISTE. La conversione del 26/08 (test di eligibilita', 500 USDE) fu una chiamata a mano al
gateway: nessuno script, nessuna guardia, nessuna traccia se non il diario. Il gate USDE-01 prevede
entrambe le direzioni — IDONEO -> si decide la quota; NON IDONEO -> "si riconverte" — e nessuna delle
due aveva un attrezzo. Qui c'e', e vale in tutti e due i versi (quota piu' alta = compra USDE, piu'
bassa = vende).
PERCHE' NON PASSA DA DeribitTrader. `execution.ALLOWED` ammette solo i due perp del libro: e' il
guardrail anti-fat-finger del percorso soldi del book e NON va allargato per far entrare uno spot che
col libro non c'entra. Questo script parla al gateway per conto suo e porta le proprie guardie.
LE GUARDIE (tutte BLOCCANTI, tutte dichiarate qui):
* banda di prezzo [0.995, 1.005] — la stessa del 26/08. Fuori banda non si converte: un USDE a
0.98 e' esattamente il momento in cui NON si compra, e uno a 1.02 non esiste (si clampa a 1.0).
* quota bersaglio in [0, QUOTA_HARD_MAX] — il gate USDE-01 dice "mai 100%: R1 emittente non
recuperabile". Il tetto e' HARD e non si passa da riga di comando.
* CUSCINO DI REGOLAMENTO. Il P&L e il funding dei perp USDC-lineari si regolano in USDC, non nel
collaterale: a quota alta il saldo USDC va negativo alla prima perdita del libro e Deribit lo
finanzia a interesse. Il margine NON e' il vincolo (r0830_usde_quota: l'haircut non morde fino
al 90%) — il vincolo e' questo. Il cuscino richiesto e' il disaster-SL sulla MASSIMA esposizione
che il libro puo' prendere: n_asset * frac * disaster_sl_pct * equity, tutto letto da
config/live.json (P1: il bersaglio si deriva, non si ridichiara).
* dry-run DI DEFAULT: senza --esegui stampa il piano e non manda niente.
IL TETTO PER-ORDINE (misurato il 30/08, non documentato da nessuna parte). Il venue rifiuta gli
ordini spot grandi con `not_enough_funds_in_currency` ANCHE quando i fondi ci sono: 100 accettato
e 300 rifiutato con $1.442 disponibili (e 500 era passato il 26/08 con piu' saldo). Il gateway non
espone `available_withdrawal_funds` ne' `get_order_book`, quindi la causa vera NON e' leggibile da
qui e il messaggio del venue e' fuorviante: si tratta il tetto come ignoto e si CHUNKA, dimezzando
il blocco a ogni rifiuto. Costa zero: il book ha 3.990 unita' al miglior ask e la commissione spot
e' 0, quindi N fill piccoli e un fill grande pagano lo stesso prezzo.
NON E' NE' LA TAGLIA NE' IL TEMPO: E' IL PREZZO. Diagnosi finale (30/08 20:02, tre ordini da 1
USDE): SELL @1.0000 passa, BUY @1.0003 passa, BUY @1.0002 e BUY market falliscono. L'ask si era
mosso da 1.0002 a 1.0003 e l'ordine aveva smesso di INCROCIARE: Deribit rifiuta un limite BUY non
marcabile sullo spot con `not_enough_funds_in_currency`, che e' un messaggio fuorviante e ha fatto
inseguire per venti minuti due ipotesi sbagliate (tetto di size, rate-limit). Il difetto vero era
qui: il prezzo dell'ordine si derivava dall'INDICE (1.0001 + 1 tick) invece che dal BOOK. Sono due
prezzi diversi con due mestieri diversi — l'indice MARCA il collaterale (usde.valuta, mediana
multi-exchange: la lezione Binance 10/10), il book PREZZA lo scambio. Confonderli non sbaglia la
valutazione, sbaglia l'esecuzione.
LO STORICO DELLE IPOTESI SBAGLIATE, tenuto apposta: 943.98 rifiutato per "Invalid params" (vero:
min_trade_amount=1, il passo e' intero) -> 933 rifiutato per fondi con $1.541 disponibili (falso
indizio) -> 100 passa e 300 no (sembrava un tetto di size: era l'ask che si muoveva in mezzo) ->
anche 1 rifiutato otto volte (sembrava rate-limit: era sempre l'ask). Chi rilegge non deve rifare
questo giro. Misurato subito dopo: a raffica il venue rifiuta anche 1 USDE,
otto volte di fila, mentre 100 era appena passato. Il messaggio `not_enough_funds_in_currency` e'
FUORVIANTE — il vincolo e' la cadenza (regolamento spot asincrono / rate-limit), non il saldo ne'
la size. Percio' si SPAZIANO gli ordini (`--pausa`) e si fa backoff sul TEMPO, riducendo il blocco
solo dopo rifiuti ripetuti. Dimezzare la size a ogni rifiuto, come faceva la prima versione, e' la
reazione sbagliata al sintomo giusto: consuma i tentativi senza toccare la causa.
uv run python scripts/live/usde_convert.py --quota 0.70 # piano, nessun ordine
uv run python scripts/live/usde_convert.py --quota 0.70 --esegui # manda l'ordine
"""
from __future__ import annotations
import argparse
import json
import math
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from src.live import usde as U # noqa: E402
from src.live.deribit import DeribitRead # noqa: E402
LOG = ROOT / "data" / "live" / "usde_convert.jsonl"
PX_MIN, PX_MAX = 0.995, 1.005 # banda di sicurezza sul prezzo (26/08)
QUOTA_HARD_MAX = 0.95 # gate USDE-01: "mai 100%"
MIN_ORDER_USD = 5.0
TOLL = 0.005 # entro mezzo punto di quota si considera gia' a bersaglio
EPS_CUSCINO = 0.01 # 1 centesimo: il criterio e' ">= cuscino", e una quota derivata DAL
# cuscino ci cade sopra esatta (0.30*E da entrambi i lati). Senza
# questo epsilon il rumore float boccia il caso di pareggio.
def cuscino_richiesto_usd(equity_tot: float) -> tuple[float, str]:
"""Cuscino USDC di REGOLAMENTO = disaster-SL sulla massima esposizione lorda del libro.
Derivato da config/live.json + src/live/book.py, mai ridichiarato qui."""
cfg = json.loads((ROOT / "config" / "live.json").read_text())
sl = float(cfg["disaster_sl_pct"])
from src.live import book as B
frac, n_asset = float(B.WEIGHT), 2
lordo_max = equity_tot * frac * n_asset
return lordo_max * sl, f"{n_asset} asset x frac {frac} x SL {sl:.0%} su ${equity_tot:,.2f}"
def book_top(timeout: float = 5.0) -> tuple[float | None, float | None]:
"""Miglior bid/ask dello spot USDE_USDC dall'API PUBBLICA Deribit (tokenless: il gateway non
espone get_order_book). -> (bid, ask), (None, None) se non leggibile. Mai solleva.
NON e' il prezzo di `usde.valuta`: quello e' l'INDICE e serve a marcare il collaterale. Questo
serve a far INCROCIARE l'ordine, ed e' l'unico che il matching engine guarda."""
try:
import requests
r = requests.get("https://www.deribit.com/api/v2/public/get_order_book",
params={"instrument_name": U.SPOT, "depth": 5}, timeout=timeout).json()
res = r.get("result", r)
bid = float(res["bids"][0][0]) if res.get("bids") else None
ask = float(res["asks"][0][0]) if res.get("asks") else None
return bid, ask
except Exception:
return None, None
def piano(eq_usdc: float, eq_usde: float, px: float, quota_target: float,
equity_tot: float, bid: float | None = None, ask: float | None = None) -> dict:
"""PURA. -> piano di conversione, con verdetto e motivo. Non manda niente."""
val_usde, _ = U.valuta(eq_usde, px)
quota_ora = val_usde / equity_tot if equity_tot > 0 else 0.0
val_target = quota_target * equity_tot
delta_usd = val_target - val_usde # >0 compra USDE, <0 vende
side = "buy" if delta_usd > 0 else "sell"
# MARCABILE CON MARGINE. Il book REST pubblico e' in RITARDO sul matching engine: misurato il
# 30/08, un BUY a ask+1tick (1.0003) e' stato rifiutato e uno a 1.0004 accettato un minuto dopo,
# col book che riportava ask 1.0002x3855 in entrambi i casi. Percio' si incrocia con 5 tick di
# margine e un TETTO DURO: il fill avviene al prezzo del LIBRO, non al limite, quindi il margine
# non si paga se il book non si e' mosso, e se si e' mosso il tetto limita il danno a ~9 bps.
# Senza book leggibile si RINUNCIA — non si tira a indovinare (P5).
TICK, MARGINE = 0.0001, 5
CAP_BUY, CAP_SELL = 1.0010, 0.9990
rif = ask if side == "buy" else bid
if rif is None:
px_ord = None
elif side == "buy":
px_ord = round(min(rif + MARGINE * TICK, CAP_BUY), 4)
else:
px_ord = round(max(rif - MARGINE * TICK, CAP_SELL), 4)
# PASSO DI SIZE = 1 USDE, misurato sul venue (30/08): 500 e 10 accettati, 943.98 e 933.99
# rifiutati con "Invalid params". `get_instrument` non e' esposto dal gateway (404), quindi il
# passo e' EMPIRICO e dichiarato tale. Si quantizza per DIFETTO: meno USDE = piu' USDC, cioe'
# dal lato del cuscino di regolamento. Sul lato vendita il difetto va nello stesso verso.
amount = float(math.floor(abs(delta_usd) / (px_ord or px)))
cuscino, come = cuscino_richiesto_usd(equity_tot)
usdc_dopo = eq_usdc - delta_usd
motivi = []
if px_ord is None:
motivi.append("book USDE_USDC non leggibile — non si prezza l'ordine sull'indice (l'indice"
" marca il collaterale, il book prezza lo scambio)")
elif not (PX_MIN <= px_ord <= PX_MAX):
motivi.append(f"prezzo d'ordine {px_ord:.4f} fuori banda [{PX_MIN}, {PX_MAX}]")
if not (PX_MIN <= px <= PX_MAX):
motivi.append(f"prezzo {px:.4f} fuori banda [{PX_MIN}, {PX_MAX}]")
if not (0.0 <= quota_target <= QUOTA_HARD_MAX):
motivi.append(f"quota {quota_target:.0%} fuori dal tetto HARD {QUOTA_HARD_MAX:.0%} (gate USDE-01)")
if abs(quota_ora - quota_target) <= TOLL:
motivi.append(f"gia' a bersaglio ({quota_ora:.1%} vs {quota_target:.0%}, toll {TOLL:.1%})")
if abs(delta_usd) < MIN_ORDER_USD:
motivi.append(f"delta ${abs(delta_usd):,.2f} sotto il minimo ${MIN_ORDER_USD:,.2f}")
if usdc_dopo < cuscino - EPS_CUSCINO:
motivi.append(f"cuscino di regolamento: USDC dopo ${usdc_dopo:,.2f} < ${cuscino:,.2f} richiesti ({come})")
return {"quota_ora": quota_ora, "quota_target": quota_target, "val_usde": val_usde,
"equity_tot": equity_tot, "delta_usd": delta_usd, "side": side, "amount": amount,
"px": px, "px_ordine": px_ord, "usdc_dopo": usdc_dopo, "cuscino": cuscino,
"cuscino_come": come, "ok": not motivi, "motivi": motivi}
def leggi_stato(c: DeribitRead) -> tuple[float, float, float | None, str]:
eq_usdc = float(c.account_summary("USDC")["equity"])
try:
eq_usde = float(c.account_summary("USDE")["equity"])
except Exception:
eq_usde = 0.0
px, fonte = U.prezzo(client=c)
return eq_usdc, eq_usde, px, fonte
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--quota", type=float, required=True, help="quota USDE bersaglio, es. 0.70")
ap.add_argument("--esegui", action="store_true", help="manda l'ordine (senza: solo il piano)")
ap.add_argument("--chunk", type=float, default=100.0, help="blocco massimo per ordine (USDE)")
ap.add_argument("--pausa", type=float, default=12.0, help="secondi fra un ordine e il successivo")
a = ap.parse_args()
c = DeribitRead()
eq_usdc, eq_usde, px, fonte = leggi_stato(c)
if px is None:
print(" ✗ prezzo USDE non leggibile — non si converte al buio (P5)")
return 2
val_usde, _ = U.valuta(eq_usde, px)
equity_tot = eq_usdc + val_usde
bid, ask = book_top()
p = piano(eq_usdc, eq_usde, px, a.quota, equity_tot, bid, ask)
print("=" * 78)
print(f" USDE CONVERT — {datetime.now(timezone.utc):%Y-%m-%dT%H:%M:%SZ}"
f" {'ESECUZIONE' if a.esegui else 'PIANO (dry-run)'}")
print("=" * 78)
print(f" prezzo : indice {px:.4f} ({fonte}) · book bid {bid} / ask {ask}"
f" -> ordine @ {p['px_ordine']}, banda [{PX_MIN}, {PX_MAX}]")
print(f" equity : USDC ${eq_usdc:,.2f} + USDE {eq_usde:,.4f} (${val_usde:,.2f})"
f" = ${equity_tot:,.2f}")
print(f" quota : {p['quota_ora']:.1%} -> {p['quota_target']:.0%}")
print(f" ordine : {p['side'].upper()} {p['amount']:,.2f} USDE_USDC @ limit {p['px_ordine']:.4f}"
f" (${abs(p['delta_usd']):,.2f})")
slack = p["usdc_dopo"] - p["cuscino"]
print(f" USDC dopo : ${p['usdc_dopo']:,.2f} · cuscino richiesto ${p['cuscino']:,.2f}"
f" ({p['cuscino_come']})")
print(f" margine : ${slack:+,.2f} di slack sul cuscino"
+ (" ⚠️ la quota e' ESATTAMENTE sul limite: zero slack" if abs(slack) <= EPS_CUSCINO else ""))
for m in p["motivi"]:
print(f"{m}")
if not p["ok"]:
print(" → NON si converte.")
return 1
if not a.esegui:
print(" → piano valido. Rilancia con --esegui per mandarlo.")
return 0
LOG.parent.mkdir(parents=True, exist_ok=True)
chunk, fatti, tot_filled, prezzi, falliti = a.chunk, [], 0.0, [], 0
for _ in range(60):
eq_usdc, eq_usde, px, _f = leggi_stato(c)
if px is None:
print(" ✗ prezzo non piu' leggibile — mi fermo qui"); break
val, _ = U.valuta(eq_usde, px)
bid, ask = book_top()
pp = piano(eq_usdc, eq_usde, px, a.quota, eq_usdc + val, bid, ask)
if not pp["ok"]:
print(f" · fine: {pp['motivi'][0]}")
break
amt = float(min(pp["amount"], math.floor(chunk)))
if amt < 1:
print(" ✗ blocco sceso sotto 1 USDE — mi fermo"); break
resp = c._unwrap(c._post("/mcp-deribit/tools/place_order",
{"instrument_name": U.SPOT, "side": pp["side"], "amount": amt,
"type": "limit", "price": pp["px_ordine"],
"label": f"usde-quota-{a.quota:.2f}"})) or {}
if not isinstance(resp, dict) or resp.get("error") or "order" not in resp:
err = resp.get("error", resp) if isinstance(resp, dict) else resp
falliti += 1
attesa = min(a.pausa * (2 ** falliti), 90.0) # backoff sul TEMPO, non sulla taglia
if falliti >= 3: # solo allora sospetto la size
chunk = max(1.0, math.floor(chunk / 2))
print(f" · rifiutato {amt:,.0f}: {err} -> attendo {attesa:.0f}s"
+ (f", blocco a {chunk:,.0f}" if falliti >= 3 else ""))
time.sleep(attesa)
with LOG.open("a") as f:
f.write(json.dumps({"ts": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"rifiutato": err, "amount": amt, "nuovo_chunk": chunk}) + "\n")
if falliti >= 8:
print(" ✗ troppi rifiuti consecutivi — mi fermo"); break
continue
falliti = 0
order, trades = resp["order"], resp.get("trades", []) or []
filled = float(order.get("filled_amount") or 0) or sum(float(t.get("amount", 0) or 0) for t in trades)
tot_filled += filled
prezzi += [(float(t["price"]), float(t["amount"])) for t in trades if t.get("price") and t.get("amount")]
fatti.append({"order_id": order.get("order_id"), "state": order.get("order_state"),
"amount": amt, "filled": filled})
print(f" · {pp['side']} {amt:,.0f} -> {order.get('order_state')} filled {filled:,.0f}"
f" (quota {pp['quota_ora']:.1%} -> bersaglio {a.quota:.0%})")
if filled <= 0:
print(" ✗ ordine accettato ma non fillato — mi fermo"); break
time.sleep(a.pausa)
px_med = sum(q * v for q, v in prezzi) / sum(v for _, v in prezzi) if prezzi else None
eq_usdc, eq_usde, px, _f = leggi_stato(c)
val, _ = U.valuta(eq_usde, px)
tot = eq_usdc + val
print(f"\n ESITO : {tot_filled:,.0f} USDE in {len(fatti)} ordini"
+ (f" @ {px_med:.4f} medio" if px_med else ""))
print(f" conto : USDC ${eq_usdc:,.2f} + USDE {eq_usde:,.4f} (${val:,.2f}) = ${tot:,.2f}")
print(f" quota : {val / tot:.2%} (bersaglio {a.quota:.0%})")
with LOG.open("a") as f:
f.write(json.dumps({"ts": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
"quota_target": a.quota, "tot_filled": tot_filled, "px_medio": px_med,
"ordini": fatti, "quota_finale": val / tot if tot else None,
"usdc": eq_usdc, "usde": eq_usde}) + "\n")
print(f" registrato : {LOG.relative_to(ROOT)}")
return 0 if tot_filled > 0 else 1
if __name__ == "__main__":
raise SystemExit(main())