nautechsystems/nautilus_trader · error

Empty underlying for {inst_id}: instrument may be pre-open o

Error message

Empty underlying for {inst_id}: instrument may be pre-open or misconfigured

What it means

validate_underlying checks that an OKX derivatives instrument (SWAP, FUTURES, OPTION) has a non-empty underlying (`uly`). Without an underlying the instrument cannot be linked to its index/base assets, which usually means the instrument is in pre-open state or misconfigured, so it bails naming the `inst_id`.

Source

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

        definition,
        &SpotInstrumentParser,
        margin_init,
        margin_maint,
        maker_fee,
        taker_fee,
        ts_init,
    )
}

/// Validates that the underlying field is not empty for derivative instruments.
///
/// # Errors
///
/// Returns an error if the underlying field is empty, which typically indicates
/// a pre-open or misconfigured instrument.
fn validate_underlying(inst_id: Ustr, uly: Ustr) -> anyhow::Result<()> {
    if uly.is_empty() {
        anyhow::bail!(
            "Empty underlying for {inst_id}: instrument may be pre-open or misconfigured"
        );
    }
    Ok(())
}

/// Parses an OKX swap instrument definition into a Nautilus crypto perpetual.
///
/// # Errors
///
/// Returns an error if the instrument definition cannot be parsed.
///
/// # Panics
///
/// Panics if the constructed instrument fails validation.
pub fn parse_swap_instrument(
    definition: &OKXInstrument,
    margin_init: Option<Decimal>,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Filter out instruments with an empty `uly` before calling the instrument parsers
  2. Only parse instruments with state "live" and a populated `uly`/`instFamily`
  3. Re-fetch instruments if the record may have been a transient pre-open entry
  4. If OKX legitimately omits `uly` for a product type you use (some SWAPs), extend the parser to derive the underlying from `instId`/`instFamily` instead of failing

Example fix

// before
for def in response.data {
    let inst = parse_swap_instrument(&def, ...)?; // bails on empty uly
}
// after
for def in response.data.into_iter().filter(|d| !d.uly.is_empty()) {
    let inst = parse_swap_instrument(&def, ...)?;
}
Defensive patterns

Strategy: validation

Validate before calling

fn usable_derivative(def: &OKXInstrument) -> bool {
    matches!(def.inst_type, "SWAP" | "FUTURES" | "OPTION")
        && !def.uly.is_empty()
        && def.state == "live"
}

Try / catch

match parse_swap_instrument(&definition, ...) {
    Ok(inst) => add(inst),
    Err(e) if e.to_string().contains("Empty underlying") => log::debug!("pre-open instrument skipped: {e}"),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: parse_swap_instrument, parse_futures_instrument, or parse_option_instrument receiving an instrument definition with an empty `uly` field — common for SWAPs (where uly can be absent for some listings) or option/pre-open records returned by the instruments endpoint.

Common situations: Parsing the unfiltered full instrument list at startup; newly announced SWAP/FUTURES contracts in pre-open; instruments whose `uly` is intentionally absent in certain OKX product configurations.

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/1facd5e7d5ac94be. Report an issue: GitHub.