nautechsystems/nautilus_trader · error
Cannot persist position with no events: {}
Error message
Cannot persist position with no events: {} What it means
Raised by `position_last_event` when a `Position` has no events, i.e. `position.last_event()` returns None. The SQL cache persists positions by writing their latest `OrderFilled` event via `add_position`/`update_position`; a position with an empty event log (no opening fill) cannot be represented as a row, so this invariant violation aborts persistence.
Source
Thrown at crates/infrastructure/src/sql/cache.rs:1328
fn heartbeat(&self, _timestamp: UnixNanos) -> anyhow::Result<()> {
todo!()
}
}
fn account_last_event(account: &AccountAny) -> anyhow::Result<AccountState> {
account
.last_event()
.ok_or_else(|| anyhow::anyhow!("Cannot persist account with no events: {}", account.id()))
}
fn order_initialized_event(order: &OrderAny) -> OrderInitialized {
order.init_event().clone()
}
fn position_last_event(position: &Position) -> anyhow::Result<OrderFilled> {
position
.last_event()
.ok_or_else(|| anyhow::anyhow!("Cannot persist position with no events: {}", position.id))
}
#[expect(
clippy::too_many_lines,
reason = "database command dispatch enumerates each cache query variant explicitly"
)]
async fn drain_buffer(pool: &PgPool, buffer: &mut VecDeque<DatabaseQuery>) {
for cmd in buffer.drain(..) {
let result: anyhow::Result<()> = match cmd {
DatabaseQuery::Close => Ok(()),
DatabaseQuery::Add(key, value) => DatabaseQueries::add(pool, key, value).await,
DatabaseQuery::AddCurrency(currency) => {
DatabaseQueries::add_currency(pool, currency).await
}
DatabaseQuery::AddInstrument(instrument_any) => match instrument_any {
InstrumentAny::Betting(instrument) => {
DatabaseQueries::add_instrument(pool, "BETTING", Box::new(instrument)).await
}View on GitHub (pinned to 18893faf8b)
Solutions
- Only create positions through the normal OrderFilled flow so the opening event always exists before persistence.
- Check position.last_event().is_some() before calling add_position/update_position and skip/defer if None.
- Fix snapshot/event-replay reconstruction so the event log is preserved.
- Use position.id from the error message to locate the code that built the position without events.
Example fix
// before
cache.add_position(&position).await?;
// after
if position.last_event().is_some() {
cache.add_position(&position).await?;
} else {
tracing::warn!("skipping persistence of event-less position {}", position.id);
} Defensive patterns
Strategy: validation
Validate before calling
if position.last_event().is_none() {
return Err(anyhow::anyhow!(
"refusing to persist position {} with no events",
position.id
));
} Type guard
fn has_events(position: &Position) -> bool {
position.last_event().is_some()
} Try / catch
match cache.add_position(&position).await {
Err(e) if e.to_string().contains("no events") => {
tracing::warn!("skipping event-less position {}: {e}", position.id);
}
result => result?,
} Prevention
- Create positions only through the normal OrderFilled flow; never hand-construct them for persistence.
- Check last_event().is_some() before calling add_position/update_position.
- Verify snapshot/event-replay reconstruction preserves the position's event log.
- Cover position persistence in integration tests that exercise the full fill-to-cache path.
When it happens
Trigger: Calling `cache.add_position(position)` or `cache.update_position(position)` with a Position constructed without going through the normal fill flow: a manually built Position in tests or tools, a position reconstructed from snapshots with its event log dropped, or a fill pipeline that never recorded the opening OrderFilled.
Common situations: Strategy/backtest test harnesses that fabricate Position objects and push them to a Postgres cache; porting positions from an external system without replaying the opening fill; a snapshot-restore bug that yields event-less positions.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Cannot persist account with no events: {}
- Account {account_id} not found after cache update
- Cannot update position {}: not found in cache
- Cannot update position {position_id}: not found in cache
- Verified finality requires decision evidence
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/265606ab85ee6f39.
Report an issue: GitHub.