nautechsystems/nautilus_trader · error

invalid Bybit TP/SL order type

Error message

invalid Bybit TP/SL order type: '{s}', expected Market or Limit

What it means

When parsing Bybit TP/SL parameters, the adapter maps the order type attached to TP/SL orders to BybitOrderType. Only "Market" and "Limit" are defined by Bybit for TP/SL orders; any other string is rejected explicitly instead of falling through serde's lenient deserialization.

Solutions

  1. Inspect the raw response's orderType field and correct it to "Market" or "Limit"
  2. Update the adapter/dependency to a version supporting the new order type
  3. Fix fixture or mock data casing to match Bybit's exact strings

Example fix

// before
parse_tp_sl_order_type("market")? // rejected
// after
parse_tp_sl_order_type("Market")?
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_tpsl_order_type(s: &str) -> bool { matches!(s, "Market" | "Limit") }

Type guard

fn valid_order_type(s: &str) -> Option<BybitOrderType> {
    match s { "Market" => Some(BybitOrderType::Market), "Limit" => Some(BybitOrderType::Limit), _ => None }
}

Try / catch

let ot = parse_tp_sl_order_type(raw).unwrap_or(BybitOrderType::Market); // or surface the error

Prevention

When it happens

Trigger: parse_bybit_tp_sl_params receives a payload where the TP/SL order type field (e.g. orderType in the TPSL response) is not exactly "Market" or "Limit" — typos, lowercase 'market', or an exchange-side new value.

Common situations: Mock/test fixtures with wrong casing; a newer Bybit API adding an order type the adapter doesn't know; manually constructed JSON used in integration testing.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

Appendix: source

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

    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"
        ),
    }
}

pub(crate) fn parse_tp_sl_order_type(s: &str) -> anyhow::Result<BybitOrderType> {
    match s {
        "Market" => Ok(BybitOrderType::Market),
        "Limit" => Ok(BybitOrderType::Limit),
        _ => anyhow::bail!("invalid Bybit TP/SL order type: '{s}', expected Market or Limit"),
    }
}

// A plain `serde_json` deserialize would accept unknown strings: `BybitTpSlMode` carries a
// `#[serde(other)] Unknown` variant, so garbage would silently map to `Unknown`.
pub(crate) fn parse_tpsl_mode(s: &str) -> anyhow::Result<BybitTpSlMode> {
    match s {
        "Full" => Ok(BybitTpSlMode::Full),
        "Partial" => Ok(BybitTpSlMode::Partial),
        _ => anyhow::bail!("invalid Bybit TP/SL mode: '{s}', expected Full or Partial"),
    }
}

#[cfg(test)]
mod tests {
    use nautilus_model::{
        data::BarSpecification,
        enums::{AggregationSource, BarAggregation, PositionSide, PriceType},

View on GitHub (pinned to 18893faf8b)