nautechsystems/nautilus_trader · error

Invalid `PositionSide`

Error message

Invalid `PositionSide`

What it means

`AccountPosition::from_row` parses the `side` column as a `PositionSide` via `PositionSide::from_str` and unwraps with `.expect("Invalid `PositionSide`")`. A `side` value that is not a valid `PositionSide` variant ('LONG', 'SHORT', 'FLAT') causes the row deserialization to panic. This is a defensive guard against corrupted or foreign-written rows.

Source

Thrown at crates/infrastructure/src/sql/models/positions.rs:57

        let instrument_id = row
            .try_get::<&str, _>("instrument_id")
            .map(InstrumentId::from)?;
        let account_id = row.try_get::<&str, _>("account_id").map(AccountId::from)?;
        let opening_order_id = row
            .try_get::<&str, _>("opening_order_id")
            .map(ClientOrderId::from)?;
        let closing_order_id = row
            .try_get::<Option<&str>, _>("closing_order_id")
            .ok()
            .and_then(|x| x.map(ClientOrderId::from));
        let entry = row
            .try_get::<&str, _>("entry")
            .map(OrderSide::from_str)?
            .expect("Invalid `OrderSide`");
        let side = row
            .try_get::<&str, _>("side")
            .map(PositionSide::from_str)?
            .expect("Invalid `PositionSide`");
        let signed_qty = row.try_get::<f64, _>("signed_qty")?;
        let quantity = row.try_get::<&str, _>("quantity").map(Quantity::from)?;
        let peak_qty = row.try_get::<&str, _>("peak_qty").map(Quantity::from)?;
        let quote_currency = row
            .try_get::<&str, _>("quote_currency")
            .map(Currency::from)?;
        let base_currency = row
            .try_get::<Option<&str>, _>("base_currency")
            .ok()
            .and_then(|x| x.map(Currency::from));
        let settlement_currency = row
            .try_get::<&str, _>("settlement_currency")
            .map(Currency::from)?;
        let avg_px_open = row.try_get::<f64, _>("avg_px_open")?;
        let avg_px_close = row.try_get::<Option<f64>, _>("avg_px_close")?;
        let realized_return = row.try_get::<Option<f64>, _>("realized_return")?;
        let realized_pnl = row.try_get::<&str, _>("realized_pnl").map(Money::from)?;
        let unrealized_pnl = row

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the `side` value of the failing row and rewrite it as a valid `PositionSide` ('LONG', 'SHORT', or 'FLAT')
  2. Run `SELECT DISTINCT side FROM positions` and validate each value against `PositionSide::from_str`
  3. Fix the writer/adapter that produced the non-canonical string so it uses the library serializer
  4. Rebuild affected rows from underlying order/fill events if source data is available

Example fix

// before
side = 'long'
// after
side = 'LONG'
Defensive patterns

Strategy: validation

Validate before calling

fn valid_position_side(s: &str) -> bool { PositionSide::from_str(s).is_some() }
// check before persisting or accepting positions rows

Type guard

fn parse_position_side(s: &str) -> Option<PositionSide> { PositionSide::from_str(s) }

Prevention

When it happens

Trigger: Reading a `positions` row whose `side` column contains an unrecognized string (e.g. empty, lowercase 'long', 'LONG_POSITION'), typically from manual inserts, ETL imports, or a schema/version mismatch between writer and reader.

Common situations: Manual data fixes on the positions table; importing positions from another system with different side naming; backup restores across nautilus versions with changed enum values.

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