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

  1. Only create positions through the normal OrderFilled flow so the opening event always exists before persistence.
  2. Check position.last_event().is_some() before calling add_position/update_position and skip/defer if None.
  3. Fix snapshot/event-replay reconstruction so the event log is preserved.
  4. 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

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


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