open_orders ingoia gli errori: 'non letto' diventa 'nessun bracket' -> disaster-SL doppio o orfani #24

Closed
opened 2026-09-27 13:12:11 +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 A5 [bug]

FILE: PG:src/live/deribit.py:252-267 (open_orders) usato da execution.py:220 (ensure_disaster_sl)
COSA: open_orders ingoia gli errori di entrambe le chiamate e ritorna [] : "non letto" diventa "nessun bracket".
SCENARIO: posizione long aperta, bracket esistente, get_open_orders 502 su entrambi i tipi -> brackets=[] -> si piazza un SECONDO stop reduce_only; giro dopo len==2 -> cancella entrambi e ripiazza (finestra scoperta). A posizione flat con lettura fallita i bracket orfani non vengono cancellati e il giro riporta "flat" come se fosse pulito.
PROVA: VERIFICATO: lettura deribit.py:257-266 (try/except: pass per tipo) ed execution.py:219-224.
PROPOSTA: se tutte e due le chiamate falliscono sollevare (o ritornare None) e in ensure_disaster_sl ritornare stato "unknown" senza piazzare.

Verifica fable A5 VERDETTO: CONFERMATO

GRAVITA': medio
PERCHE': deribit.py:257-266 try/except Exception: pass per ciascun tipo -> [] anche se entrambe le chiamate falliscono; execution.py:219-224 tratta [] come "nessun bracket": a posizione aperta piazza un secondo stop; a flat con lettura fallita non cancella gli orfani e ritorna "flat". Danno contenuto: due stop reduce_only sono ridondanti e il giro dopo ricostruisce (finestra scoperta breve gestita da naked).
CORREZIONE: ok (ritornare None/sollevare se tutte le chiamate falliscono; ensure_disaster_sl -> stato "unknown").

Revisione 2026-09-27 (goldrake): rilievo Opus 5.5, verifica avversariale fable. Sola lettura, nessun codice toccato. ### Rilievo A5 [bug] FILE: PG:src/live/deribit.py:252-267 (open_orders) usato da execution.py:220 (ensure_disaster_sl) COSA: open_orders ingoia gli errori di entrambe le chiamate e ritorna [] : "non letto" diventa "nessun bracket". SCENARIO: posizione long aperta, bracket esistente, get_open_orders 502 su entrambi i tipi -> brackets=[] -> si piazza un SECONDO stop reduce_only; giro dopo len==2 -> cancella entrambi e ripiazza (finestra scoperta). A posizione flat con lettura fallita i bracket orfani non vengono cancellati e il giro riporta "flat" come se fosse pulito. PROVA: VERIFICATO: lettura deribit.py:257-266 (try/except: pass per tipo) ed execution.py:219-224. PROPOSTA: se tutte e due le chiamate falliscono sollevare (o ritornare None) e in ensure_disaster_sl ritornare stato "unknown" senza piazzare. ### Verifica fable A5 VERDETTO: CONFERMATO GRAVITA': medio PERCHE': deribit.py:257-266 `try/except Exception: pass` per ciascun tipo -> [] anche se entrambe le chiamate falliscono; execution.py:219-224 tratta [] come "nessun bracket": a posizione aperta piazza un secondo stop; a flat con lettura fallita non cancella gli orfani e ritorna "flat". Danno contenuto: due stop reduce_only sono ridondanti e il giro dopo ricostruisce (finestra scoperta breve gestita da `naked`). CORREZIONE: ok (ritornare None/sollevare se tutte le chiamate falliscono; ensure_disaster_sl -> stato "unknown").
Adriano added the bug label 2026-09-27 13:12:11 +00:00
Author
Owner

