nautechsystems/nautilus_trader · error · anyhow::Error

`exchange` order param must be a string

Error message

`exchange` order param must be a string

What it means

When building an IB contract from order params, the adapter reads an optional 'exchange' entry from the params map. If the key exists but its value is not a JSON string (e.g. a number, bool, array, or object), the adapter refuses to proceed with this error instead of silently coercing or ignoring the value.

Source

Thrown at crates/adapters/interactive_brokers/src/execution/core_tracking.rs:133

        instrument_provider
            .resolve_contract_for_instrument(instrument_id)
            .context("Failed to convert instrument ID to IB contract")
    }

    pub(super) fn contract_with_order_exchange_param(
        mut contract: ibapi::contracts::Contract,
        params: Option<&nautilus_core::Params>,
    ) -> anyhow::Result<ibapi::contracts::Contract> {
        let Some(params) = params else {
            return Ok(contract);
        };

        let Some(exchange_value) = params.get("exchange") else {
            return Ok(contract);
        };

        let Some(exchange) = exchange_value.as_str() else {
            anyhow::bail!("`exchange` order param must be a string");
        };

        if exchange.is_empty() {
            return Ok(contract);
        }

        contract.exchange = ibapi::contracts::Exchange::from(exchange);
        Ok(contract)
    }

    #[allow(clippy::too_many_arguments)]
    pub(super) fn cache_order_tracking(
        ib_order_id: i32,
        client_order_id: ClientOrderId,
        instrument_id: InstrumentId,
        trader_id: TraderId,
        strategy_id: StrategyId,
        order_side: OrderSide,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure params["exchange"] is a plain string, e.g. "SMART", "NYSE", "NASDAQ".
  2. Fix the upstream config/deserialization so exchange is parsed as a string type, not a number or enum object.
  3. If the value can be legitimately absent, remove the key entirely rather than passing a non-string placeholder.
  4. Coerce known numeric venue codes to their string names before building params.

Example fix

// before
let params = serde_json::json!({ "exchange": 1 });
// after
let params = serde_json::json!({ "exchange": "SMART" });
Defensive patterns

Strategy: validation

Validate before calling

fn validate_exchange_param(params: &serde_json::Map<String, serde_json::Value>) -> Result<(), String> {
    match params.get("exchange") {
        Some(v) if v.as_str().is_some() => Ok(()),
        Some(v) => Err(format!("exchange param must be a string, got: {v}")),
        None => Ok(()), // optional
    }
}

Type guard

fn is_string_param(v: &serde_json::Value) -> Option<&str> {
    v.as_str()
}

Prevention

When it happens

Trigger: Calling the contract-building function (contract_with_order_exchange_param) with an order params map that contains params["exchange"] set to a non-string value, such as a nested object, integer, or null-like typed value.

Common situations: Config files or message payloads where exchange is provided as a numeric enum or parsed incorrectly (e.g. YAML/JSON typed as a number); passing an untagged enum or Option directly instead of a string; deserializing venue config into the wrong type.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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