nautechsystems/nautilus_trader · error
Missing underlying for {}
Error message
Missing underlying for {} What it means
Same required-field guard family in the instrument message TryFrom: `underlying` is null in the received BitmexInstrumentMsg, so the full instrument definition cannot be constructed and the conversion fails.
Source
Thrown at crates/adapters/bitmex/src/websocket/messages.rs:585
pub fair_basis: Option<f64>,
pub fair_basis_rate: Option<f64>,
pub fair_price: Option<f64>,
pub timestamp: Timestamp,
}
impl TryFrom<BitmexInstrumentMsg> for crate::http::models::BitmexInstrument {
type Error = anyhow::Error;
fn try_from(msg: BitmexInstrumentMsg) -> Result<Self, Self::Error> {
use crate::common::enums::{BitmexInstrumentState, BitmexInstrumentType};
// Required fields
let root_symbol = msg
.root_symbol
.ok_or_else(|| anyhow::anyhow!("Missing root_symbol for {}", msg.symbol))?;
let underlying = msg
.underlying
.ok_or_else(|| anyhow::anyhow!("Missing underlying for {}", msg.symbol))?;
let quote_currency = msg
.quote_currency
.ok_or_else(|| anyhow::anyhow!("Missing quote_currency for {}", msg.symbol))?;
let tick_size = msg
.tick_size
.ok_or_else(|| anyhow::anyhow!("Missing tick_size for {}", msg.symbol))?;
let multiplier = msg
.multiplier
.ok_or_else(|| anyhow::anyhow!("Missing multiplier for {}", msg.symbol))?;
let is_quanto = msg
.is_quanto
.ok_or_else(|| anyhow::anyhow!("Missing is_quanto for {}", msg.symbol))?;
let is_inverse = msg
.is_inverse
.ok_or_else(|| anyhow::anyhow!("Missing is_inverse for {}", msg.symbol))?;
// Parse state - default to Open if not present
let state = msgView on GitHub (pinned to 18893faf8b)
Solutions
- Catch and skip the record; process instruments that do carry underlying.
- Cache full snapshots and apply partial updates only to cached definitions.
- Filter subscriptions to standard derivative symbols with complete definitions.
- Update adapter field mappings if BitMEX changed the message schema.
Example fix
// before
let instrument = TryInto::<Instrument>::try_into(msg)?;
// after
if let Err(e) = TryInto::<Instrument>::try_into(msg) {
log::debug!("Ignoring incomplete instrument update: {e}");
return Ok(());
} Defensive patterns
Strategy: fallback
Validate before calling
if msg.underlying.is_none() { /* defer to next full snapshot */ } Type guard
fn has_definition(msg: &BitmexInstrumentMsg) -> bool {
msg.root_symbol.is_some() && msg.underlying.is_some() && msg.quote_currency.is_some() && msg.tick_size.is_some()
} Try / catch
let def = match TryInto::<InstrumentDef>::try_into(msg) {
Ok(d) => d,
Err(e) => { debug!("skipping: {e}"); return Ok(()); }
}; Prevention
- Cache full instrument definitions and apply WS deltas to the cache.
- Filter subscriptions to derivative symbols that carry underlying.
- Handle partial WS snapshots as deltas, not standalone definitions.
When it happens
Trigger: A WS `instrument` message for a symbol (typically derivatives) arriving with underlying: null — partial snapshots, spot/index symbols, or delisted contracts.
Common situations: Bulk instrument subscriptions including non-derivative symbols; BitMEX emitting deltas where absent fields are null rather than omitted; older API messages missing recently added/required fields.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- Missing root_symbol for {}
- Missing quote_currency for {}
- Missing tick_size for {}
- Missing multiplier for {}
- Missing is_quanto for {}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/39e0e581c5d5c71e.
Report an issue: GitHub.