diff --git a/prod/clean_arch/dita_v2/rust_backend.py b/prod/clean_arch/dita_v2/rust_backend.py index 401febf..95a7b35 100644 --- a/prod/clean_arch/dita_v2/rust_backend.py +++ b/prod/clean_arch/dita_v2/rust_backend.py @@ -793,7 +793,39 @@ class ExecutionKernel: emitted_events = [] all_venue_transitions: List[KernelTransition] = [] if outcome.accepted and intent.action in {KernelCommandType.ENTER, KernelCommandType.EXIT}: - emitted_events = self.venue.submit(intent) + try: + emitted_events = self.venue.submit(intent) + except Exception as _submit_exc: + # venue.submit() failed (e.g. BingX timeout). The Rust FSM already + # advanced to ORDER_REQUESTED / ENTRY_WORKING with no corresponding + # exchange order. Feed a synthetic REJECTED event so the FSM rolls + # back to IDLE — otherwise the slot is stranded and every subsequent + # ENTER with a different trade_id hits SLOT_BUSY forever. + import logging as _log + _log.getLogger(__name__).error( + "venue.submit failed (%s) — feeding synthetic REJECTED to roll back FSM slot=%d action=%s", + _submit_exc, intent.slot_id, intent.action.value, + ) + _reject_event = VenueEvent( + timestamp=datetime.now(timezone.utc), + event_id=f"{intent.trade_id}:submit_error", + trade_id=intent.trade_id, + slot_id=intent.slot_id, + kind=KernelEventKind.ORDER_REJECT, + status=VenueEventStatus.REJECTED, + venue_order_id="", + venue_client_id="", + side=intent.side, + asset=intent.asset, + price=0.0, + size=float(intent.target_size or 0.0), + filled_size=0.0, + remaining_size=float(intent.target_size or 0.0), + reason=f"VENUE_SUBMIT_ERROR:{_submit_exc}", + raw_payload={}, + metadata={"intent_id": intent.intent_id, "action": intent.action.value}, + ) + emitted_events = [_reject_event] for event in emitted_events: evt_outcome = self.on_venue_event(event) all_venue_transitions.extend(evt_outcome.transitions)