nautechsystems/nautilus_trader · error

`lot_sz` is empty for {}

Error message

`lot_sz` is empty for {}

What it means

parse_spread_instrument also validates `lot_sz` (minimum tradable quantity increment). An empty value cannot be converted to a Quantity, so parsing bails with this message naming the `sprd_id`. This check runs immediately after the `tick_sz` check.

Source

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

/// Returns an error if the spread definition cannot be parsed.
///
/// # Panics
///
/// Panics if the constructed instrument fails validation.
pub fn parse_spread_instrument(
    definition: &OKXSpread,
    margin_init: Option<Decimal>,
    margin_maint: Option<Decimal>,
    maker_fee: Option<Decimal>,
    taker_fee: Option<Decimal>,
    ts_init: UnixNanos,
) -> anyhow::Result<InstrumentAny> {
    if definition.tick_sz.is_empty() {
        anyhow::bail!("`tick_sz` is empty for {}", definition.sprd_id);
    }

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

    let context = format!("SPREAD instrument {}", definition.sprd_id);
    let instrument_id = parse_instrument_id(definition.sprd_id);
    let raw_symbol = Symbol::from_ustr_unchecked(definition.sprd_id);
    let underlying =
        Currency::get_or_create_crypto_with_context(definition.base_ccy, Some(&context));
    let quote_currency =
        Currency::get_or_create_crypto_with_context(definition.quote_ccy, Some(&context));
    let settlement_currency = spread_settlement_currency(definition, underlying, quote_currency);
    let is_inverse = matches!(definition.sprd_type, OKXSpreadType::Inverse);
    let activation_ns = definition
        .list_time
        .map(parse_millisecond_timestamp)
        .ok_or_else(|| anyhow::anyhow!("`list_time` is required for {}", definition.sprd_id))?;
    let expiration_ns = definition
        .exp_time
        .map(parse_millisecond_timestamp)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Filter out spread records with an empty `lotSz` (together with empty `tickSz`) before parsing
  2. Re-fetch the instrument definition from OKX and retry
  3. Only parse instruments whose state indicates they are live
  4. If a legitimately live instrument has empty lotSz, report/update the adapter — the API contract expects a populated lotSz
Defensive patterns

Strategy: validation

Validate before calling

if definition.lot_sz.is_empty() {
    eprintln!("skipping spread {} with empty lot_sz", definition.sprd_id);
    return Ok(None);
}

Try / catch

match parse_spread_instrument(&definition, ...) {
    Ok(inst) => add(inst),
    Err(e) if e.to_string().contains("lot_sz") => log::debug!("skipped: {e}"),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling parse_spread_instrument / request_spread_instruments with a spread definition whose `lotSz` is an empty string, typically for pre-open or placeholder spread instruments returned by OKX.

Common situations: Bulk-parsing all spreads without filtering incomplete records; instruments newly listed where OKX has not populated lot size yet; cached/malformed instrument snapshots.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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