nautechsystems/nautilus_trader · error

Instrument missing from cache

Error message

Instrument {symbol} missing from cache

What it means

This message covers two cases in OKX report resolution when an instrument is missing from cache: for open instruments or positions (`is_open_or_position`) the adapter hard-fails because a valid instrument is mandatory; for closed/historical instruments it logs a warning and returns `InstrumentResolution::Incomplete`, letting the report skip detailed resolution. The hard bail prevents generating reports for live positions without instrument metadata.

Solutions

  1. Load the missing instrument into the cache before generating reports (include it in the instrument-loading scope).
  2. Check the log line `Skipping report for out-of-scope instrument` to see if the scope filter is excluding it, and widen the scope.
  3. For closed/historical instruments the warning variant is non-fatal — reports proceed as `Incomplete`; verify that is acceptable for your reconciliation.
  4. Confirm the symbol string matches OKX naming (e.g. `BTC-USDT-SWAP`) with no stale/expired contract IDs.

Example fix

// before: scope excludes the instrument with open orders
let scope = InstrumentScope::only("BTC-USDT.OKX");
// after: include all instruments with open interest
let scope = InstrumentScope::all();
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight: every open order/position symbol must resolve in cache
for symbol in open_symbols() {
    let inst_id = InstrumentId::from(format!("{symbol}.OKX"));
    assert!(cache.instrument(&inst_id).is_some(), "{inst_id} missing from cache");
}

Try / catch

// treat Incomplete resolution as skip; hard-fail on open instruments
match resolution {
    InstrumentResolution::Found(inst) => use(inst),
    InstrumentResolution::Incomplete => log::warn!("report incomplete; instrument not cached"),
    InstrumentResolution::Skip | Err => continue,
}

Prevention

When it happens

Trigger: `resolve_instrument`-style lookup finds the symbol absent from the cache while generating order/position reports: an open order/position references an instrument that was never loaded, or was excluded by the scope filter.

Common situations: Cache loaded with a narrow instrument scope that excludes an instrument with open orders/positions; newly listed or delisted instruments; restarts where cached instruments were not restored before report generation.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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

Appendix: source

Thrown at crates/adapters/okx/src/http/client.rs:7025

            return Ok(InstrumentResolution::Found(Box::new(instrument)));
        }

        let in_scope = match scope {
            None => true,
            Some(scope) if is_spread => scope.load_spreads,
            Some(scope) => {
                scope.instrument_types.contains(&inst_type)
                    || scope.instrument_types.contains(&OKXInstrumentType::Any)
            }
        };

        if !in_scope {
            log::debug!("Skipping report for out-of-scope instrument: symbol={symbol}");
            return Ok(InstrumentResolution::Skip);
        }

        if is_open_or_position {
            anyhow::bail!("Instrument {symbol} missing from cache");
        }

        log::warn!("Instrument {symbol} missing from cache");
        Ok(InstrumentResolution::Incomplete)
    }

    pub(crate) async fn request_spread_order_status_report(
        &self,
        account_id: AccountId,
        instrument_id: InstrumentId,
        client_order_id: Option<ClientOrderId>,
        venue_order_id: Option<VenueOrderId>,
    ) -> anyhow::Result<Option<OrderStatusReport>> {
        let instrument = self.instrument_from_cache(instrument_id.symbol.inner())?;
        let mut params_builder = GetSpreadOrderParamsBuilder::default();

        match (client_order_id, venue_order_id) {
            (Some(client_order_id), None) => {

View on GitHub (pinned to 18893faf8b)