ANALISI (passo 1, Opus) — unita' U1, ramo lavoro/goldrake-u1, repo gitea:Adriano/PythagorasGoal
RIF: #17 (D1), #18 (A1), #24 (A5)
FILE: src/live/deribit.py, src/live/execution.py, tests/ (nuovo tests/test_execution_u1.py)

  1. #17 — DeribitRead.position_usd (src/live/deribit.py ~238): il gateway manda size in abs() e direction a parte.
    Nuova regola: s = float(size or size_currency or 0); dir = str(p.get("direction","")).lower();
    dir in ("sell","short") -> -abs(s); dir in ("buy","long") -> abs(s); altrimenti (assente/"zero") -> s cosi' com'e' (compatibile con un gateway che un giorno mandi il segno).
    Cercare con grep in src/ e scripts/live/ (escluso scripts/research, Old) ogni altro lettore di positions( o di ["size"]/.get("size") di una posizione e applicare la stessa regola SOLO se legge posizioni perp del libro; elencare nel rapporto quelli trovati.
    Verifica a valle: close() e ensure_disaster_sl() decidono lato e stop da pos > 0: con il segno giusto uno short produce close BUY reduce_only e stop SOPRA il mark (disaster_stop_price long=False). Test che lo provano passando per positions() mockato (NON mockare position_usd).
  2. #18 — tetto per ordine d'apertura: MAX_AMOUNT {BTC 0.01, ETH 0.25} resta come DEFAULT (microtest lo usa), ma DeribitTrader acquista un tetto per strumento derivato dal cap di config:
    • attributo self._open_cap: dict[str, float] = {}; metodo set_open_cap(instrument, usd_cap, mark) -> amount_max = 1.10 * usd_cap / mark (usd_cap>0, mark>0 altrimenti ValueError).
    • _submit sul ramo non reduce_only usa self._open_cap.get(instrument, MAX_AMOUNT[instrument]).
    • NON toccare book_execute.py (lo fa l'unita' U2, che chiamera' trader.set_open_cap(inst, r["cap_per_asset"] * r["scala"], mark) prima di rebalance_signed). Scrivi nel rapporto la firma esatta.
    • aggiornare il commento di MAX_AMOUNT e del docstring del modulo (niente "micro-test / deploy gated": il libro e' armato dal 23/06/2026).
  3. #24 — DeribitRead.open_orders: oggi ingoia gli errori e ritorna []. Nuovo: se UNA delle due chiamate ("all", "trigger_all") solleva, sollevare RuntimeError("open_orders illeggibile: : ") — un bracket perso su trigger_all e' lo stesso difetto.
    ensure_disaster_sl: la lettura di posizione+open_orders in try; su eccezione ritorna {"state": "unknown", "notes": ": "} SENZA cancellare ne' piazzare. Aggiornare il docstring (stati possibili).
    Chiamanti di open_orders: grep; shadow.shadow_report lo chiama gia' dentro try/except (ok, non toccare shadow.py: e' di U2).

TEST (tutti in tests/test_execution_u1.py, client finto che sostituisce _post/positions, nessuna rete):

  • position_usd con {"size": 560, "direction": "sell"} -> -560; "buy" -> +560; senza direction e size -3 -> -3.
  • close() su short -> _submit con side "buy", reduce_only.
  • ensure_disaster_sl su short -> stop sopra il mark, side "buy".
  • set_open_cap: equity 4583, cap/asset 2291.5, ETH mark 2694 -> apertura di 0,2807 ETH NON solleva; apertura di 3x il tetto solleva GuardrailError; senza set_open_cap il default MAX_AMOUNT resta.
  • open_orders: "trigger_all" solleva -> RuntimeError; ensure_disaster_sl con open_orders che solleva -> state "unknown", nessuna cancel/place chiamata.
    Suite completa: cd <worktree> && /opt/docker/PythagorasGoal/.venv/bin/python -m pytest -q -p no:cacheprovider -> baseline 1098 passed, 3 skipped. Nessun test esistente deve rompersi; se uno esistente blinda il comportamento vecchio, aggiornalo e dillo.

NON TOCCARE: scripts/live/book_execute.py, src/live/shadow.py, src/live/book.py (U2); config/live.json; tutto sotto /opt/docker/PythagorasGoal (produzione); nessuna chiamata di rete, nessun ordine.

ANALISI (passo 1, Opus) — unita' U1, ramo lavoro/goldrake-u1, repo gitea:Adriano/PythagorasGoal RIF: #17 (D1), #18 (A1), #24 (A5) FILE: src/live/deribit.py, src/live/execution.py, tests/ (nuovo tests/test_execution_u1.py) 1. #17 — DeribitRead.position_usd (src/live/deribit.py ~238): il gateway manda `size` in abs() e `direction` a parte. Nuova regola: s = float(size or size_currency or 0); dir = str(p.get("direction","")).lower(); dir in ("sell","short") -> -abs(s); dir in ("buy","long") -> abs(s); altrimenti (assente/"zero") -> s cosi' com'e' (compatibile con un gateway che un giorno mandi il segno). Cercare con grep in src/ e scripts/live/ (escluso scripts/research, Old) ogni altro lettore di `positions(` o di `["size"]`/`.get("size")` di una posizione e applicare la stessa regola SOLO se legge posizioni perp del libro; elencare nel rapporto quelli trovati. Verifica a valle: close() e ensure_disaster_sl() decidono lato e stop da `pos > 0`: con il segno giusto uno short produce close BUY reduce_only e stop SOPRA il mark (disaster_stop_price long=False). Test che lo provano passando per positions() mockato (NON mockare position_usd). 2. #18 — tetto per ordine d'apertura: MAX_AMOUNT {BTC 0.01, ETH 0.25} resta come DEFAULT (microtest lo usa), ma DeribitTrader acquista un tetto per strumento derivato dal cap di config: - attributo `self._open_cap: dict[str, float] = {}`; metodo `set_open_cap(instrument, usd_cap, mark)` -> amount_max = 1.10 * usd_cap / mark (usd_cap>0, mark>0 altrimenti ValueError). - `_submit` sul ramo non reduce_only usa `self._open_cap.get(instrument, MAX_AMOUNT[instrument])`. - NON toccare book_execute.py (lo fa l'unita' U2, che chiamera' `trader.set_open_cap(inst, r["cap_per_asset"] * r["scala"], mark)` prima di rebalance_signed). Scrivi nel rapporto la firma esatta. - aggiornare il commento di MAX_AMOUNT e del docstring del modulo (niente "micro-test / deploy gated": il libro e' armato dal 23/06/2026). 3. #24 — DeribitRead.open_orders: oggi ingoia gli errori e ritorna []. Nuovo: se UNA delle due chiamate ("all", "trigger_all") solleva, sollevare RuntimeError("open_orders illeggibile: <tipo>: <errore>") — un bracket perso su trigger_all e' lo stesso difetto. ensure_disaster_sl: la lettura di posizione+open_orders in try; su eccezione ritorna {"state": "unknown", "notes": "<Tipo>: <msg>"} SENZA cancellare ne' piazzare. Aggiornare il docstring (stati possibili). Chiamanti di open_orders: grep; shadow.shadow_report lo chiama gia' dentro try/except (ok, non toccare shadow.py: e' di U2). TEST (tutti in tests/test_execution_u1.py, client finto che sostituisce _post/positions, nessuna rete): - position_usd con {"size": 560, "direction": "sell"} -> -560; "buy" -> +560; senza direction e size -3 -> -3. - close() su short -> _submit con side "buy", reduce_only. - ensure_disaster_sl su short -> stop sopra il mark, side "buy". - set_open_cap: equity 4583, cap/asset 2291.5, ETH mark 2694 -> apertura di 0,2807 ETH NON solleva; apertura di 3x il tetto solleva GuardrailError; senza set_open_cap il default MAX_AMOUNT resta. - open_orders: "trigger_all" solleva -> RuntimeError; ensure_disaster_sl con open_orders che solleva -> state "unknown", nessuna cancel/place chiamata. Suite completa: `cd <worktree> && /opt/docker/PythagorasGoal/.venv/bin/python -m pytest -q -p no:cacheprovider` -> baseline 1098 passed, 3 skipped. Nessun test esistente deve rompersi; se uno esistente blinda il comportamento vecchio, aggiornalo e dillo. NON TOCCARE: scripts/live/book_execute.py, src/live/shadow.py, src/live/book.py (U2); config/live.json; tutto sotto /opt/docker/PythagorasGoal (produzione); nessuna chiamata di rete, nessun ordine.
Author
Owner

ESITO: fatto sul ramo, non in produzione
RIF: gitea:Adriano/PythagorasGoal#24
COSA: correzione su lavoro/goldrake (pushato), analisi Opus, codice Sonnet, controllo finale Opus
FILE: commit 7c72308 841d4c7
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#24 COSA: correzione su lavoro/goldrake (pushato), analisi Opus, codice Sonnet, controllo finale Opus FILE: commit 7c72308 841d4c7 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:39 +00:00
Author
Owner

Soluzione

src/live/deribit.py open_orders solleva su lettura fallita; ensure_disaster_sl -> stato unknown; book_execute: notify 'non verificabile', lista non_verificati, exit 2.
COMMIT: 7c72308 841d4c7, su main dal 27/09/2026 (merge con ok di Adriano). Produzione aggiornata.

**Soluzione** src/live/deribit.py open_orders solleva su lettura fallita; ensure_disaster_sl -> stato unknown; book_execute: notify 'non verificabile', lista non_verificati, exit 2. COMMIT: 7c72308 841d4c7, su main dal 27/09/2026 (merge con ok di Adriano). Produzione aggiornata.
Adriano removed the attesa:adriano label 2026-09-27 14:27:56 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Adriano/PythagorasGoal#24