nautechsystems/nautilus_trader · error
Cannot persist position event with no position_id: {}
Error message
Cannot persist position event with no position_id: {} What it means
event_position_id extracts the Option<PositionId> from an OrderFilled event and errors when it is None, because a position_event row cannot be written without its position_id foreign reference. This is a data-integrity guard: only fills that are part of an opened position carry a position_id.
Source
Thrown at crates/infrastructure/src/sql/queries.rs:1079
.bind(event.commission.map(|commission| commission.to_string()))
.bind(event.reconciliation)
.bind(position_event_info)
.bind(
event
.causation_id
.map(|causation_id| causation_id.to_string()),
)
.bind(event.ts_event.to_string())
.bind(event.ts_init.to_string())
.execute(&mut **transaction)
.await
.map(|_| ())
.map_err(|e| anyhow::anyhow!("Failed to insert into position_event table: {e}"))
}
fn event_position_id(event: &OrderFilled) -> anyhow::Result<PositionId> {
event.position_id.ok_or_else(|| {
anyhow::anyhow!(
"Cannot persist position event with no position_id: {}",
event.event_id
)
})
}
/// Inserts or updates an `AccountState` event via the provided `pool`.
///
/// # Errors
///
/// Returns an error if the SQL INSERT or UPDATE operation fails.
pub async fn add_account(
pool: &PgPool,
updated: bool,
account_event: AccountState,
) -> anyhow::Result<()> {
if updated {
let exists =View on GitHub (pinned to 18893faf8b)
Solutions
- Check why the fill has no position: confirm the order opened/maintains a position and that the execution engine set position_id on the fill.
- Filter out fills with position_id None before handing them to the database cache if they are not meant to be persisted as position events.
- If constructing OrderFilled manually (tests/fixtures), set position_id to a valid PositionId.
- Log and skip such events instead of failing the whole persistence batch if they are expected.
Example fix
// before: cache errors on fills without a position
cache.insert_position_event(&event).await?;
// after: guard before persisting
if let Some(_pos) = event.position_id {
cache.insert_position_event(&event).await?;
} else {
log::warn!("Skipping persist, no position_id for event {}", event.event_id);
} Defensive patterns
Strategy: type-guard
Validate before calling
// caller-side check before persisting
if event.position_id.is_none() {
log::warn!("fill {} has no position_id; not a position event", event.event_id);
} Type guard
fn has_position(event: &OrderFilled) -> bool {
event.position_id.is_some()
} Try / catch
match cache.insert_position_event(&event).await {
Err(e) if e.to_string().contains("no position_id") => {
log::warn!("skipping non-position fill {}: {e}", event.event_id);
}
other => other?,
} Prevention
- Only route OrderFilled events with a set position_id to the position-event persistence path.
- Ensure your venue/adapter populates position_id on fills (open positions correctly before order submission).
- In test fixtures, always set position_id on OrderFilled.
- Validate event completeness at the boundary where events enter the persistence pipeline.
When it happens
Trigger: insert_position_event is called with an OrderFilled whose position_id field is None — e.g. fills on orders that never opened a position (position_id was not set by the emulation/exec engine), or an OrderFilled reconstructed from a stream/fixture that dropped the field.
Common situations: Persisting fill events from venues or custom adapters that do not populate position_id; replaying events through a path that lost the position linkage; testing with hand-constructed OrderFilled fixtures where position_id was left as None.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- Cannot persist position event {} for mismatched position_id:
- Intervals are not contiguous. When ensure_contiguous_files=t
- Intervals are not disjoint after resetting file names
- Persisted swap intent has no pool address
- Persisted swap intent has no input amount
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/ed398fb70942225f.
Report an issue: GitHub.