nautechsystems/nautilus_trader · error

`exp_time` is required for {}

Error message

`exp_time` is required for {}

What it means

Thrown by `parse_futures_instrument` when the OKX instrument definition has no `exp_time` (expiry timestamp). The expiry is mandatory because the parser computes the instrument's `expiration_ns` from it; without it, no valid Nautilus futures instrument can be constructed. The error names the offending `inst_id`.

Source

Thrown at crates/adapters/okx/src/common/parse.rs:2310

    let settlement_currency =
        Currency::get_or_create_crypto_with_context(definition.settle_ccy, Some(&context));
    let is_inverse = match definition.ct_type {
        OKXContractType::Linear => false,
        OKXContractType::Inverse => true,
        OKXContractType::None => {
            anyhow::bail!(
                "Invalid contract type '{}' for {}: expected 'linear' or 'inverse'",
                definition.ct_type,
                definition.inst_id
            )
        }
    };
    let listing_time = definition
        .list_time
        .ok_or_else(|| anyhow::anyhow!("`list_time` is required for {}", definition.inst_id))?;
    let expiry_time = definition
        .exp_time
        .ok_or_else(|| anyhow::anyhow!("`exp_time` is required for {}", definition.inst_id))?;
    let activation_ns = parse_millisecond_timestamp(listing_time);
    let expiration_ns = parse_millisecond_timestamp(expiry_time);

    if definition.tick_sz.is_empty() {
        anyhow::bail!("`tick_sz` is empty for {}", definition.inst_id);
    }

    let price_increment = Price::from_str(&definition.tick_sz).map_err(|e| {
        anyhow::anyhow!(
            "Failed to parse `tick_sz` '{}' for {}: {e}",
            definition.tick_sz,
            definition.inst_id
        )
    })?;

    if definition.lot_sz.is_empty() {
        anyhow::bail!("`lot_sz` is empty for {}", definition.inst_id);
    }

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the OKX instruments response for the inst_id and confirm `exp_time` is present; skip instruments that lack it.
  2. Set `exp_time: Some(<ms timestamp>)` when constructing the definition manually.
  3. For perpetual swaps without expiry, use an adapter/parser path that models them with a far-future or sentinel expiration instead of requiring exp_time.

Example fix

// before
OkxInstrumentDef { inst_id: "BTC-USD-240329", exp_time: None, .. }
// after
OkxInstrumentDef { inst_id: "BTC-USD-240329", exp_time: Some(1711665600000), .. }
Defensive patterns

Strategy: validation

Validate before calling

if definition.exp_time.is_none() {
    eprintln!("skipping {}: no exp_time", definition.inst_id);
    return Ok(None);
}

Type guard

fn has_exp_time(def: &OkxInstrumentDef) -> bool {
    def.exp_time.is_some()
}

Try / catch

match parse_instrument_any(definition) {
    Ok(inst) => inst,
    Err(e) if e.to_string().contains("`exp_time` is required") => { skip; }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `parse_instrument_any`/`parse_futures_instrument` with a definition whose `exp_time` is None — OKX omitted the field in the instruments response or the caller built the struct without it.

Common situations: Perpetual swaps or some OKX instrument records where `exp_time` is absent; hand-built definitions in tests/fixtures; API version changes that made `exp_time` optional on the wire.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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