usde_convert: ordini limit GTC mai cancellati, cuscino_watch dopo 6h rimanda il delta pieno #26

Closed
opened 2026-09-27 13:12:12 +00:00 by Adriano · 3 comments
Owner

Revisione 2026-09-27 (goldrake): rilievo Opus 5.5, verifica avversariale fable. Sola lettura, nessun codice toccato.

Rilievo B1 [bug]

FILE: pg:scripts/live/usde_convert.py:249-252,277-278
COSA: ordine limit spot USDE_USDC senza time_in_force e senza cancel: un ordine accettato e non (o parzialmente) fillato resta APERTO sul book, e nessuno lo vede.
SCENARIO: SCOPERTO con bid 0.9985 (indice 0.998 > depeg_warn 0.99, banda PX_MIN 0.995 ok) -> px_ord = max(bid-5tick, CAP_SELL 0.999) = 0.9990, non incrocia -> Deribit lo accetta GTC (default) -> "accettato ma non fillato — mi fermo", rc=1. L'ordine resta vivo; 6h dopo cuscino_watch rilegge i saldi (l'USDE impegnato e' ancora nell'equity) e manda un'ALTRA vendita per lo stesso delta: se si fillano entrambi la quota scende sotto 0.64 di un intero delta (possibile ECCEDENTE -> riacquisto, un giro di spread). Stesso caso lato buy con ask > CAP_BUY 1.0010. Parziale: ogni iterazione con fill parziale lascia il residuo aperto e ricalcola il delta pieno dai saldi.
PROVA: VERIFICATO: uv run python offline su usde_convert.piano(1300,3280,0.998,0.64,...,bid=0.9985) -> side sell, amount 346, px_ordine 0.999, ok=True (non incrocia il bid 0.9985); piano(...,ask=1.0012) -> buy @1.001 ok=True. Lettura — payload place_order senza time_in_force/post_only; nessuna chiamata cancel/get_open_orders su USDE_USDC in usde_convert.py ne' cuscino_watch.py (DeribitRead.open_orders esiste ma mappa solo gli strumenti di _CONTRACT). PRESUNTO: default GTC del gateway (non letto il codice di cerbero-mcp).
PROPOSTA: passare "time_in_force": "immediate_or_cancel" (o fill_or_kill) nel place_order; in alternativa, dopo un fill < amt, cancellare l'ordine per order_id prima di proseguire/uscire.

Verifica fable B1 VERDETTO: CONFERMATO

GRAVITA': alto
PERCHE': cerbero-mcp src/cerbero_mcp/exchanges/deribit/tools.py:160-168 (PlaceOrderReq: instrument_name, side, amount, type, price, reduce_only, post_only, label, leverage — NESSUN time_in_force) e client.py:1637-1670 (place_order non manda time_in_force a private/buy|sell) -> Deribit applica il default good_til_cancelled. In PG scripts/live/usde_convert.py:249-252 payload senza tif/post_only; unico cancel_order del repo e' src/live/execution.py:193 (bracket dei perp), mai chiamato su USDE_USDC; usde_convert.py:277-278 su filled<=0 esce con l'ordine vivo, e su fill parziale prosegue ricalcolando il delta dai saldi (leggi_stato legge equity, che include l'USDE impegnato). cuscino_watch.py:161-165: da SCOPERTO riprova dopo RIPRISTINO_MIN_ORE 6h col delta pieno. Il gateway espone /tools/cancel_order (routers/deribit.py:263) ma PG non lo usa qui. Gateway non modificato dalla PRESUNZIONE del revisore: confermata.
CORREZIONE: la proposta «passare time_in_force nel place_order» NON FUNZIONA cosi' com'e': PlaceOrderReq e' un BaseModel pydantic senza extra="allow" (solo i modelli alle righe 28 e 43 lo hanno) -> il campo verrebbe IGNORATO in silenzio e l'ordine resterebbe GTC. Due strade: (a) aggiungere time_in_force: str | None a PlaceOrderReq + client.place_order in cerbero-mcp e poi passarlo da PG; (b) solo in PG, senza toccare il gateway: dopo ogni ordine con filled < amt chiamare /mcp-deribit/tools/cancel_order con order["order_id"] (e a inizio di usde_convert --esegui cancellare gli ordini aperti su USDE_USDC via get_open_orders, che DeribitRead.open_orders gia' interroga ma solo per gli strumenti di _CONTRACT). (b) e' verificabile con un test sul mock del client; (a) e' la soluzione pulita ma tocca un secondo repo.

