diff --git a/CLAUDE.md b/CLAUDE.md index f025d37..fdd5ab4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -174,7 +174,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. **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 ≈31,2% dell'equity — e' una FRAZIONE, non un livello.** Ogni acquisto oltre il tetto e' rifiutato con `not_enough_funds_in_currency` pur avendo **$1.400 disponibili**: messaggio **fuorviante**, che ha fatto inseguire tre ipotesi sbagliate (taglia, cadenza, prezzo). Che sia un tetto e non un problema d'ordine e' provato a saldo neutro (BUY rifiutato → SELL OK → BUY riaccettato → BUY rifiutato); che sia una **frazione** e' provato il 31/08 lasciando scendere l'equity di $8: **il tetto e' sceso con lei** — 30/08 in [644,18·645,18) con equity $2.063,79 = **31,21-31,26%**, 31/08 in [643,18·644,18) con equity $2.055,56 = **31,29-31,34%** (0,05pp di scarto, dentro il rumore dell'equity che oscilla ±$2-8/ora). ⇒ **il tetto SCALA col conto**: la quota resta ~31% e il valore in dollari cresce col capitale, ma **il 70% non e' raggiungibile ne' ora ne' mai**. 🚨 **Deribit non lo documenta**: l'articolo ufficiale *Yield/reward bearing coins* descrive un **Cap ETHENA** che diluisce il **TASSO** a livello di exchange (`User APR = min(bal_exchange, cap)/bal_exchange × Ethena APR`) e **nessun limite sulle quantita' detenibili** da un conto. Ora e' in config (`usde.venue_cap_frac` 0.312, bordo basso dei bracket) e `usde_convert` **rifiuta il piano prima di sparare** invece di scoprirlo dal venue. **(b) L'USDC NON frutta SUL NOSTRO CONTO — misurato, e la premessa del gate e' RESTAURATA.** `public/get_currencies` dice USDC 3,4000% (USDE 4,1071%) e per qualche ora quel numero e' stato scritto qui come se fosse nostro: **era un listino del venue, non un accredito**. Deribit paga i reward USDC **una volta al MESE** («paid out as a single monthly payment early in the following month», accrual 00:00 UTC sulla **minima equity**) — non ogni giorno come l'USDE, ed e' per questo che una sorveglianza giornaliera non poteva vederli. Verificato sui **due confini di mese a libro FLAT**, dove `equity == balance == USDC` e ogni scalino sarebbe un accredito: **29/06→08/07 equity 598,06 costante, 237/237 ore ferme, 0 scalini** (attesi $0,45) e **31/07→03/08 equity 596,92 costante per 4 giorni** (attesi **$1,72 contro una risoluzione di $0,01, 172×**). **Zero misurato, non zero sotto soglia.** ⇒ il guadagno di tenere USDE e' il **tasso pieno ~4,1%**, non uno spread: **~$26/anno** sui $644. 🚨 **Il motivo ora e' DOCUMENTATO, e non e' MiCA come avevo ipotizzato**: l'articolo ufficiale elenca le giurisdizioni escluse dai reward USDC e **l'ITALIA c'e'** (con Canada e Giappone: non e' una lista MiCA, e' policy di giurisdizione Deribit). ⚠️ **E la finestra di pagamento e' piu' larga di quella che avevo controllato**: «within the **first two weeks** of the following month». Rifatta la verifica sulle due settimane vere: **luglio e' conclusivo** (atteso $1,72 contro un'escursione TOTALE dell'equity di **$0,48** in 1-16/08, zero scalini compatibili), **giugno NO** (il libro ha iniziato a operare a meta' mese e il rumore e' della taglia del segnale). Quindi: **una finestra conclusiva piu' la lista ufficiale**, non due finestre — il «172× la risoluzione» del 30/08 era sovra-affermato. 📌 **Un tasso pubblicato non e' un tasso incassato: si verifica sul CONTO** — ed e' costato una query su una serie che avevamo gia'. Strumento: **`scripts/live/balance_watch.py`** + `cron_balance.sh` (orario :42, sola lettura) registra il **balance a 8 decimali**, la serie che mancava. 📌 **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) | +| **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 ≈31,2% dell'equity — e' una FRAZIONE, non un livello.** Ogni acquisto oltre il tetto e' rifiutato con `not_enough_funds_in_currency` pur avendo **$1.400 disponibili**: messaggio **fuorviante**, che ha fatto inseguire tre ipotesi sbagliate (taglia, cadenza, prezzo). Che sia un tetto e non un problema d'ordine e' provato a saldo neutro (BUY rifiutato → SELL OK → BUY riaccettato → BUY rifiutato); che sia una **frazione** e' provato il 31/08 lasciando scendere l'equity di $8: **il tetto e' sceso con lei** — 30/08 in [644,18·645,18) con equity $2.063,79 = **31,21-31,26%**, 31/08 in [643,18·644,18) con equity $2.055,56 = **31,29-31,34%** (0,05pp di scarto, dentro il rumore dell'equity che oscilla ±$2-8/ora). ⇒ **il tetto SCALA col conto**: la quota resta ~31% e il valore in dollari cresce col capitale, ma **il 70% non e' raggiungibile ne' ora ne' mai**. 🚨 **Deribit non lo documenta**: l'articolo ufficiale *Yield/reward bearing coins* descrive un **Cap ETHENA** che diluisce il **TASSO** a livello di exchange (`User APR = min(bal_exchange, cap)/bal_exchange × Ethena APR`) e **nessun limite sulle quantita' detenibili** da un conto. Ora e' in config (`usde.venue_cap_frac` 0.312, bordo basso dei bracket) e `usde_convert` **rifiuta il piano prima di sparare** invece di scoprirlo dal venue. **(b) L'USDC NON frutta SUL NOSTRO CONTO — misurato, e la premessa del gate e' RESTAURATA.** `public/get_currencies` dice USDC 3,4000% (USDE 4,1071%) e per qualche ora quel numero e' stato scritto qui come se fosse nostro: **era un listino del venue, non un accredito**. Deribit paga i reward USDC **una volta al MESE** («paid out as a single monthly payment early in the following month», accrual 00:00 UTC sulla **minima equity**) — non ogni giorno come l'USDE, ed e' per questo che una sorveglianza giornaliera non poteva vederli. Verificato sui **due confini di mese a libro FLAT**, dove `equity == balance == USDC` e ogni scalino sarebbe un accredito: **29/06→08/07 equity 598,06 costante, 237/237 ore ferme, 0 scalini** (attesi $0,45) e **31/07→03/08 equity 596,92 costante per 4 giorni** (attesi **$1,72 contro una risoluzione di $0,01, 172×**). **Zero misurato, non zero sotto soglia.** ⇒ il guadagno di tenere USDE e' il **tasso pieno ~4,1%**, non uno spread: **~$26/anno** sui $644. 🚨 **Il motivo ora e' DOCUMENTATO, e non e' MiCA come avevo ipotizzato**: l'articolo ufficiale elenca le giurisdizioni escluse dai reward USDC e **l'ITALIA c'e'** (con Canada e Giappone: non e' una lista MiCA, e' policy di giurisdizione Deribit). ⚠️ **E la finestra di pagamento e' piu' larga di quella che avevo controllato**: «within the **first two weeks** of the following month». Rifatta la verifica sulle due settimane vere: **luglio e' conclusivo** (atteso $1,72 contro un'escursione TOTALE dell'equity di **$0,48** in 1-16/08, zero scalini compatibili), **giugno NO** (il libro ha iniziato a operare a meta' mese e il rumore e' della taglia del segnale). Quindi: **una finestra conclusiva piu' la lista ufficiale**, non due finestre — il «172× la risoluzione» del 30/08 era sovra-affermato. 📌 **Un tasso pubblicato non e' un tasso incassato: si verifica sul CONTO** — ed e' costato una query su una serie che avevamo gia'. Strumento: **`scripts/live/balance_watch.py`** + `cron_balance.sh` (orario :42, sola lettura) registra il **balance a 8 decimali**, la serie che mancava. 📌 **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). 📌 **Il costo del NON averlo e' ora documentato e vale il criterio**: un'equity negativa in una valuta paga una *collateral fee* dello **0,05% AL GIORNO** — **18,25%/anno**, addebitata al secondo, **4,3× la resa USDE che si starebbe comprando** (fonte: KB *Cross collateral specifications*). Il ribilanciamento automatico NON salva: scatta solo a **$1M** assoluti o al **100% della cross equity**, soglie che a $2k non si toccano mai — si sanguina la fee e basta. Il 30/08 avevo scritto «Deribit lo finanzia a interesse» **senza il numero**: il numero e' questo, e rende il cuscino piu' giustificato, non meno. 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 | diff --git a/config/live.json b/config/live.json index 748b3cc..cf8f557 100644 --- a/config/live.json +++ b/config/live.json @@ -10,7 +10,7 @@ "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_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. 🚨 venue_cap_frac 0.312 = TETTO DEL VENUE sull'USDE, misurato e NON documentato da Deribit (l'articolo ufficiale 'Yield/reward bearing coins' descrive un Cap ETHENA che diluisce il TASSO a livello di exchange, e nessun limite sulle quantita' detenibili da un conto). Ogni acquisto oltre il tetto e' rifiutato con `not_enough_funds_in_currency` pur avendo $1.400 disponibili: messaggio FUORVIANTE, non c'entrano fondi/taglia/prezzo/liquidita'/cadenza. E' una FRAZIONE DELL'EQUITY TOTALE, non un livello fisso — provato il 31/08 facendo scendere l'equity di $8: il tetto e' sceso con lei (30/08 in [644,18·645,18) con equity $2.063,79 = 31,21-31,26%; 31/08 in [643,18·644,18) con equity $2.055,56 = 31,29-31,34%; i due bracket distano 0,05pp, dentro il rumore dell'equity che oscilla +-$2-8/ora). ⇒ **il tetto SCALA col conto**: la quota resta ~31% e il valore in dollari cresce col capitale, e il 70% di quota_target NON e' raggiungibile ne' ora ne' mai. Il valore 0.312 e' il bordo BASSO dei bracket, scelto per far fallire il piano PRIMA dell'ordine invece che dopo il rifiuto; va ri-sondato se cambia qualcosa sul venue. 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.", + "_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. 🚨 venue_cap_frac 0.312 = TETTO DEL VENUE sull'USDE, misurato e NON documentato da Deribit (l'articolo ufficiale 'Yield/reward bearing coins' descrive un Cap ETHENA che diluisce il TASSO a livello di exchange, e nessun limite sulle quantita' detenibili da un conto). Ogni acquisto oltre il tetto e' rifiutato con `not_enough_funds_in_currency` pur avendo $1.400 disponibili: messaggio FUORVIANTE, non c'entrano fondi/taglia/prezzo/liquidita'/cadenza. E' una FRAZIONE DELL'EQUITY TOTALE, non un livello fisso — provato il 31/08 facendo scendere l'equity di $8: il tetto e' sceso con lei (30/08 in [644,18·645,18) con equity $2.063,79 = 31,21-31,26%; 31/08 in [643,18·644,18) con equity $2.055,56 = 31,29-31,34%; i due bracket distano 0,05pp, dentro il rumore dell'equity che oscilla +-$2-8/ora). ⇒ **il tetto SCALA col conto**: la quota resta ~31% e il valore in dollari cresce col capitale, e il 70% di quota_target NON e' raggiungibile ne' ora ne' mai. Il valore 0.312 e' il bordo BASSO dei bracket, scelto per far fallire il piano PRIMA dell'ordine invece che dopo il rifiuto; va ri-sondato se cambia qualcosa sul venue. 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 registrato il 26/08 come 'verificato sul venue' (USDC 0%). 🚨 DIVERGENZA APERTA, non riparata (P12: fra due fonti che non concordano, una riparazione silenziosa e' un'invenzione): l'articolo ufficiale 'Cross collateral specifications' (Knowledge Base, aggiornato 2026-02-20) tabula USDe al **5%** — e con lui stETH 7.5%, PAXG 2.5/5%, USDT 2%, USYC 2%, BUIDL 2%, SOL 0/15%, e USDC/BTC/ETH a ZERO. Il 26/08 non ha lasciato traccia di COME fu verificato, e l'articolo e' ANTERIORE a quella data: non si sa quale delle due sia stale. Si TIENE 0.10 perche' e' il lato CONSERVATIVO (sottostima il margine utilizzabile) e perche' l'haircut non e' sul percorso soldi — non entra nel sizing, lo usa solo il report di usde_watch, ed e' gia' misurato che non morde a nessuna quota fino al 90%. Da chiudere leggendo la pagina margini del conto, che e' dove Deribit dice di guardare.", "usde": { "index_name": "usde_usdc", "haircut": 0.1, diff --git a/docs/diary/2026-08-30-usde-quota-tetto-venue.md b/docs/diary/2026-08-30-usde-quota-tetto-venue.md index aadaf30..07c357a 100644 --- a/docs/diary/2026-08-30-usde-quota-tetto-venue.md +++ b/docs/diary/2026-08-30-usde-quota-tetto-venue.md @@ -28,6 +28,13 @@ A quota ~99% resterebbero **$41 di USDC** contro un libro che, col disaster-SL a 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.** +> 📌 **Corretto e QUANTIFICATO il 31/08** (KB *Cross collateral specifications*): «*a collateral fee +> will be charged […] **default = 0.05% per day***», al secondo, sulla valuta negativa — cioè +> **18,25%/anno, 4,3× la resa USDE** che si starebbe comprando. Il ribilanciamento automatico non +> salva: scatta a **$1M** assoluti o al **100% della cross equity**, soglie irraggiungibili a $2k. +> Qui il 30/08 avevo scritto «lo finanzia a interesse» **senza il numero**: il numero rende il +> criterio del cuscino più giustificato, non meno. + 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%**. diff --git a/docs/diary/2026-08-31-buidl-test-eligibilita.md b/docs/diary/2026-08-31-buidl-test-eligibilita.md index f2dcc06..fe9ff18 100644 --- a/docs/diary/2026-08-31-buidl-test-eligibilita.md +++ b/docs/diary/2026-08-31-buidl-test-eligibilita.md @@ -129,10 +129,26 @@ per uno tutti i sospetti che sull'USDE avevano portato fuori strada: > «**All Deribit users are permitted to buy and sell BUIDL tokens in the spot markets on Deribit, > with no extra requirements.**» — `insights.deribit.com/education/buidl-launches-on-deribit/` -Non per noi. Motivo plausibile e **non verificato**: BUIDL è un **titolo** (fondo BlackRock emesso -via Securitize), e la sua distribuzione è ristretta per giurisdizione a monte dell'exchange — -l'articolo di marketing non lo dice. *Il fatto misurato è il rifiuto, non il suo motivo* (stessa -disciplina dello zero USDC). +Non per noi. **Il fatto misurato è il rifiuto, non il suo motivo** (stessa disciplina dello zero +USDC). Le spiegazioni candidate sono due, e da fuori **non sono distinguibili**: + +- **(a) giurisdizione** — BUIDL è un **titolo** (fondo BlackRock emesso via Securitize), la cui + distribuzione è ristretta a monte dell'exchange. L'articolo di marketing non lo dice. +- **(b) limiti di spot per TIER DI CONTO** — la documentazione BUIDL, sezione *Limits and + Requirements*: «*Default non-margin spot order limits apply across assets. Check your account + tiers […] for specific maximum open order size configurations.*» Un tier che per questo asset + vale **zero** produrrebbe esattamente ciò che vediamo. + +⚠️ Ma i nostri dati **restringono**: lo **spot USDE funziona** — ne abbiamo comprati 644 lo stesso +giorno, sullo stesso conto, con lo stesso codice. Quindi **non è un blocco generale sullo spot né +un limite di tier trasversale: è SPECIFICO DELL'ASSET.** Il che è compatibile con entrambe le +ipotesi (una giurisdizione per-asset, o un tier per-asset a zero) e non ne elegge nessuna. +*Restringere le ipotesi con l'evidenza che si ha vale più che sceglierne una con l'evidenza che +non si ha.* + +Irrilevante per noi ma registrato: «*Deribit only supports the ERC-20 version of BUIDL on the +Ethereum blockchain*» — riguarda i trasferimenti on-chain, e la nostra uscita sarebbe comunque +una vendita sullo spot, non un prelievo. 📌 **Due affermazioni pubblicate di Deribit smentite dal conto in due giorni**: la APR USDC al 3,40% (30/08) e ora «all users can buy BUIDL». Non è sfortuna, è una **regola**: diff --git a/docs/diary/2026-08-31b-venue-news-annunci.md b/docs/diary/2026-08-31b-venue-news-annunci.md new file mode 100644 index 0000000..ea78777 --- /dev/null +++ b/docs/diary/2026-08-31b-venue-news-annunci.md @@ -0,0 +1,152 @@ +# 2026-08-31 — Fine della Proof of Reserves, e il guardiano che mancava + +L'operatore ha passato l'annuncio *Deribit to Discontinue Daily Proof of Reserves Publication*. +La notizia in sé vale poco per noi. Quello che è emerso cercandola vale molto di più. + +## 1. La notizia: frequenza giù, sostanza su, per noi nulla cambia + +Dal **1 settembre 2026** la pagina Proof of Reserves sparisce e gli aggiornamenti quotidiani +cessano. Al loro posto, sotto il regime **VARA** di Dubai: audit **annuale** indipendente delle +riserve, audit annuale del bilancio, obbligo di **copertura 100%** e **segregazione** degli attivi +dei clienti. E: «*approximately 90% of client assets have been migrated to Coinbase, which acts as +a custodian for Deribit*». + +**Non è un peggioramento netto, ed è importante non raccontarlo come tale.** Si perde un segnale +**ad alta frequenza e debole** (una PoR auto-pubblicata prova che gli attivi esistono in un istante, +non che coprano le passività, ed è aggirabile attorno allo snapshot) e si guadagna un segnale **a +bassa frequenza e più forte** (audit indipendente obbligatorio invece di pubblicazione volontaria), +più un miglioramento **sostanziale** di custodia: 90% degli attivi presso un custode terzo regolato +invece che nei wallet dell'exchange. + +**Per noi non cambia nulla di operativo**, e per una ragione che va detta: *la PoR non l'abbiamo +mai sorvegliata*. Zero occorrenze in tutto il codice. `venue_probe` legge se il venue **risponde**, +non cosa **dichiara**. Si perde un segnale che non stavamo usando. + +Ho provato a catturare l'ultimo dato prima che la pagina sparisse — è la mossa giusta per un dato +non ricostruibile con una scadenza. **Rinunciato di proposito**: la pagina è una SPA e +`get_proof_of_reserves` non esiste come metodo API («Method not found»), ma soprattutto uno +snapshot singolo e auto-riportato non regge lo standard di prova di questo progetto e **non +permetterebbe comunque di stimare `p`**. Un dato che non cambierebbe una decisione non vale il +lavoro per prenderlo. + +## 2. 📌 La conseguenza vera: il riapritore della decisione più grande si è ristretto + +La decisione vincolante **«100% Deribit fino a $20k»** (26/07) ha come riapritori dichiarati: +*«**$20k**, o un cambio di piano, o `p` che diventa **stimabile** invece che assunto»*. + +Dal 1 settembre il segnale pubblico di solvibilità passa da **quotidiano ad annuale**. Il terzo +riapritore non è formalmente chiuso — un audit indipendente è semmai evidenza più forte — ma +diventa **praticamente irraggiungibile**: da una serie giornaliera si può costruire una stima, da +un punto all'anno no. ⇒ **In pratica quella decisione è ora gated SOLO dal capitale**: si riapre a +$20k, o non si riapre. Non cambia la decisione di oggi (fu presa con `p` esplicitamente **assunto**, +non osservato), ma chiude l'unica uscita non-capitale che aveva. + +## 3. 🚨 Quello che ho trovato cercando: quattro annunci materiali che nessuno leggeva + +Il debito #8 dice, testuale: *«Resta scoperto il Rulebook vero e proprio: la sonda legge se il +venue RISPONDE, non cosa il venue ANNUNCIA»*. Esiste un feed RSS pubblico +(`insights.deribit.com/exchange-updates/feed/`) con 10 voci. Alla prima lettura: + +| data | annuncio | perché ci tocca | +|---|---|---| +| 27/08 | Discontinue Daily Proof of Reserves | §1 e §2 di questo diario | +| **14/08** | **Contract Specifications change for Linear USDC Perpetuals** | **i nostri due strumenti** | +| 05/08 | New SM Margin Model On Deribit | leva per taglia, tiered | +| **31/07** | **USDC Rewards Available In More Countries** | tocca una conclusione viva | +| 29/06 | New Fee Schedule On Deribit | `fee_watch` esiste per questo | + +**Il caso che decide la questione è il 14/08.** Le specifiche dei perpetual USDC sono cambiate il +**18/08**; l'annuncio è del **14/08**. Il codice lo racconta già con onestà: la tabella «*non se +n'era accorta […] è andata bene per la direzione del cambiamento, non perché ce ne fossimo +accorti*» — i cambi erano **riduzioni** (tick BTC 0,5→0,1, ETH 0,05→0,01, min/step ETH +0,001→0,0001) e un valore più grosso resta conforme. `check_specs()` è **nato da quella svista** e +oggi gira ogni ora. Ma rileva la deriva **dopo**; il feed l'avrebbe detta **quattro giorni prima**. +*Il giorno che Deribit ALZA un minimo, «dopo» significa ordini rifiutati.* + +Il 31/07 chiude un cerchio aperto ieri: l'espansione delle giurisdizioni idonee ai reward USDC +**non contiene l'Italia**, né alcun paese UE — mentre **San Marino e Città del Vaticano** (i due +microstati europei fuori dall'UE) **sono idonei**. La lista che avevo letto (aggiornata 20/08) è +posteriore all'espansione: **la conclusione dello zero USDC è confermata due volte**. Il pattern +UE-fuori/microstati-dentro suggerisce un driver regolatorio, ma la lista contiene anche Canada e +Giappone: **non asserisco una causa**, il fatto è l'esclusione. + +## 4. Costruito: `venue_news.py` + +Sorveglianza giornaliera del feed, dentro `cron_daily.sh` accanto a `fee_watch` (suo parente +stretto). Scelte che contano: + +- **NON interpreta.** Dice *«è uscito questo, guardalo»*. Nessun automatismo su un testo di + marketing: P13 — una guardia sui NUMERI non copre il RAGIONAMENTO, e la prosa si legge come + opinione di un lettore fallibile. L'unica cosa che classifica è l'**urgenza**. +- **Le parole-chiave sono DERIVATE, non ridichiarate** (P1, il difetto più ricorrente del + progetto): gli strumenti vengono da `deribit._CONTRACT`, la valuta di collaterale da + `config/live.json`. *Chi aggiunge un asset al book allarga la sorveglianza senza toccare questo + file* — ed è esattamente il test che lo blinda. +- **Primo giro semina senza allertare** (P9: l'allarme massimo non si spende per un arretrato di + 10 voci, o non verrà letto il giorno che è vero). +- **Feed illeggibile → codice 2 e nessun silenzio implicito** (P5: «non vedo» non è «niente di + nuovo»). + +5 test sulle funzioni pure. La rete no: `scarica()` ritorna `None` su qualunque errore, ed è quello +il contratto. + +**Cosa NON copre**, e va detto: il **Rulebook** vero e proprio (ADL, perdita socializzata, +*emergency powers*, conti dormienti) non ha un feed. Il debito #8 si restringe, non si chiude. + +--- + +## 5. La Knowledge Base risponde a due domande aperte — e corregge due cose mie + +L'operatore ha passato anche *yield-generating-collateral-usde-buidl-and-more*, che **non** documenta +tetti né haircut ma rimanda alla Knowledge Base. Interrogata via API Zendesk +(`support.deribit.com/api/v2/help_center/articles/search.json`), l'articolo giusto è +**«Cross collateral specifications»** (aggiornato 2026-02-20). + +### (a) 🚨 Il costo del saldo negativo: **0,05% al GIORNO** + +> «*While the equity of a currency in an account remains negative, a **collateral fee** will be +> charged to that account. This fee is charged daily in the same currency as the negative balance +> (**default = 0.05% per day**). The fee is charged based on the amount of time the negative equity +> is held, down to a granularity of seconds.*» + +**18,25% annuo**, cioè **4,3× la resa USDE** che si starebbe comprando tenendo meno USDC. Il 30/08 +avevo scritto «Deribit lo finanzia a interesse» **senza il numero** — l'affermazione era giusta e +vuota. Con il numero il criterio del **cuscino di regolamento** smette di essere prudenza e diventa +aritmetica: scendere sotto il cuscino per tenere più USDE è **−14 punti**. + +E il ribilanciamento automatico **non salva**: scatta solo oltre **$1.000.000** assoluti o il +**100% della cross equity** (default tabulati), soglie che a $2k non si toccano mai. Non veniamo +ribilanciati: **sanguiniamo la fee**. *La cosa che sembrava un paracadute è, alla nostra taglia, +esattamente l'assenza di un paracadute.* + +### (b) 🚨 Haircut USDe: la fonte dice **5%**, noi abbiamo registrato **10%** + +| valuta | haircut X:PM | X:SM | +|---|---|---| +| BTC · ETH · **USDC** | — | — | +| USDT · USYC · **BUIDL** | 2% | 2% | +| PAXG | 2,5% | 5% | +| **USDe** | **5%** | **5%** | +| stETH | 7,5% | 7,5% | +| SOL | — | 15% | + +`config/live.json` dice `haircut: 0.10`, e il diario del 26/08 lo dà per «verificato sul venue» +senza lasciare traccia di **come**. L'articolo è **anteriore** a quella data, quindi non si sa +quale delle due sia stale. + +**Non l'ho riparato** (P12: fra due fonti che non concordano, una riparazione silenziosa è +un'invenzione; M28: la contraddizione è informazione). Si tiene **0,10** per tre ragioni dichiarate: +è il lato **conservativo** (sottostima il margine utilizzabile), **non è sul percorso soldi** (non +entra nel sizing — lo usa solo il report di `usde_watch`), ed è già misurato che **non morde a +nessuna quota fino al 90%**. Si chiude leggendo la pagina margini del conto, che è dove Deribit +stesso dice di guardare. + +### (c) Un dato di lato che vale per il futuro + +**BUIDL ha haircut 2%** — sarebbe stato il *miglior* collaterale a rendimento del listino (contro +il 5% dell'USDe), se solo lo si potesse comprare. E **stETH 7,5%**, il peggiore: terza ragione +indipendente per lasciarlo stare, dopo il tasso dimezzato e l'esposizione ETH. + +📌 **Nessuna delle due fonti documenta un tetto sulle quantità detenibili.** Il ~31,2% sull'USDE +resta **misurato e non spiegato** — e ora si sa che non è una svista di lettura: non è scritto da +nessuna parte. diff --git a/scripts/cron_daily.sh b/scripts/cron_daily.sh index 375f619..abae383 100755 --- a/scripts/cron_daily.sh +++ b/scripts/cron_daily.sh @@ -31,6 +31,12 @@ mkdir -p logs # Schema fee Deribit (nuovo dal 2026-08-01, annunciato senza numeri): legge il tier BASE # dall'endpoint pubblico e applica la regola decisa in anticipo (<=5bps nulla, >10bps peso SKH01). uv run python scripts/live/fee_watch.py --quiet || true + # Cosa il venue ANNUNCIA (debito #8: la sonda venue_probe legge se RISPONDE, non cosa dichiara). + # Feed RSS pubblico degli exchange-update. NON interpreta: classifica solo l'urgenza, su parole + # DERIVATE da deribit._CONTRACT e config/live.json. In una settimana quattro cambi di policy + # materiali erano arrivati a mano; le specifiche dei perp USDC cambiate il 18/08 erano annunciate + # il 14/08, e check_specs() se ne accorge solo DOPO. + uv run python scripts/live/venue_news.py --quiet || true # f delle strutture VRP dalle quote REALI (canonico vs candidato bocciato dal gate 30/07). # Criterio di sufficienza pre-registrato (>=40 coppie, IC95 <=0.12): avvisa da solo quando # il campione basta, invece di lasciare la misura appesa a un promemoria. diff --git a/scripts/live/venue_news.py b/scripts/live/venue_news.py new file mode 100644 index 0000000..1a0ae9b --- /dev/null +++ b/scripts/live/venue_news.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python +"""venue_news.py — legge cosa il venue ANNUNCIA. SOLA LETTURA, non blocca mai nulla. + +PERCHE' ESISTE (2026-08-31). Il debito #8 dice, testuale: *«Resta scoperto il Rulebook vero e +proprio: la sonda legge se il venue RISPONDE, non cosa il venue ANNUNCIA.»* Questa e' quella +meta'. In una settimana quattro cambi di policy Deribit materiali sono arrivati **a mano**, perche' +l'operatore ha incollato dei link — non perche' qualcosa li avesse visti: + + * tetto sull'USDE (~31,2% dell'equity) * esclusione dell'Italia dai reward USDC + * permesso mancante su BUIDL * fine della Proof of Reserves quotidiana + +E il caso che decide la questione: le **specifiche dei perpetual lineari USDC** sono cambiate il +18/08, annunciate il **14/08**. `check_specs()` rileva la deriva DOPO che e' avvenuta ed e' nato +proprio da quella svista; il feed l'avrebbe detto **quattro giorni prima**. Rilevare a posteriori e +sapere in anticipo non sono la stessa cosa: la prima volta e' andata bene solo perche' i cambi +erano RIDUZIONI (un valore piu' grosso resta conforme). Il giorno che Deribit ALZA un minimo, gli +ordini vengono rifiutati. + +COSA E' E COSA NON E'. E' un promemoria che legge un feed RSS pubblico e dice *«e' uscito questo, +guardalo»*. **Non interpreta e non decide**: nessun automatismo su un testo di marketing (P13 — una +guardia sui NUMERI non copre il RAGIONAMENTO, e la prosa si legge come opinione di un lettore +fallibile). L'unica cosa che classifica e' l'URGENZA, e lo fa su parole DERIVATE dal codice +sorvegliato, non ridichiarate qui (P1): gli strumenti vengono da `deribit._CONTRACT`, le valute di +collaterale da `config/live.json`. Chi aggiunge uno strumento o una valuta allarga da solo la +sorveglianza, senza toccare questo file. + + uv run python scripts/live/venue_news.py # report + allerta sui nuovi + uv run python scripts/live/venue_news.py --quiet # solo allerta (per il cron) +""" +from __future__ import annotations + +import argparse +import html +import json +import re +import sys +from datetime import datetime, timezone +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +sys.path.insert(0, str(ROOT)) + +from src.live.notifier import notify # noqa: E402 + +FEED = "https://insights.deribit.com/exchange-updates/feed/" +STATE = ROOT / "data" / "live" / "venue_news.jsonl" + +# Parole che alzano un annuncio a 🚨. Le prime sono FISSE (categorie di rischio che ci toccano +# per costruzione); le altre si DERIVANO dal codice, sotto. +_CHIAVI_FISSE = ( + "contract specification", "margin", "collateral", "fee", "delist", "discontinu", + "reserve", "maintenance", "liquidation", "settlement", "withdrawal", "reward", + "jurisdiction", "eligib", "insurance fund", "socialized", "adl", "rulebook", +) + + +def chiavi() -> list[str]: + """Parole-chiave DERIVATE dal codice sorvegliato (P1), piu' quelle fisse.""" + out = set(_CHIAVI_FISSE) + try: + from src.live.deribit import _CONTRACT + for nome in _CONTRACT: # BTC_USDC-PERPETUAL -> "btc", "usdc" + for pezzo in re.split(r"[_\-]", nome.lower()): + if len(pezzo) >= 3 and pezzo != "perpetual": + out.add(pezzo) + except Exception: + pass + try: + cfg = json.loads((ROOT / "config" / "live.json").read_text()) + out.add(str(cfg.get("usde", {}).get("index_name", "")).split("_")[0].lower()) + except Exception: + pass + return sorted(k for k in out if k) + + +def scarica(url: str = FEED, timeout: float = 20.0) -> str | None: + """Feed RSS pubblico (tokenless). None se non leggibile. Mai solleva.""" + try: + import requests + r = requests.get(url, timeout=timeout, + headers={"User-Agent": "PythagorasGoal/venue_news (+read-only)"}) + return r.text if r.status_code == 200 else None + except Exception: + return None + + +def voci(xml: str) -> list[dict]: + """PURA. Estrae (guid, titolo, data, link) dal RSS. Mai solleva.""" + out = [] + for blocco in re.findall(r"(.*?)", xml or "", re.S): + def campo(tag): + m = re.search(rf"<{tag}[^>]*>(?:)?", blocco, re.S) + return html.unescape(m.group(1).strip()) if m else "" + g = campo("guid") or campo("link") + if g: + out.append({"guid": g, "titolo": campo("title"), + "data": campo("pubDate"), "link": campo("link")}) + return out + + +def rilevante(titolo: str, ks: list[str]) -> list[str]: + """PURA. Quali parole-chiave compaiono nel titolo.""" + t = titolo.lower() + return [k for k in ks if k in t] + + +def gia_visti() -> set[str]: + if not STATE.exists(): + return set() + out = set() + for riga in STATE.read_text().splitlines(): + try: + out.add(json.loads(riga)["guid"]) + except Exception: + pass + return out + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--quiet", action="store_true") + a = ap.parse_args() + + xml = scarica() + if xml is None: + if not a.quiet: + print(" ✗ feed non leggibile — 'non vedo' non e' 'niente di nuovo' (P5)") + return 2 + + ks, viste, nuove = chiavi(), gia_visti(), [] + primo_giro = not STATE.exists() + tutte = voci(xml) + STATE.parent.mkdir(parents=True, exist_ok=True) + with STATE.open("a") as f: + for v in tutte: + if v["guid"] in viste: + continue + v["match"] = rilevante(v["titolo"], ks) + v["visto_il"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + v["seed"] = primo_giro + f.write(json.dumps(v) + "\n") + nuove.append(v) + + # Primo giro: si semina lo storico SENZA allertare (P9: l'allarme massimo non si spende + # per un arretrato di 10 voci, o non verra' letto il giorno che e' vero). + if not primo_giro: + for v in nuove: + if v["match"]: + notify("🚨 annuncio Deribit sul NOSTRO percorso", + {"titolo": v["titolo"], "tocca": ", ".join(v["match"][:6]), + "data": v["data"], "link": v["link"], + "azione": "leggerlo: questo sorvegliante NON interpreta"}) + else: + notify("📌 nuovo annuncio Deribit", + {"titolo": v["titolo"], "data": v["data"], "link": v["link"]}) + + if not a.quiet: + print("=" * 78) + print(f" VENUE NEWS — {len(tutte)} voci nel feed, {len(nuove)} nuove" + + (" (PRIMO GIRO: storico seminato, nessun allarme)" if primo_giro else "")) + print("=" * 78) + for v in tutte: + m = rilevante(v["titolo"], ks) + segno = "🚨" if m else ("·" if v["guid"] in viste else "📌") + print(f" {segno} {v['data'][:16]:17s} {v['titolo'][:70]}") + if m: + print(f" tocca: {', '.join(m[:8])}") + print(f"\n parole derivate dal codice: {', '.join(ks)}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_venue_news.py b/tests/test_venue_news.py new file mode 100644 index 0000000..7e59bed --- /dev/null +++ b/tests/test_venue_news.py @@ -0,0 +1,64 @@ +"""venue_news: il guardiano di cio' che il venue ANNUNCIA. + +Si testano le funzioni PURE (parsing del feed, match, derivazione delle chiavi). La rete no: +`scarica()` ritorna None su qualunque errore ed e' quello il contratto — 'non vedo' non e' +'niente di nuovo' (P5). +""" +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +sys.path.insert(0, str(ROOT / "scripts" / "live")) + +import importlib.util + +_spec = importlib.util.spec_from_file_location( + "venue_news", ROOT / "scripts" / "live" / "venue_news.py") +vn = importlib.util.module_from_spec(_spec) +_spec.loader.exec_module(vn) + + +_FEED = """ +<![CDATA[Contract Specifications change for Linear USDC Perpetuals]]> +https://x/ahttps://x/?p=1Fri, 14 Aug 2026 09:18:57 +0000 +Deribit Welcomes Orbit Markets +https://x/bhttps://x/?p=2Fri, 10 Jul 2026 00:00:00 +0000 +""" + + +def test_estrae_le_voci_con_guid_titolo_e_data(): + v = vn.voci(_FEED) + assert [x["guid"] for x in v] == ["https://x/?p=1", "https://x/?p=2"] + assert v[0]["titolo"] == "Contract Specifications change for Linear USDC Perpetuals" + assert v[0]["data"].startswith("Fri, 14 Aug 2026") + + +def test_feed_illeggibile_non_solleva_e_non_inventa_voci(): + assert vn.voci("") == [] + assert vn.voci("rotto") == [] + + +def test_le_chiavi_sono_DERIVATE_dagli_strumenti_del_book(): + """P1: il bersaglio si deriva dal codice sorvegliato, non si ridichiara. Se un domani il book + prende un altro asset, la sorveglianza lo segue da sola.""" + from src.live.deribit import _CONTRACT + ks = vn.chiavi() + for nome in _CONTRACT: + base = nome.split("_")[0].split("-")[0].lower() + assert base in ks, f"{base} (da {nome}) non e' fra le parole sorvegliate" + assert "usdc" in ks and "margin" in ks and "collateral" in ks + + +def test_riconosce_l_annuncio_che_ci_tocca_e_lascia_stare_gli_altri(): + ks = vn.chiavi() + assert vn.rilevante("Contract Specifications change for Linear USDC Perpetuals", ks) + assert vn.rilevante("New SM Margin Model On Deribit", ks) + assert vn.rilevante("Deribit to Discontinue Daily Proof of Reserves Publication", ks) + assert vn.rilevante("USDC Rewards Available In More Countries", ks) + assert not vn.rilevante("Deribit Welcomes Orbit Markets to Block RFQ Platform", ks) + + +def test_il_match_e_case_insensitive_e_riporta_QUALI_parole(): + m = vn.rilevante("New SM MARGIN Model On Deribit", vn.chiavi()) + assert "margin" in m