nautechsystems/nautilus_trader · error · anyhow::Error

Failed to send order denied event: {e}

Error message

Failed to send order denied event: {e}

What it means

When the adapter converts an IB order-denied notification into an `OrderEventAny::Denied` event, it sends the event over the global `tokio::sync::mpsc::UnboundedSender` obtained from `get_exec_event_sender()`. If the receiver side has been shut down (the execution event loop dropped its receiver), `send` fails and this anyhow error is returned, so the denial cannot be surfaced to the strategy.

Source

Thrown at crates/adapters/interactive_brokers/src/execution/core.rs:1926

        instrument_id: InstrumentId,
        client_order_id: ClientOrderId,
        reason: &str,
    ) -> anyhow::Result<()> {
        let ts_event = get_atomic_clock_realtime().get_time_ns();
        let event = OrderDenied::new(
            trader_id,
            strategy_id,
            instrument_id,
            client_order_id,
            Ustr::from(reason),
            UUID4::new(),
            ts_event,
            ts_event,
        );

        get_exec_event_sender()
            .send(ExecutionEvent::Order(OrderEventAny::Denied(event)))
            .map_err(|e| anyhow::anyhow!("Failed to send order denied event: {e}"))
    }

    fn send_order_modify_rejected(
        cmd: &ModifyOrder,
        reason: &str,
        exec_sender: &tokio::sync::mpsc::UnboundedSender<ExecutionEvent>,
        ts_event: UnixNanos,
        account_id: AccountId,
    ) -> anyhow::Result<()> {
        let event = OrderModifyRejected::new(
            cmd.trader_id,
            cmd.strategy_id,
            cmd.instrument_id,
            cmd.client_order_id,
            Ustr::from(reason),
            UUID4::new(),
            ts_event,
            ts_event,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure the node's execution event loop/actor stays alive as long as the IB adapter can deliver events; shut down the adapter before dropping the engine.
  2. Check that `get_exec_event_sender()` is wired to the correct receiver and nothing re-initialized the global channel.
  3. If this occurs at shutdown, treat it as benign log noise, but drain in-flight events before stopping the event loop.

Example fix

// before
let sender = get_exec_event_sender();
sender.send(event).map_err(...)?;
// after
if let Err(e) = get_exec_event_sender().send(event) {
    log::warn!("exec event dropped (receiver closed): {e}");
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before shutdown-sensitive work, confirm the exec channel still has a receiver
fn exec_channel_alive() -> bool { /* true if the execution event loop task is still running */ true }

Try / catch

if let Err(e) = client.submit_order(&cmd).await {
    if e.to_string().contains("Failed to send order denied event") {
        log::warn!("exec receiver closed; order denial lost: {e}");
    } else {
        return Err(e);
    }
}

Prevention

When it happens

Trigger: An order denied by IB arrives (e.g. rejected at submit due to margin/limits) while the execution event receiver has been dropped — i.e. during shutdown, after the actor/event-loop stopped, or a miswired exec event channel.

Common situations: Node shutting down while a rejection arrives in flight; TWS emitting a late deny after `ExecutionEngine` teardown; tests or tools that drop the receiver early.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/68d2fab5a747d542. Report an issue: GitHub.