Revisione 2026-09-27 (goldrake): rilievo Opus 5.5, verifica avversariale fable. Sola lettura, nessun codice toccato. ### Rilievo B1 [bug] FILE: pg:scripts/live/usde_convert.py:249-252,277-278 COSA: ordine limit spot USDE_USDC senza time_in_force e senza cancel: un ordine accettato e non (o parzialmente) fillato resta APERTO sul book, e nessuno lo vede. SCENARIO: SCOPERTO con bid 0.9985 (indice 0.998 > depeg_warn 0.99, banda PX_MIN 0.995 ok) -> px_ord = max(bid-5tick, CAP_SELL 0.999) = 0.9990, non incrocia -> Deribit lo accetta GTC (default) -> "accettato ma non fillato — mi fermo", rc=1. L'ordine resta vivo; 6h dopo cuscino_watch rilegge i saldi (l'USDE impegnato e' ancora nell'equity) e manda un'ALTRA vendita per lo stesso delta: se si fillano entrambi la quota scende sotto 0.64 di un intero delta (possibile ECCEDENTE -> riacquisto, un giro di spread). Stesso caso lato buy con ask > CAP_BUY 1.0010. Parziale: ogni iterazione con fill parziale lascia il residuo aperto e ricalcola il delta pieno dai saldi. PROVA: VERIFICATO: `uv run python` offline su usde_convert.piano(1300,3280,0.998,0.64,...,bid=0.9985) -> side sell, amount 346, px_ordine 0.999, ok=True (non incrocia il bid 0.9985); piano(...,ask=1.0012) -> buy @1.001 ok=True. Lettura — payload place_order senza time_in_force/post_only; nessuna chiamata cancel/get_open_orders su USDE_USDC in usde_convert.py ne' cuscino_watch.py (DeribitRead.open_orders esiste ma mappa solo gli strumenti di _CONTRACT). PRESUNTO: default GTC del gateway (non letto il codice di cerbero-mcp). PROPOSTA: passare "time_in_force": "immediate_or_cancel" (o fill_or_kill) nel place_order; in alternativa, dopo un fill < amt, cancellare l'ordine per order_id prima di proseguire/uscire. ### Verifica fable B1 VERDETTO: CONFERMATO GRAVITA': alto PERCHE': cerbero-mcp `src/cerbero_mcp/exchanges/deribit/tools.py:160-168` (`PlaceOrderReq`: instrument_name, side, amount, type, price, reduce_only, post_only, label, leverage — NESSUN `time_in_force`) e `client.py:1637-1670` (`place_order` non manda `time_in_force` a `private/buy|sell`) -> Deribit applica il default `good_til_cancelled`. In PG `scripts/live/usde_convert.py:249-252` payload senza tif/post_only; unico `cancel_order` del repo e' `src/live/execution.py:193` (bracket dei perp), mai chiamato su USDE_USDC; `usde_convert.py:277-278` su `filled<=0` esce con l'ordine vivo, e su fill parziale prosegue ricalcolando il delta dai saldi (`leggi_stato` legge `equity`, che include l'USDE impegnato). `cuscino_watch.py:161-165`: da SCOPERTO riprova dopo `RIPRISTINO_MIN_ORE` 6h col delta pieno. Il gateway espone `/tools/cancel_order` (`routers/deribit.py:263`) ma PG non lo usa qui. Gateway non modificato dalla PRESUNZIONE del revisore: confermata. CORREZIONE: la proposta «passare time_in_force nel place_order» NON FUNZIONA cosi' com'e': `PlaceOrderReq` e' un BaseModel pydantic senza `extra="allow"` (solo i modelli alle righe 28 e 43 lo hanno) -> il campo verrebbe IGNORATO in silenzio e l'ordine resterebbe GTC. Due strade: (a) aggiungere `time_in_force: str | None` a `PlaceOrderReq` + `client.place_order` in cerbero-mcp e poi passarlo da PG; (b) solo in PG, senza toccare il gateway: dopo ogni ordine con `filled < amt` chiamare `/mcp-deribit/tools/cancel_order` con `order["order_id"]` (e a inizio di `usde_convert --esegui` cancellare gli ordini aperti su USDE_USDC via `get_open_orders`, che `DeribitRead.open_orders` gia' interroga ma solo per gli strumenti di `_CONTRACT`). (b) e' verificabile con un test sul mock del client; (a) e' la soluzione pulita ma tocca un secondo repo.
Adriano added the bug label 2026-09-27 13:12:12 +00:00
Author
Owner

