live/deribit,execution: open_orders non ingoia gli errori; ensure_disaster_sl -> unknown (#24)
open_orders solleva RuntimeError se 'all' o 'trigger_all' falliscono, invece di ritornare []
(un bracket disaster-SL perso su 'trigger_all' e' lo stesso difetto di una posizione persa su
'all'). ensure_disaster_sl avvolge la lettura di posizione+open_orders in try/except: su
eccezione ritorna {"state": "unknown", "notes": ...} senza cancellare ne' piazzare, distinto
da "naked" (protezione tolta e non rimessa). shadow.shadow_report chiama open_orders gia'
dentro try/except (non toccato).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,10 @@ che dovrebbe farle — non discusse.
|
||||
ordine e prezzo dello stop dal segno sbagliato.
|
||||
#18. Tetto per ordine d'apertura: `MAX_AMOUNT` era un tetto FISSO uguale per tutti i conti;
|
||||
ora il libro deriva il proprio tetto dal cap di config via `set_open_cap()`.
|
||||
#24. `DeribitRead.open_orders` ingoiava gli errori di ENTRAMBE le chiamate ('all' e
|
||||
'trigger_all') e ritornava []: un bracket disaster-SL perso passava per "nessun bracket".
|
||||
Ora solleva, e `ensure_disaster_sl` distingue lo stato "unknown" (letture fallite, non si
|
||||
cancella ne' si piazza) da "naked" (protezione tolta e non rimessa).
|
||||
|
||||
Nessun test tocca la rete.
|
||||
"""
|
||||
@@ -158,3 +162,63 @@ def test_senza_set_open_cap_resta_il_default_max_amount():
|
||||
t._submit(ETH, "buy", dentro, reduce_only=False, label="book-open") # non deve solllevare
|
||||
with pytest.raises(GuardrailError):
|
||||
t._submit(ETH, "buy", MAX_AMOUNT[ETH] * 3, reduce_only=False, label="book-open")
|
||||
|
||||
|
||||
# =================================================================================================
|
||||
# #24 — open_orders non ingoia gli errori; ensure_disaster_sl diventa "unknown", non a caso.
|
||||
# =================================================================================================
|
||||
class _FakeOpenOrders(DeribitRead):
|
||||
"""DeribitRead senza rete: 'all' risponde vuoto, 'trigger_all' solleva — replica un bracket
|
||||
disaster-SL perso su un solo dei due tipi di chiamata."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass # niente token, niente rete
|
||||
|
||||
def _post(self, path, payload):
|
||||
if payload.get("type") == "trigger_all":
|
||||
raise RuntimeError("502 Server Error: Bad Gateway")
|
||||
return {"result": []}
|
||||
|
||||
|
||||
def test_open_orders_trigger_all_solleva_runtime_error():
|
||||
r = _FakeOpenOrders()
|
||||
with pytest.raises(RuntimeError, match="open_orders illeggibile"):
|
||||
r.open_orders(ETH)
|
||||
|
||||
|
||||
class _FakeTraderOpenOrdersRompe(DeribitTrader):
|
||||
"""position_usd legge una posizione REALE, ma open_orders solleva: ensure_disaster_sl non
|
||||
deve decidere su meta' dei dati."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._open_cap: dict[str, float] = {}
|
||||
self.submitted: list[dict] = []
|
||||
self.cancelled: list[str] = []
|
||||
|
||||
def positions(self, currency):
|
||||
return [{"instrument_name": ETH, "size": 560, "direction": "sell"}]
|
||||
|
||||
def mark_price(self, instrument):
|
||||
return 2694.0
|
||||
|
||||
def open_orders(self, instrument):
|
||||
raise RuntimeError("open_orders illeggibile: RuntimeError: 502 Server Error: Bad Gateway")
|
||||
|
||||
def cancel_order(self, order_id):
|
||||
self.cancelled.append(order_id)
|
||||
return {}
|
||||
|
||||
def _submit(self, instrument, side, amount, *, reduce_only, label,
|
||||
order_type="market", price=None):
|
||||
self.submitted.append(dict(side=side, reduce_only=reduce_only))
|
||||
return Fill(instrument=instrument, side=side, amount=amount, filled=amount, price=price,
|
||||
fee_usdc=0.0, order_id="1", state="filled", verified=True)
|
||||
|
||||
|
||||
def test_ensure_disaster_sl_con_open_orders_che_solleva_diventa_unknown_senza_agire():
|
||||
t = _FakeTraderOpenOrdersRompe()
|
||||
ds = t.ensure_disaster_sl(ETH, 0.30)
|
||||
assert ds["state"] == "unknown"
|
||||
assert "502" in ds["notes"]
|
||||
assert t.cancelled == [] # nessuna cancel
|
||||
assert t.submitted == [] # nessun piazzamento
|
||||
|
||||
Reference in New Issue
Block a user