nautechsystems/nautilus_trader · error

Cannot persist position event {} for mismatched position_id:

Error message

Cannot persist position event {} for mismatched position_id: expected {}, was {}

What it means

add_position persists a position-opening event (OrderFilled) keyed by a PositionId. Before writing, it re-derives the position_id from the event via event_position_id and compares it with the caller-supplied position_id; on mismatch it bails rather than persisting an event under the wrong key, protecting the position event stream's integrity.

Source

Thrown at crates/infrastructure/src/sql/queries.rs:850

                orders.push(order);
            }
        }
        Ok(orders)
    }

    /// Replaces the fill event log for a `position_id` via the provided `pool`.
    ///
    /// # Errors
    ///
    /// Returns an error if the fill is invalid or if the SQL operations fail.
    pub async fn add_position(
        pool: &PgPool,
        position_id: PositionId,
        event: &OrderFilled,
    ) -> anyhow::Result<()> {
        let event_position_id = Self::event_position_id(event)?;
        if event_position_id != position_id {
            anyhow::bail!(
                "Cannot persist position event {} for mismatched position_id: expected {}, was {}",
                event.event_id,
                position_id,
                event_position_id
            );
        }

        let mut transaction = pool.begin().await?;

        sqlx::query(r#"DELETE FROM "position_event" WHERE position_id = $1"#)
            .bind(position_id.to_string())
            .execute(&mut *transaction)
            .await
            .map(|_| ())
            .map_err(|e| anyhow::anyhow!("Failed to delete position_event rows: {e}"))?;

        Self::insert_position_event(&mut transaction, event).await?;
        transaction

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify the caller derives the PositionId from the same OrderFilled event it passes (same instrument_id, venue, and position identity logic)
  2. Check for stale/reused events: ensure the event belongs to the position you are adding (match event position/instrument ids before calling)
  3. Log the expected vs actual position_id in the message and fix the origin of the mismatched ID (usually the strategy or reconciliation code)
  4. If events come from a cache/reconciliation stream, confirm ordering so the fill that opens position X is not attributed to position Y

Example fix

// before: mismatched id -> bail
cache.add_position(&stale_position_id, &fill_event)?;
// after: derive id from the event itself
let position_id = PositionId::from(&fill_event);
cache.add_position(&position_id, &fill_event)?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust: guard at the call site
let derived = PositionId::from(&fill_event);
assert_eq!(derived, position_id, "event position id mismatch before add_position");

Try / catch

if let Err(e) = cache.add_position(&position_id, &fill_event) {
    if e.to_string().contains("mismatched position_id") {
        // re-derive id from the event and retry once
        let pid = PositionId::from(&fill_event);
        cache.add_position(&pid, &fill_event)?;
    } else { return Err(e); }
}

Prevention

When it happens

Trigger: Calling add_position with a position_id that differs from the one derivable from the OrderFilled event — e.g. passing a stale or wrong PositionId, reusing an event from a different position, or a client bug constructing position_id/instrument_id combinations inconsistently with the event.

Common situations: Backfill or replay code passing position IDs from a different venue/instrument than the fill event; mixing up position IDs when multiple instruments trade concurrently; custom persistence adapters forwarding events out of order or from the wrong position.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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