nautechsystems/nautilus_trader · error

Missing tick_size for {}

Error message

Missing tick_size for {}

What it means

The instrument conversion guard chain continues: `tick_size` is null in the BitmexInstrumentMsg. Tick size is mandatory for constructing a valid instrument (price precision/step), so the conversion fails with this error.

Source

Thrown at crates/adapters/bitmex/src/websocket/messages.rs:591

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 = msg
            .state
            .and_then(|s| serde_json::from_str::<BitmexInstrumentState>(&format!("\"{s}\"")).ok())
            .unwrap_or(BitmexInstrumentState::Open);

        // Parse instrument type - default to PerpetualContract if not present
        let instrument_type = msg

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Skip the incomplete record and rely on a later complete snapshot.
  2. Cache full instrument definitions and merge deltas before conversion.
  3. Restrict subscription to symbols known to have complete metadata.
  4. Refetch the instrument definition via REST if the WS update is incomplete.

Example fix

// before
let tick_size = msg.tick_size.ok_or_else(|| anyhow::anyhow!("Missing tick_size for {}", msg.symbol))?;
// after
let Some(tick_size) = msg.tick_size else {
    log::debug!("Deferring {}: no tick_size yet", msg.symbol);
    return Ok(());
};
Defensive patterns

Strategy: fallback

Validate before calling

if msg.tick_size.is_none() { /* wait for complete snapshot or fetch via REST */ }

Type guard

fn has_tick_size(msg: &BitmexInstrumentMsg) -> Option<f64> { msg.tick_size }

Try / catch

let Ok(def) = TryInto::<InstrumentDef>::try_into(msg) else {
    pending.insert(msg.symbol.clone(), msg);
    return Ok(());
};

Prevention

When it happens

Trigger: WS instrument message arriving with tick_size: null — partial instrument updates, index symbols, or contracts where BitMEX has not published tick metadata.

Common situations: Initial bulk snapshot fetching where some rows are placeholders; partial delta updates missing fields not changed; newly listed instruments with incomplete metadata.

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


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