nautechsystems/nautilus_trader · error

Cannot modify order without venue_order_id

Error message

Cannot modify order without venue_order_id

What it means

modify_order maps to a Betfair bet update identified by bet_id, taken from the command's venue_order_id. The ModifyOrder command arrived with venue_order_id None, so there is no bet to reference and the call fails before the API is contacted. Usually this means the order was modified before the venue acknowledged it (bet_id not yet assigned or not yet visible in the cache).

Source

Thrown at crates/adapters/betfair/src/execution.rs:2123

                    "Cancel {client_order_id} failed without per-order result, awaiting OCM reconciliation: {reason}",
                );
            }

            Ok(())
        });

        Ok(())
    }

    fn modify_order(&self, cmd: ModifyOrder) -> anyhow::Result<()> {
        self.process_pending_resync();

        let instrument_id = cmd.instrument_id;
        let market_id = extract_market_id(&instrument_id)?;

        let venue_order_id = cmd
            .venue_order_id
            .ok_or_else(|| anyhow::anyhow!("Cannot modify order without venue_order_id"))?;
        let bet_id: BetId = venue_order_id.to_string();

        // Compare against existing order to determine actual changes
        let existing_order = self.core.get_order(&cmd.client_order_id);
        let has_price_change = match (&cmd.price, &existing_order) {
            (Some(new_price), Ok(order)) => order.price() != Some(*new_price),
            (Some(_), Err(_)) => true,
            (None, _) => false,
        };
        let has_quantity_change = match (&cmd.quantity, &existing_order) {
            (Some(new_qty), Ok(order)) => order.quantity() != *new_qty,
            (Some(_), Err(_)) => true,
            (None, _) => false,
        };

        // Betfair does not support atomic price+quantity modification
        if has_price_change && has_quantity_change {
            let ts_event = self.clock.get_time_ns();

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Wait until the order reaches Accepted status with a populated venue_order_id (gate on OrderAccepted events or cache lookups) before sending modifies.
  2. Use NautilusTrader's standard cancel/replace flow, which resolves the venue id through the cache.
  3. When building ModifyOrder manually, fetch the venue order id from the cache and include it.

Example fix

// before
engine.submit_order(&order);
let modify = factory.modify(&order, Some(new_price), None).build()?;
engine.modify_order(&modify); // venue_order_id still None

// after
engine.submit_order(&order);
// in the OrderAccepted event handler:
if let Some(venue_id) = event.order().venue_order_id() {
    let modify = factory.modify(&order, Some(new_price), None).build()?;
    engine.modify_order(&modify);
}
Defensive patterns

Strategy: validation

Validate before calling

// before modifying, confirm the venue has acknowledged the order
if let Ok(order) = cache.get_order(&client_order_id) {
    if order.venue_order_id().is_none() {
        log::warn!("order {client_order_id} not yet acknowledged; deferring modify");
        return Ok(());
    }
}

Type guard

fn order_has_venue_id(order: &OrderAny) -> bool {
    order.venue_order_id().is_some()
}

Prevention

When it happens

Trigger: Issuing modify_order in the same event/tick as submit_order, before an OrderAccepted/OrderUpdated event carrying the venue bet_id has been processed; or manually constructing ModifyOrder commands without setting venue_order_id.

Common situations: Fast strategies repricing immediately after entry; races between an in-flight submit acknowledgment and a pending modify after reconnect or reconciliation.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16). Data as JSON: /api/errors/b4f24b6afe5760d2. Report an issue: GitHub.