nautechsystems/nautilus_trader · error

`tick_sz` is empty for {}

Error message

`tick_sz` is empty for {}

What it means

parse_spread_instrument validates an OKX spread instrument definition before constructing a Nautilus instrument. A spread definition with an empty `tick_sz` (minimum price increment) cannot produce a valid Price, so parsing is aborted with this message naming the `sprd_id`.

Source

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

/// Parses an OKX spread definition into a Nautilus crypto spread.
///
/// # Errors
///
/// 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)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Filter spread instrument records with an empty `tickSz` before parsing (skip or defer them)
  2. Re-fetch the spread instrument definition; the empty field may be transient for newly listed spreads
  3. Check the instrument state field (`state`/`listTime`) and only parse live instruments

Example fix

// before
for sprd in response.data {
    let inst = parse_spread_instrument(&sprd, ...)?; // bails on empty tickSz
}
// after
for sprd in response.data.into_iter().filter(|s| !s.tick_sz.is_empty() && !s.lot_sz.is_empty()) {
    let inst = parse_spread_instrument(&sprd, ...)?;
}
Defensive patterns

Strategy: validation

Validate before calling

if definition.tick_sz.is_empty() || definition.lot_sz.is_empty() {
    return Ok(None); // skip incomplete spread instrument
}

Try / catch

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

Prevention

When it happens

Trigger: Calling parse_spread_instrument (directly or via request_spread_instrument/request_spread_instruments) with an OKX spread instrument JSON whose `tickSz` field is an empty string — typically from a pre-listing or placeholder spread record returned by the spreads endpoint.

Common situations: Requesting all spread instruments and iterating over records without filtering pre-open ones; stale cached instrument data; OKX API returning partially populated definitions during new spread listings.

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/170923f2089f0ac1. Report an issue: GitHub.