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?;
transactionView on GitHub (pinned to 18893faf8b)
Solutions
- Verify the caller derives the PositionId from the same OrderFilled event it passes (same instrument_id, venue, and position identity logic)
- Check for stale/reused events: ensure the event belongs to the position you are adding (match event position/instrument ids before calling)
- Log the expected vs actual position_id in the message and fix the origin of the mismatched ID (usually the strategy or reconciliation code)
- 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
- Always derive PositionId from the same event you persist (PositionId::from(&event))
- Avoid caching position IDs across instrument/venue changes; key them per instrument
- In reconciliation/replay code, validate event ordering so fills are attributed to the correct position
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
- Failed to load order events: {e}
- Cannot persist bar with composite bar type {}: the bar table
- Cannot write {type_name} data with mixed identities: element
- {type_name} timestamps must be in ascending order
- Failed to assign nonce {nonce} to execution intent: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/00f53d6af989c03e.
Report an issue: GitHub.