nautechsystems/nautilus_trader · error

Cannot persist position event

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.

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.

Appendix: 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)