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:
@@ -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.
|
||||
|
||||
📌 **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
|
||||
+0,052055 USDE; quota rinviata a lunedi' 31/08 — GATE USDE-01 in §4). Autorita' dei parametri: sezione `usde` di `config/live.json`
|
||||
all'indice pubblico `usde_usdc`): il conto tiene **644 USDE** (**IDONEO** dal 28/08; portati da 500 a
|
||||
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
|
||||
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`
|
||||
@@ -171,7 +172,7 @@ sull'hold-out; cambiarne le soglie guardando l'esito, pure.
|
||||
|
||||
| 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 |
|
||||
| **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 |
|
||||
|
||||
+5
-4
@@ -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_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,
|
||||
"max_notional_per_asset_usd": 3000,
|
||||
"max_notional_per_asset_frac": 0.5,
|
||||
@@ -10,11 +10,12 @@
|
||||
"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.",
|
||||
"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": {
|
||||
"index_name": "usde_usdc",
|
||||
"haircut": 0.10,
|
||||
"quota_max_frac": 0.50,
|
||||
"haircut": 0.1,
|
||||
"quota_target": 0.7,
|
||||
"quota_max_frac": 0.5,
|
||||
"depeg_warn": 0.99,
|
||||
"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` né
|
||||
`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.
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user