nautechsystems/nautilus_trader · warning · anyhow::Error

Missing exchange in record: {e}

Error message

Missing exchange in record: {e}

What it means

When `use_exchange_as_venue` is enabled and the instrument's venue is GLBX, the adapter replaces the venue with the record's specific exchange code. If the raw DBN InstrumentDefMsg has no exchange field populated, `msg.exchange()` fails and this error wraps it. It indicates malformed/absent data in the record rather than an API call failure.

Source

Thrown at crates/adapters/databento/src/historical.rs:320

        let metadata = decoder.metadata().clone();
        let mut metadata_cache = MetadataCache::new(metadata);
        let mut instruments = Vec::new();

        while let Some(msg) = decoder.decode_record::<dbn::InstrumentDefMsg>().await? {
            let record = dbn::RecordRef::from(msg);
            let sym_map = self.symbol_venue_map.load();
            let mut instrument_id = decode_nautilus_instrument_id(
                &record,
                &mut metadata_cache,
                &self.publisher_venue_map,
                &sym_map,
            )?;

            if self.use_exchange_as_venue && instrument_id.venue == Venue::GLBX() {
                let exchange = msg
                    .exchange()
                    .map_err(|e| anyhow::anyhow!("Missing exchange in record: {e}"))?;
                let venue = Venue::from_code(exchange)
                    .map_err(|e| anyhow::anyhow!("Venue not found for exchange {exchange}: {e}"))?;
                instrument_id.venue = venue;
            }

            match decode_instrument_def_msg(msg, instrument_id, None, None) {
                Ok(Some(instrument)) => instruments.push(instrument),
                Ok(None) => {} // Decoder logged a warning for the unsupported class
                Err(e) => anyhow::bail!("Failed to decode instrument {instrument_id}: {e}"),
            }
        }

        for instrument in &instruments {
            self.price_precisions
                .insert(instrument.id().symbol, instrument.price_precision());
        }

        Ok(instruments)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Disable `use_exchange_as_venue` (pass false) so the default GLBX venue is kept
  2. Check the dataset/record actually corresponds to CME Globex data with exchange codes
  3. Update the databento/dbn crates to the latest version and refetch
  4. Inspect the raw record to confirm the exchange field is genuinely empty

Example fix

// before
let client = DatabentoHistoricalClient::new(cred, path, clock, true)?; // use_exchange_as_venue = true
// after
let client = DatabentoHistoricalClient::new(cred, path, clock, false)?;
Defensive patterns

Strategy: fallback

Validate before calling

// if records may lack exchange codes, do not request per-exchange venues
let use_exchange_as_venue = false;

Type guard

fn msg_has_exchange(msg: &dbn::InstrumentDefMsg) -> bool { msg.exchange().is_ok() }

Try / catch

let venue = match msg.exchange() {
    Ok(code) => Venue::from_code(code).unwrap_or(Venue::GLBX()),
    Err(_) => Venue::GLBX(),
};

Prevention

When it happens

Trigger: Calling `get_range_instruments` with `use_exchange_as_venue = true` on a GLBX instrument definition record whose `exchange` field is empty/missing.

Common situations: Datasets or record versions where the exchange field is not populated; decoding non-Globex data that was tagged GLBX; changes in DBN schema between crate versions.

Related errors


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