nautechsystems/nautilus_trader · error · anyhow::Error

Cannot cache futures spread: underlying {underlying_instrume

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.

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)

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.