nautechsystems/nautilus_trader · error

Unknown product type for '{product_id}'

Error message

Unknown product type for '{product_id}'

What it means

parse_instrument maps each Coinbase product to a Nautilus instrument based on its CoinbaseProductType (SPOT, FUTURE, PERPETUAL). When a product carries the Unknown product type, the parser cannot determine which instrument constructor to use and fails with this error naming the product_id.

Source

Thrown at crates/adapters/coinbase/src/http/parse.rs:323

        .build()
        .unwrap();

    Ok(InstrumentAny::CryptoFuture(instrument))
}

/// Parses a Coinbase product into the appropriate Nautilus instrument type.
pub fn parse_instrument(product: &Product, ts_init: UnixNanos) -> anyhow::Result<InstrumentAny> {
    match product.product_type {
        CoinbaseProductType::Spot => parse_spot_instrument(product, ts_init),
        CoinbaseProductType::Future => {
            if is_perpetual_product(product) {
                parse_perpetual_instrument(product, ts_init)
            } else {
                parse_future_instrument(product, ts_init)
            }
        }
        CoinbaseProductType::Unknown => {
            anyhow::bail!("Unknown product type for '{}'", product.product_id)
        }
    }
}

/// Determines whether a futures product is a perpetual contract.
///
/// Coinbase returns `contract_expiry_type: "EXPIRING"` for both perpetuals
/// and dated futures, so the `CoinbaseContractExpiryType::Perpetual` variant
/// alone is not sufficient. We check three signals in order:
///
/// 1. `contract_expiry_type == Perpetual` (forward compat if Coinbase fixes the API)
/// 2. Non-empty `funding_rate` in `future_product_details` (structural signal:
///    only perpetuals have ongoing funding)
/// 3. `display_name` contains "PERP" or "Perpetual" (heuristic fallback)
pub(crate) fn is_perpetual_product(product: &Product) -> bool {
    if let Some(details) = &product.future_product_details {
        if details.contract_expiry_type == CoinbaseContractExpiryType::Perpetual {
            return true;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the product_id on Coinbase's API and confirm it is a supported SPOT/FUTURE/PERPETUAL product
  2. Update CoinbaseProductType and parse_instrument to support the new product type if Coinbase introduced one
  3. Skip/filter Unknown products when bulk-loading instruments

Example fix

// before
let inst = client.request_instrument("BTC-JAM-UNKNOWN").await?;
// after
let products: Vec<_> = all.filter(|p| p.product_type != CoinbaseProductType::Unknown).collect();
Defensive patterns

Strategy: type-guard

Validate before calling

if product.product_type == CoinbaseProductType::Unknown {
    log::warn!("skipping unsupported product {}", product.product_id);
    return Ok(None);
}

Type guard

fn is_supported_product(p: &CoinbaseProduct) -> bool {
    !matches!(p.product_type, CoinbaseProductType::Unknown)
}

Prevention

When it happens

Trigger: Requesting instruments via request_instruments/request_instrument and Coinbase returns a product whose product_type field deserializes to Unknown (new asset classes, unusual product listings, or API schema changes).

Common situations: Coinbase adds a new product category before the adapter enum is updated; calling request_instrument on a product_id for a product type the adapter does not model (e.g. certain derivatives listings).

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/8fde3286199262be. Report an issue: GitHub.