ANALISI (passo 1, Opus) — unita' U3, ramo lavoro/goldrake-u3, repo gitea:Adriano/PythagorasGoal
RIF: #26 (B1), #29 (B2, B8), #31 (B7), #35 (solo B3, B4)
FILE: scripts/live/usde_convert.py, scripts/live/usde_watch.py, scripts/live/fee_watch.py, src/live/venue_watch.py, scripts/live/venue_watch.py, tests/

  1. #26 — usde_convert: ordini limit GTC mai cancellati. Il gateway cerbero-mcp NON accetta time_in_force (PlaceOrderReq senza il campo, extra ignorati: gitea:Adriano/Cerbero-mcp#1), quindi la correzione e' tutta in PG:
    a) funzione cancella_residui(c) -> list[str]: legge gli ordini aperti sullo spot U.SPOT (USDE_USDC) e cancella SOLO quelli con label che inizia per "usde-quota-". Per sapere come chiedere gli ordini aperti di uno spot leggi (SOLA LETTURA) /opt/docker/cerbero-mcp/src/cerbero_mcp/exchanges/deribit/tools.py e client.py (get_open_orders: parametri accettati; cancel_order: {"order_id"}). Se la lettura fallisce -> solleva (non si ordina al buio).
    b) in main() con --esegui: chiamare cancella_residui PRIMA del ciclo (stampare quanti) e, nel ciclo, dopo ogni ordine con filled < amt cancellare quell'order_id (c._post("/mcp-deribit/tools/cancel_order", {"order_id": ...})), registrarlo nel record (cancellato: true/errore). Se la cancel fallisce: stampa, registra e FERMA il ciclo (break) — mai un secondo ordine con un residuo vivo.
    c) il record finale e l'exit code non cambiano significato.
  2. #29 — usde_watch.py (B2) e fee_watch.py (B8): lo stato si salva prima dell'invio e il ritorno di notify() non si legge. Leggi src/live/notifier.py per sapere cosa ritorna notify (bool? solleva?). Schema di cuscino_watch.py (leggilo: e' il modello): il record porta allertati = condizioni il cui invio e' riuscito; nuove = att_now - {c in att_prev se c in prev.get("allertati", att_prev)} — compatibilita': un record vecchio senza allertati vale come tutto allertato. fee_watch: save_state solo se l'invio (quando serviva) e' riuscito.
  3. #31 — venue_watch BLIND: severity resta None e il titolo diventa 🚨 "PRIMO PASSO: prelievo di prova". Dare a BLIND una severity propria "blind" con titolo "⚠️ venue_watch CIECO — referenze non leggibili, nessun giudizio sul venue" in scripts/live/venue_watch.py::_titolo. La ripetizione oraria di BLIND e' voluta (commento esistente): non toccarla.
  4. #35 B3/B4 — usde_watch: togliere DECISIONE_QUOTA_DAL / quota_da_decidere e la notify "la decisione di QUOTA e' dovuta" (la quota e' derivata dal cuscino dal 10/09, config usde._nota_cuscino); testo IDONEO: usare c["quota_max_frac"] invece di "50%" cablato; la riga "margine" non deve dire "conto SEGREGATO": il modello e' Cross X:SM dal 31/08 — riscrivere la riga come "margine: ~$X USDC + USDE scontato (cross X:SM)" usando i numeri che la funzione ha gia', senza inventarne.

