nautechsystems/nautilus_trader · error

invalid type for 'bbo_level': {value}, expected string or in

Error message

invalid type for 'bbo_level': {value}, expected string or integer

What it means

The 'bbo_level' value must be a string or an integer (i64/u64) so it can be normalized to a level string and parsed via parse_bbo_level. Any other JSON type (float, bool, null, object) triggers this bail with the offending value in the message.

Source

Thrown at crates/adapters/bybit/src/common/parse.rs:1946

        anyhow::bail!("'bbo_side_type' and 'bbo_level' must be provided together");
    }

    if let Some(value) = params.get("bbo_side_type") {
        let side_type = value.as_str().ok_or_else(|| {
            anyhow::anyhow!("invalid type for 'bbo_side_type': {value}, expected string")
        })?;
        result.bbo_side_type = Some(parse_bbo_side_type(side_type)?);
    }

    if let Some(value) = params.get("bbo_level") {
        let level = if let Some(s) = value.as_str() {
            s.to_string()
        } else if let Some(i) = value.as_i64() {
            i.to_string()
        } else if let Some(u) = value.as_u64() {
            u.to_string()
        } else {
            anyhow::bail!("invalid type for 'bbo_level': {value}, expected string or integer");
        };
        result.bbo_level = Some(parse_bbo_level(level)?);
    }

    Ok(result)
}

pub(crate) fn parse_trigger_type(s: &str) -> anyhow::Result<BybitTriggerType> {
    match s {
        "LastPrice" => Ok(BybitTriggerType::LastPrice),
        "MarkPrice" => Ok(BybitTriggerType::MarkPrice),
        "IndexPrice" => Ok(BybitTriggerType::IndexPrice),
        _ => anyhow::bail!(
            "invalid Bybit trigger type: '{s}', expected LastPrice, MarkPrice, or IndexPrice"
        ),
    }
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Pass 'bbo_level' as a string (e.g. "1") or a JSON integer
  2. Ensure the config source emits whole numbers, not floats, for bbo_level
  3. Check that the value also passes parse_bbo_level (valid level format) after fixing the type

Example fix

// before
params.set("bbo_level", 1.5);
// after
params.set("bbo_level", "1");
Defensive patterns

Strategy: type-guard

Validate before calling

fn validate_bbo_level(params: &Params) -> Result<(), String> {
    if let Some(v) = params.get("bbo_level") {
        if !(v.is_string() || v.is_i64() || v.is_u64()) {
            return Err(format!("bbo_level must be string or integer, got {v}"));
        }
    }
    Ok(())
}

Type guard

fn is_bbo_level_like(v: &Value) -> bool {
    v.is_string() || v.is_i64() || v.is_u64()
}

Try / catch

match parse_bybit_tp_sl_params(&params) {
    Ok(p) => submit(p),
    Err(e) if e.to_string().contains("invalid type for 'bbo_level'") => {
        log::warn("bbo_level must be a string or integer, not a float/bool/null");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Passing 'bbo_level' as a float (e.g. 1.5), bool, null, or nested value while 'bbo_side_type' is also present.

Common situations: YAML/JSON config where the level was parsed as a float; untyped deserialization producing f64 for whole numbers.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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