nautechsystems/nautilus_trader · error

invalid 'position_idx': {idx}, expected 0, 1, or 2

Error message

invalid 'position_idx': {idx}, expected 0, 1, or 2

What it means

The 'position_idx' field parsed from TP/SL update params did not map to 0, 1, or 2, which are the only values Bybit uses for one-way (0) or hedge-mode (1 buy, 2 sell) position indexing; any other integer is rejected by parse_bybit_tp_sl_params.

Source

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

        }
    }

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

    if let Some(value) = params.get("position_idx") {
        let idx = value.as_i64().ok_or_else(|| {
            anyhow::anyhow!("invalid type for 'position_idx': {value}, expected integer")
        })?;
        result.position_idx = Some(match idx {
            0 => BybitPositionIdx::OneWay,
            1 => BybitPositionIdx::BuyHedge,
            2 => BybitPositionIdx::SellHedge,
            _ => anyhow::bail!("invalid 'position_idx': {idx}, expected 0, 1, or 2"),
        });
    }

    let has_bbo_side_type = params.get("bbo_side_type").is_some();
    let has_bbo_level = params.get("bbo_level").is_some();

    if has_bbo_side_type != has_bbo_level {
        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") {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Use only 0, 1, or 2 for 'position_idx'
  2. Map the desired hedge mode to the correct Bybit index (OneWay=0, BuyHedge=1, SellHedge=2)
  3. Omit 'position_idx' if the account uses one-way mode and no override is needed

Example fix

// before
params.set("position_idx", 3);
// after
params.set("position_idx", 1); // BuyHedge
Defensive patterns

Strategy: validation

Validate before calling

fn validate_position_idx(params: &Params) -> Result<(), String> {
    if let Some(v) = params.get("position_idx") {
        match v.as_i64() {
            Some(i) if (0..=2).contains(&i) => Ok(()),
            other => Err(format!("position_idx must be 0, 1, or 2, got {other:?}")),
        }
    } else { Ok(()) }
}

Type guard

fn is_valid_position_idx(v: &Value) -> bool {
    matches!(v.as_i64(), Some(0 | 1 | 2))
}

Try / catch

match parse_bybit_tp_sl_params(&params) {
    Ok(p) => submit(p),
    Err(e) if e.to_string().contains("invalid 'position_idx'") => {
        log::warn("position_idx must be 0 (OneWay), 1 (BuyHedge), or 2 (SellHedge)");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Passing 'position_idx' with a value like 3, -1, or 99 in the TP/SL params.

Common situations: Loop-generated position indices; hardcoded index copied from another exchange's convention; off-by-one where modes were assumed 1-based.

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/a098e99b4b242cc9. Report an issue: GitHub.