TEST (tests/test_usde_u3.py ecc., client finto, nessuna rete, time.sleep patchato):

  • ordine con filled 0 -> cancel chiamata con quell'order_id, ciclo fermo; cancel che solleva -> break, registrato.
  • residuo "usde-quota-0.64" aperto all'avvio -> cancellato; ordine con altra label -> NON toccato.
  • usde_watch: notify fallito -> al giro dopo la stessa condizione si rinvia; notify riuscito -> non si ripete.
  • fee_watch: invio fallito -> stato non salvato.
  • venue_watch BLIND -> titolo senza "prelievo".
  • usde_watch non emette piu' QUOTA_DA_DECIDERE.
    Suite completa: cd <worktree> && /opt/docker/PythagorasGoal/.venv/bin/python -m pytest -q -p no:cacheprovider (baseline 1098 passed, 3 skipped).

NON TOCCARE: src/live/usde.py (formula del cuscino: e' #28, altra unita'), cuscino_watch.py (salvo leggerlo), config/live.json, /opt/docker/cerbero-mcp (sola lettura), /opt/docker/PythagorasGoal. Nessuna rete, nessun ordine, nessun Telegram (notify va mockato nei test).

ANALISI (passo 1, Opus) — unita' U3, ramo lavoro/goldrake-u3, repo gitea:Adriano/PythagorasGoal RIF: #26 (B1), #29 (B2, B8), #31 (B7), #35 (solo B3, B4) FILE: scripts/live/usde_convert.py, scripts/live/usde_watch.py, scripts/live/fee_watch.py, src/live/venue_watch.py, scripts/live/venue_watch.py, tests/ 1. #26 — usde_convert: ordini limit GTC mai cancellati. Il gateway cerbero-mcp NON accetta time_in_force (PlaceOrderReq senza il campo, extra ignorati: gitea:Adriano/Cerbero-mcp#1), quindi la correzione e' tutta in PG: a) funzione `cancella_residui(c) -> list[str]`: legge gli ordini aperti sullo spot U.SPOT (USDE_USDC) e cancella SOLO quelli con label che inizia per "usde-quota-". Per sapere come chiedere gli ordini aperti di uno spot leggi (SOLA LETTURA) /opt/docker/cerbero-mcp/src/cerbero_mcp/exchanges/deribit/tools.py e client.py (get_open_orders: parametri accettati; cancel_order: {"order_id"}). Se la lettura fallisce -> solleva (non si ordina al buio). b) in main() con --esegui: chiamare cancella_residui PRIMA del ciclo (stampare quanti) e, nel ciclo, dopo ogni ordine con filled < amt cancellare quell'order_id (`c._post("/mcp-deribit/tools/cancel_order", {"order_id": ...})`), registrarlo nel record (`cancellato: true/errore`). Se la cancel fallisce: stampa, registra e FERMA il ciclo (break) — mai un secondo ordine con un residuo vivo. c) il record finale e l'exit code non cambiano significato. 2. #29 — usde_watch.py (B2) e fee_watch.py (B8): lo stato si salva prima dell'invio e il ritorno di notify() non si legge. Leggi src/live/notifier.py per sapere cosa ritorna notify (bool? solleva?). Schema di cuscino_watch.py (leggilo: e' il modello): il record porta `allertati` = condizioni il cui invio e' riuscito; `nuove = att_now - {c in att_prev se c in prev.get("allertati", att_prev)}` — compatibilita': un record vecchio senza `allertati` vale come tutto allertato. fee_watch: save_state solo se l'invio (quando serviva) e' riuscito. 3. #31 — venue_watch BLIND: severity resta None e il titolo diventa 🚨 "PRIMO PASSO: prelievo di prova". Dare a BLIND una severity propria "blind" con titolo "⚠️ venue_watch CIECO — referenze non leggibili, nessun giudizio sul venue" in scripts/live/venue_watch.py::_titolo. La ripetizione oraria di BLIND e' voluta (commento esistente): non toccarla. 4. #35 B3/B4 — usde_watch: togliere DECISIONE_QUOTA_DAL / quota_da_decidere e la notify "la decisione di QUOTA e' dovuta" (la quota e' derivata dal cuscino dal 10/09, config usde._nota_cuscino); testo IDONEO: usare c["quota_max_frac"] invece di "50%" cablato; la riga "margine" non deve dire "conto SEGREGATO": il modello e' Cross X:SM dal 31/08 — riscrivere la riga come "margine: ~$X USDC + USDE scontato (cross X:SM)" usando i numeri che la funzione ha gia', senza inventarne. TEST (tests/test_usde_u3.py ecc., client finto, nessuna rete, time.sleep patchato): - ordine con filled 0 -> cancel chiamata con quell'order_id, ciclo fermo; cancel che solleva -> break, registrato. - residuo "usde-quota-0.64" aperto all'avvio -> cancellato; ordine con altra label -> NON toccato. - usde_watch: notify fallito -> al giro dopo la stessa condizione si rinvia; notify riuscito -> non si ripete. - fee_watch: invio fallito -> stato non salvato. - venue_watch BLIND -> titolo senza "prelievo". - usde_watch non emette piu' QUOTA_DA_DECIDERE. Suite completa: `cd <worktree> && /opt/docker/PythagorasGoal/.venv/bin/python -m pytest -q -p no:cacheprovider` (baseline 1098 passed, 3 skipped). NON TOCCARE: src/live/usde.py (formula del cuscino: e' #28, altra unita'), cuscino_watch.py (salvo leggerlo), config/live.json, /opt/docker/cerbero-mcp (sola lettura), /opt/docker/PythagorasGoal. Nessuna rete, nessun ordine, nessun Telegram (notify va mockato nei test).
Author
Owner

