nautechsystems/nautilus_trader · error · anyhow::Error

Cannot cache futures spread: option underlyings differ call_

Error message

Cannot cache futures spread: option underlyings differ call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}

What it means

cache_futures_spread requires the call and put legs to be a matched pair on the same underlying: the parity-based implied future price is only meaningful when both options reference the same underlying. If call_underlying != put_underlying the method bails with this error listing both instruments.

Source

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

        {
            anyhow::bail!(
                "Cannot cache futures spread: expected call/put pair call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}"
            );
        }

        let Some(call_underlying) = call_instrument.underlying() else {
            anyhow::bail!(
                "Cannot cache futures spread: missing call underlying for {call_instrument_id}"
            );
        };
        let Some(put_underlying) = put_instrument.underlying() else {
            anyhow::bail!(
                "Cannot cache futures spread: missing put underlying for {put_instrument_id}"
            );
        };

        if call_underlying != put_underlying {
            anyhow::bail!(
                "Cannot cache futures spread: option underlyings differ call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}"
            );
        }

        if call_instrument.strike_price() != put_instrument.strike_price() {
            anyhow::bail!(
                "Cannot cache futures spread: strike prices differ call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}"
            );
        }

        if call_instrument.expiration_ns() != put_instrument.expiration_ns() {
            anyhow::bail!(
                "Cannot cache futures spread: expiration dates differ call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}"
            );
        }

        let reference_future_price = self.get_price_object(&futures_instrument_id).ok_or_else(|| {
            anyhow::anyhow!(

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Group option instruments by underlying and only pass call/put pairs sharing the same underlying.
  2. Compare underlying() on both legs before calling and skip/log mismatched pairs.
  3. Correct the ID source if the pairing was built from a mis-sorted or mis-keyed collection.

Example fix

// before
let price = greeks.cache_futures_spread(esm6_call_id, esz6_put_id, esm6_future_id)?;
// after
if call.underlying() != put.underlying() {
    tracing::warn!("skipping mismatched pair {call_id} / {put_id}");
    return Ok(None);
}
let price = greeks.cache_futures_spread(esm6_call_id, esm6_put_id, esm6_future_id)?;
Defensive patterns

Strategy: validation

Validate before calling

// rust
fn same_underlying(cache: &Cache, call_id: &InstrumentId, put_id: &InstrumentId) -> bool {
    cache.instrument(call_id).and_then(|i| i.underlying()) == cache.instrument(put_id).and_then(|i| i.underlying())
}

Type guard

fn pair_key(cache: &Cache, call_id: &InstrumentId, put_id: &InstrumentId) -> Option<Ustr> {
    let u = cache.instrument(call_id)?.underlying()?;
    (cache.instrument(put_id)?.underlying()? == u).then_some(u)
}

Try / catch

match greeks.cache_futures_spread(call_id, put_id, future_id) {
    Ok(p) => use(p),
    Err(e) if e.to_string().contains("underlyings differ") => {
        tracing::warn!("dropping cross-underlying pair {call_id}/{put_id}");
        Ok(None)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Passing options from different underlyings (e.g. ESM6 options vs ESZ6 options) as the call/put pair; a copy-paste mistake pairing options across contract months or across symbols entirely.

Common situations: Loop code that pairs consecutive option listings without grouping by underlying; building calendars/diagonal-style structures and feeding them to a routine that expects same-strike/same-expiry parity pairs.

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/4c50c5bdaf2658ef. Report an issue: GitHub.