nautechsystems/nautilus_trader · error · anyhow::Error
Missing contract_expiry for dated future '{product_id}'
Error message
Missing contract_expiry for dated future '{product_id}' What it means
Coinbase's INTX futures products expose contract details in future_product_details; a dated future must carry a non-empty contract_expiry string. parse_future_instrument uses it to compute the instrument's expiration_ns, so when it is missing or empty the parser intentionally fails rather than producing an instrument with no expiration. This indicates the product payload for that future lacks the expiry field the adapter requires.
Source
Thrown at crates/adapters/coinbase/src/http/parse.rs:274
let underlying = derive_base_currency(product);
let quote_currency = Currency::get_or_create_crypto(product.quote_currency_id);
let settlement_currency = quote_currency;
let price_precision = precision_from_increment(&product.price_increment);
let size_precision = precision_from_increment(&product.base_increment);
let price_increment = parse_price(&product.price_increment, price_precision)?;
let size_increment = parse_quantity(&product.base_increment, size_precision)?;
let min_quantity = parse_optional_quantity(&product.base_min_size);
let max_quantity = parse_optional_quantity(&product.base_max_size);
let expiry_str = product
.future_product_details
.as_ref()
.map_or("", |d| d.contract_expiry.as_str());
anyhow::ensure!(
!expiry_str.is_empty(),
"Missing contract_expiry for dated future '{}'",
product.product_id
);
let expiration_ns = parse_rfc3339_timestamp(expiry_str).context(format!(
"Failed to parse contract_expiry for '{}'",
product.product_id
))?;
let multiplier = contract_size_multiplier(product);
let instrument = CryptoFuture::builder()
.instrument_id(instrument_id)
.raw_symbol(raw_symbol)
.underlying(underlying)
.quote_currency(quote_currency)
.settlement_currency(settlement_currency)View on GitHub (pinned to 18893faf8b)
Solutions
- Check the raw product JSON for the offending product_id and confirm future_product_details.contract_expiry is present and non-empty.
- Filter out or skip unsupported product types before parsing (use load_all_filtered with the desired CoinbaseProductType).
- Update the Product model/parser if the Coinbase API schema changed the contract_expiry location.
- Refresh fixtures/recorded responses if the failure comes from stale test data.
Example fix
// before
let instruments = provider.load_all().await?;
// after (skip non-parseable products / filter to futures that parse)
let instruments = provider
.load_all_filtered(CoinbaseProductType::Future)
.await
.unwrap_or_default();
// or validate before parsing:
anyhow::ensure!(
product.future_product_details.as_ref().map_or(false, |d| !d.contract_expiry.is_empty()),
"product {} lacks contract_expiry; skipping",
product.product_id
); Defensive patterns
Strategy: validation
Validate before calling
fn future_has_expiry(p: &Product) -> bool {
p.future_product_details
.as_ref()
.map_or(false, |d| !d.contract_expiry.is_empty())
}
let instruments: Vec<_> = products.into_iter().filter(|p| p.product_type != CoinbaseProductType::Future || future_has_expiry(p)).collect(); Type guard
fn parseable_future(p: &Product) -> bool { p.future_product_details.as_ref().map_or(true, |d| !d.contract_expiry.is_empty()) } Try / catch
match parse_instrument(&product) {
Ok(i) => cache.insert(i.id(), i),
Err(e) => log::warn!("skipping product {}: {e:#}", product.product_id),
} Prevention
- Skip products whose future_product_details.contract_expiry is empty before parsing.
- Keep the Product model in sync with Coinbase INTX API changes.
- Refresh recorded fixtures regularly and assert required fields exist.
When it happens
Trigger: load_all / load_all_filtered / load encounters a product with product_type FUTURE (dated) whose future_product_details is absent or whose contract_expiry is an empty string; calling parse_future_instrument (directly or via parse_instrument) on such a Product.
Common situations: Coinbase listing a new future type whose payload shape differs; test fixtures or recorded JSON missing future_product_details; API schema changes where contract_expiry moved or was renamed; expiring/expired products returning empty expiry fields.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- `exp_time` is required for {}
- `settle_ccy` or `quote_ccy` is required for EVENTS instrumen
- Failed to fetch CFM balance summary: {e}
- Failed to fetch CFM positions: {e}
- Failed to fetch CFM position '{product_id}': {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/5fa2ef84db0ecefc.
Report an issue: GitHub.