nautechsystems/nautilus_trader · error

invalid 'bbo_level': '{s}', expected 1, 2, 3, 4, or 5

Error message

invalid 'bbo_level': '{s}', expected 1, 2, 3, 4, or 5

What it means

parse_bbo_level validates the bbo_level parameter, which selects which of the top 5 BBO levels triggers a TP/SL order. Only the string values '1' through '5' are accepted; any other string raises this anyhow error.

Source

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

    let Some(value) = Option::<String>::deserialize(d)? else {
        return Ok(None);
    };

    parse_smp_type(&value).map(Some).map_err(D::Error::custom)
}

pub fn parse_bbo_side_type(s: &str) -> anyhow::Result<BybitBboSideType> {
    match s.to_ascii_lowercase().as_str() {
        "queue" => Ok(BybitBboSideType::Queue),
        "counterparty" => Ok(BybitBboSideType::Counterparty),
        _ => anyhow::bail!("invalid Bybit bbo_side_type: '{s}', expected Queue or Counterparty"),
    }
}

pub fn parse_bbo_level(s: String) -> anyhow::Result<String> {
    match s.as_str() {
        "1" | "2" | "3" | "4" | "5" => Ok(s),
        _ => anyhow::bail!("invalid 'bbo_level': '{s}', expected 1, 2, 3, 4, or 5"),
    }
}

/// Parses Bybit TP/SL parameters from an optional params map.
pub fn parse_bybit_tp_sl_params(params: Option<&Params>) -> anyhow::Result<BybitTpSlParams> {
    let Some(params) = params else {
        return Ok(BybitTpSlParams::default());
    };

    let mut result = BybitTpSlParams {
        is_leverage: params.get_bool("is_leverage").unwrap_or(false),
        ..Default::default()
    };

    if let Some(s) = get_price_str(params, "take_profit") {
        let p =
            Price::from_str(&s).map_err(|e| anyhow::anyhow!("invalid 'take_profit' price: {e}"))?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Pass bbo_level as the string '1', '2', '3', '4' or '5'
  2. Clamp your desired level to the 1-5 range before submitting
  3. Omit bbo_level to use the default

Example fix

// before
params.insert("bbo_level", "0");
// after
params.insert("bbo_level", "1");
Defensive patterns

Strategy: validation

Validate before calling

assert bbo_level in ("1", "2", "3", "4", "5"), f"bbo_level must be 1-5, got {bbo_level}"

Type guard

fn is_valid_bbo_level(s: &str) -> bool {
    matches!(s.as_str(), "1" | "2" | "3" | "4" | "5")
}

Try / catch

match parse_bbo_level(s) {
    Ok(l) => use(l),
    Err(e) => { log::error!("{e}"); use_default_level() }
}

Prevention

When it happens

Trigger: Passing bbo_level in TP/SL params (via parse_bybit_tp_sl_params, reached from order submission) as e.g. '0', '6', 'level3', or an integer-typed value stringified unusually.

Common situations: Assuming the level range starts at 0 (programmer habit), requesting a level beyond Bybit's supported top-5 depth, or passing an int instead of a string in Python params.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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