nautechsystems/nautilus_trader · error · PyValueError

Invalid BybitPositionMode value: {int_val}

Error message

Invalid BybitPositionMode value: {int_val}

What it means

The Python binding for BybitPositionMode converts incoming API payload integers to enum variants: only 0 (MergedSingle) and 3 (BothSides) are accepted. Any other integer, or an unrecognized variant-name string, raises this error.

Source

Thrown at crates/adapters/bybit/src/python/enums.rs:298

    pub fn value(&self) -> i32 {
        *self as i32
    }

    #[staticmethod]
    #[must_use]
    fn variants() -> Vec<String> {
        Self::iter().map(|x| x.to_string()).collect()
    }

    #[classmethod]
    #[pyo3(name = "from_str")]
    fn py_from_str(_cls: &Bound<'_, PyType>, data: &Bound<'_, PyAny>) -> PyResult<Self> {
        // Try to extract as integer first (for API payloads that send 0 or 3)
        if let Ok(int_val) = data.extract::<i32>() {
            return match int_val {
                0 => Ok(Self::MergedSingle),
                3 => Ok(Self::BothSides),
                _ => Err(to_pyvalue_err(anyhow::anyhow!(
                    "Invalid BybitPositionMode value: {int_val}"
                ))),
            };
        }

        // Fall back to string parsing for variant names
        let data_str: String = data.str()?.extract()?;
        Self::from_str(&data_str).map_err(to_pyvalue_err)
    }
}

#[pymethods]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
impl BybitPositionIdx {
    /// Position index values used for hedge mode payloads.
    #[new]
    fn py_new(py: Python<'_>, value: &Bound<'_, PyAny>) -> PyResult<Self> {
        let t = Self::type_object(py);

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Use 0 for MergedSingle (one-way) or 3 for BothSides (hedge mode) in integer payloads
  2. Pass exact variant names as strings: 'MergedSingle' or 'BothSides'
  3. Check the Bybit account/position-mode API response for unexpected values and update the adapter if Bybit added new modes

Example fix

// before: wrong integer mapping
mode = BybitPositionMode.from_str(1)
// after
mode = BybitPositionMode.from_str(3)  # BothSides (hedge mode)
Defensive patterns

Strategy: type-guard

Validate before calling

if isinstance(v, int) and v not in (0, 3):
    raise ValueError(f'position mode must be 0 (MergedSingle) or 3 (BothSides), got {v}')

Type guard

def is_valid_position_mode(v) -> bool:
    return v in (0, 3) or str(v) in ('MergedSingle', 'BothSides')

Try / catch

try:
    mode = BybitPositionMode.from_str(raw)
except ValueError as e:
    log.warning('bad position mode %r', raw); mode = BybitPositionMode.MergedSingle

Prevention

When it happens

Trigger: Deserializing a Bybit position-mode value from a payload where the integer is not 0 or 3 (e.g. 1 or 2), or passing an invalid string to BybitPositionMode.from_str in Python.

Common situations: Feeding values from another exchange's position-mode encoding; Bybit docs/versions using a different integer mapping; hand-written config using 'hedge' instead of 'BothSides'.

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