ESITO: fatto sul ramo, non in produzione
RIF: gitea:Adriano/PythagorasGoal#26
COSA: correzione su lavoro/goldrake (pushato), analisi Opus, codice Sonnet, controllo finale Opus
FILE: commit f0193f0 7234625
PROVA: /home/adriano/lavoro/PythagorasGoal pytest -q → 1166 passed, 3 skipped
MANCA: merge su main — richiede ok esplicito di Adriano (libro vivo). Chiusura con --soluzione dopo il merge.
PROSSIMO: ok di Adriano → merge lavoro/goldrake su main → git pull in /opt/docker/PythagorasGoal

ESITO: fatto sul ramo, non in produzione RIF: gitea:Adriano/PythagorasGoal#26 COSA: correzione su lavoro/goldrake (pushato), analisi Opus, codice Sonnet, controllo finale Opus FILE: commit f0193f0 7234625 PROVA: /home/adriano/lavoro/PythagorasGoal pytest -q → 1166 passed, 3 skipped MANCA: merge su main — richiede ok esplicito di Adriano (libro vivo). Chiusura con --soluzione dopo il merge. PROSSIMO: ok di Adriano → merge lavoro/goldrake su main → git pull in /opt/docker/PythagorasGoal
Adriano added the attesa:adriano label 2026-09-27 14:17:40 +00:00
Author
Owner

Soluzione

scripts/live/usde_convert.py: cancella_residui all'avvio (USDC+USDE, solo label usde-quota-), cancel dell'ordine parziale nel ciclo, stop se il cancel fallisce.
COMMIT: f0193f0 7234625, su main dal 27/09/2026 (merge con ok di Adriano). Produzione aggiornata.

**Soluzione** scripts/live/usde_convert.py: cancella_residui all'avvio (USDC+USDE, solo label usde-quota-), cancel dell'ordine parziale nel ciclo, stop se il cancel fallisce. COMMIT: f0193f0 7234625, su main dal 27/09/2026 (merge con ok di Adriano). Produzione aggiornata.
Adriano removed the attesa:adriano label 2026-09-27 14:27:57 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Adriano/PythagorasGoal#26