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
- Filter out instruments with an empty `uly` before calling the instrument parsers
- Only parse instruments with state "live" and a populated `uly`/`instFamily`
- Re-fetch instruments if the record may have been a transient pre-open entry
- 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
- Check uly is populated before parsing SWAP/FUTURES/OPTION instruments
- Skip or defer pre-open instruments rather than parsing the full list blindly
- Re-fetch instrument data for affected symbols; empty uly is often transient
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
- `tick_sz` is empty for {}
- `lot_sz` is empty for {}
- Unsupported bar specification for AX: {step}-{:?}
- AX requires whole contract quantities, was {}
- Order quantity must be at least 1 contract
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/1facd5e7d5ac94be.
Report an issue: GitHub.