nautechsystems/nautilus_trader · error · anyhow::Error

unknown position_side

Error message

unknown position_side

What it means

make_venue_position_id builds a venue PositionId from the position_side field of exchange order updates. BinancePositionSide::Unknown has no mapping, so the function bails with 'unknown position_side'. Both and Long/Short have defined behavior (None and LONG/SHORT suffix).

Source

Thrown at crates/adapters/binance/src/futures/websocket/streams/dispatch.rs:1136

///
/// # Errors
///
/// Returns an error when position IDs are enabled but the position side is missing or unknown.
pub(crate) fn make_venue_position_id(
    use_position_ids: bool,
    instrument_id: InstrumentId,
    position_side: Option<BinancePositionSide>,
) -> anyhow::Result<Option<PositionId>> {
    if !use_position_ids {
        return Ok(None);
    }

    let position_side = position_side.context("missing position_side")?;
    let side = match position_side {
        BinancePositionSide::Long => "LONG",
        BinancePositionSide::Short => "SHORT",
        BinancePositionSide::Both => return Ok(None),
        BinancePositionSide::Unknown => anyhow::bail!("unknown position_side"),
    };
    Ok(Some(PositionId::new(format!("{instrument_id}-{side}"))))
}

/// Dispatches exchange-generated order fills (liquidation, ADL, settlement).
///
/// Bundles the parsed `OrderStatusReport` and `FillReport` into a single
/// `OrderWithFills` send so the engine creates the external order from the
/// status report and applies the real fill (preserving `trade_id` and
/// `commission`) instead of synthesizing one. Falls back to whichever report
/// parsed if the other parser fails.
///
/// Skips events with zero fill quantity (pending liquidation notifications).
#[expect(clippy::too_many_arguments)]
pub(crate) fn dispatch_exchange_generated_fill(
    msg: &BinanceFuturesOrderUpdateMsg,
    emitter: &ExecutionEventEmitter,
    instrument_id: InstrumentId,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the raw payload's positionSide value and confirm it is one of LONG, SHORT, BOTH.
  2. Update the BinancePositionSide enum/deserializer if Binance added a new variant.
  3. Fix mock/test payloads to emit valid positionSide values.

Example fix

// before
{"positionSide": "BOTH "} // unrecognized due to whitespace -> Unknown
// after
{"positionSide": "BOTH"} // valid: returns Ok(None) in one-way mode
Defensive patterns

Strategy: validation

Validate before calling

fn known_position_side(raw: &str) -> bool {
    matches!(raw, "LONG" | "SHORT" | "BOTH")
}

Try / catch

match result {
    Err(e) if e.to_string().contains("unknown position_side") => {
        tracing::error!("unrecognized positionSide in update: log raw payload and inspect");
    }
    r => r?,
}

Prevention

When it happens

Trigger: dispatch_order_update or dispatch_algo_update receives an order update whose positionSide deserializes to BinancePositionSide::Unknown (typically an unrecognized side string from the exchange) while position IDs are enabled.

Common situations: A new/renamed Binance positionSide value the adapter does not recognize; corrupted or non-standard payloads (e.g. from a test mock or API change); deserializer defaulting unknown enum variants to Unknown.

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