nautechsystems/nautilus_trader · error · anyhow::Error

Cannot cache futures spread: underlying

Error message

Cannot cache futures spread: underlying {underlying_instrument_id} is not a futures contract

What it means

After computing the underlying instrument ID from the options' underlying symbol and venue, cache_futures_spread checks the Cache: if an instrument exists under that ID but its class is not InstrumentClass::Future, the method bails. This guards against caching a futures spread against an underlying that was registered as something else (e.g. an index or spot instrument), which would make the spread meaningless.

Solutions

  1. Ensure the underlying instrument is added to the Cache with InstrumentClass::Future, or remove the non-future instrument occupying that ID.
  2. Check how underlying_instrument_id is composed (underlying symbol + option venue) and confirm it points at the intended futures contract.
  3. Adjust the adapter's instrument classification so the underlying definition loads as a Future.

Example fix

// before
cache.add_instrument(index_def)?; // 'SPX.NYSE' registered as Index
// after
cache.add_instrument(future_def)?; // ensure the underlying ID maps to a Future instrument
Defensive patterns

Strategy: validation

Validate before calling

// rust
let underlying_id = InstrumentId::from(format!("{underlying}.{}", call_id.venue));
let ok = match cache.instrument(&underlying_id) {
    None => true, // unknown is allowed; only wrong class is rejected
    Some(i) => i.instrument_class() == InstrumentClass::Future,
};
if !ok { tracing::error!("{underlying_id} registered as non-future"); }

Type guard

fn is_cached_future(cache: &Cache, id: &InstrumentId) -> bool {
    cache.instrument(id).is_none_or(|i| i.instrument_class() == InstrumentClass::Future)
}

Try / catch

let res = greeks.cache_futures_spread(call_id, put_id, future_id);
if let Err(e) = res {
    if e.to_string().contains("is not a futures contract") {
        fix_underlying_classification(&underlying_id);
    }
}

Prevention

When it happens

Trigger: The underlying symbol+venue resolves to a cached instrument registered as Index/Spot/CurrencyPair instead of Future — e.g. options on an index where the underlying 'ES' was loaded as an index definition, or the underlying ID collides with a non-futures instrument on that venue.

Common situations: Loading both an index and a futures product under similar IDs; adapters that classify the underlying as Spot; venue suffix construction (format!("{underlying}.{venue}")) accidentally matching a different listing.

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/2797a0e01a408f27. Report an issue: GitHub.

Appendix: source

Thrown at crates/common/src/greeks.rs:1141

                "Cannot cache futures spread: missing option price for {call_instrument_id}"
            )
        })?;
        let put_price = self.get_price(&put_instrument_id).ok_or_else(|| {
            anyhow::anyhow!(
                "Cannot cache futures spread: missing option price for {put_instrument_id}"
            )
        })?;

        let underlying_instrument_id =
            InstrumentId::from(format!("{call_underlying}.{}", call_instrument_id.venue));

        // Reject if the underlying is present in cache but is not a future
        {
            let cache = self.cache.borrow();
            if let Some(underlying) = cache.instrument(&underlying_instrument_id)
                && underlying.instrument_class() != InstrumentClass::Future
            {
                anyhow::bail!(
                    "Cannot cache futures spread: underlying {underlying_instrument_id} is not a futures contract"
                );
            }
        }

        let implied_future_price =
            self.calculate_implied_future_price(&call_instrument, call_price, put_price);
        let spread = implied_future_price - reference_future_price.as_f64();
        let spread_price = reference_future_instrument.make_price(spread);

        self.cached_futures_spreads.borrow_mut().insert(
            underlying_instrument_id,
            (futures_instrument_id, spread_price),
        );

        Ok(reference_future_price + spread_price)
    }

View on GitHub (pinned to 18893faf8b)