nautechsystems/nautilus_trader · error · anyhow::Error
Cannot cache futures spread: expiration dates differ call_in
Error message
Cannot cache futures spread: expiration dates differ call_instrument_id={call_instrument_id} put_instrument_id={put_instrument_id} What it means
cache_futures_spread requires both legs to expire at the same time, since the implied future price is computed to a single expiry. If call_instrument.expiration_ns() differs from put_instrument.expiration_ns() the method bails with this error. This catches pairs drawn from different expiries (calendar-style pairs) which parity math cannot handle.
Source
Thrown at crates/common/src/greeks.rs:1111
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!(
"Cannot cache futures spread: no reference futures price for {futures_instrument_id}"
)
})?;
let call_price = self.get_price(&call_instrument_id).ok_or_else(|| {
anyhow::anyhow!(
"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}"
)View on GitHub (pinned to 18893faf8b)
Solutions
- Only pair options with identical expiration_ns (compare the raw timestamps, not formatted dates).
- Normalize expiry parsing so both legs resolve to the same expiration_ns value.
- Filter the option chain by exact expiration before choosing the call/put pair.
Example fix
// before
let price = greeks.cache_futures_spread(may_call_id, jun_put_id, future_id)?;
// after
if call.expiration_ns() != put.expiration_ns() {
tracing::warn!("expiry mismatch, skipping pair");
return Ok(None);
}
let price = greeks.cache_futures_spread(may_call_id, may_put_id, future_id)?; Defensive patterns
Strategy: validation
Validate before calling
// rust
fn same_expiry(cache: &Cache, call_id: &InstrumentId, put_id: &InstrumentId) -> bool {
cache.instrument(call_id).and_then(|i| i.expiration_ns()) == cache.instrument(put_id).and_then(|i| i.expiration_ns())
} Type guard
fn expiry_ns(cache: &Cache, id: &InstrumentId) -> Option<UnixNanos> {
cache.instrument(id).and_then(|i| i.expiration_ns())
} Try / catch
match greeks.cache_futures_spread(call_id, put_id, future_id) {
Ok(p) => use(p),
Err(e) if e.to_string().contains("expiration dates differ") => rebuild_chain_for_expiry(expiry),
Err(e) => return Err(e),
} Prevention
- Filter the option chain to a single expiration_ns before pairing legs.
- Compare raw UnixNanos timestamps, never formatted date strings, when matching expiries.
- Normalize timezone/settlement conventions in one ingestion layer so both legs agree.
When it happens
Trigger: Passing a near-month call with a far-month put; expiry timestamps parsed from different timezones or sources yielding unequal expiration_ns for nominally the same expiry; weekly vs monthly options mixed up.
Common situations: Pair-selection code that matches on expiry date strings but the raw ns timestamps differ (e.g. 16:00 vs 09:30 settlement); vendor data with differing expiry conventions per leg.
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
- Cannot cache futures spread: non-option instruments provided
- Cannot cache futures spread: expected call/put pair call_ins
- Cannot cache futures spread: option underlyings differ call_
- Cannot cache futures spread: strike prices differ call_instr
- option_summary_family_subs mutex poisoned
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/dfeed941474cd5df.
Report an issue: GitHub.