nautechsystems/nautilus_trader · error · anyhow::Error

Cannot cache futures spread: expected call/put pair call_ins

Error message

Cannot cache futures spread: expected call/put pair call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}

What it means

After confirming both instruments are options, cache_futures_spread checks option_kind(): the first must be OptionKind::Call and the second OptionKind::Put. If either is the wrong kind (put passed as call, two calls, two puts) the method bails, because the put-call parity spread computation requires exactly one call and one put.

Source

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

        };
        let Some(reference_future_instrument) = reference_future_instrument else {
            anyhow::bail!(
                "Cannot cache futures spread: no reference futures instrument for {futures_instrument_id}"
            );
        };

        if call_instrument.instrument_class() != InstrumentClass::Option
            || put_instrument.instrument_class() != InstrumentClass::Option
        {
            anyhow::bail!(
                "Cannot cache futures spread: non-option instruments provided call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id}"
            );
        }

        if call_instrument.option_kind() != Some(OptionKind::Call)
            || put_instrument.option_kind() != Some(OptionKind::Put)
        {
            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}"

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check option_kind() on both instruments before calling: first must be Some(OptionKind::Call), second Some(OptionKind::Put).
  2. If you only have an unordered pair, sort the legs by option_kind and pass call first, put second.
  3. Verify the adapter populated option_kind correctly for both cached instruments.

Example fix

// before
let price = greeks.cache_futures_spread(leg_a_id, leg_b_id, future_id)?;
// after
let (call_id, put_id) = if cache.instrument(&leg_a_id).unwrap().option_kind() == Some(OptionKind::Call) {
    (leg_a_id, leg_b_id)
} else {
    (leg_b_id, leg_a_id)
};
let price = greeks.cache_futures_spread(call_id, put_id, future_id)?;
Defensive patterns

Strategy: validation

Validate before calling

// rust
fn ordered_pair(cache: &Cache, a: &InstrumentId, b: &InstrumentId) -> Option<(InstrumentId, InstrumentId)> {
    let ka = cache.instrument(a)?.option_kind()?;
    let kb = cache.instrument(b)?.option_kind()?;
    match (ka, kb) {
        (OptionKind::Call, OptionKind::Put) => Some((a.clone(), b.clone())),
        (OptionKind::Put, OptionKind::Call) => Some((b.clone(), a.clone())),
        _ => None,
    }
}

Type guard

fn call_leg(cache: &Cache, id: &InstrumentId) -> Option<&InstrumentAny> {
    cache.instrument(id).filter(|i| i.option_kind() == Some(OptionKind::Call))
}

Try / catch

match greeks.cache_futures_spread(call_id, put_id, future_id) {
    Ok(p) => use(p),
    Err(e) if e.to_string().contains("expected call/put pair") => reorder_legs_and_retry(),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Passing two calls or two puts as the pair; swapping the call and put arguments so option_kind checks fail in both directions; instruments whose option_kind metadata is missing or set incorrectly by the adapter.

Common situations: Constructing the pair from a listing where both legs share a strike/expiry but the code grabs the wrong option_kind; refactored code that dropped an ordering guarantee; adapter loading option_kind as None.

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/90166ea0be783f21. Report an issue: GitHub.