nautechsystems/nautilus_trader · error

Cannot persist account with no events: {}

Error message

Cannot persist account with no events: {}

What it means

Raised by `account_last_event` when an `AccountAny` has no events, i.e. `account.last_event()` returns None. The SQL cache persists accounts by writing their latest `AccountState` event via `add_account`/`update_account`; an account with an empty event log cannot be represented in the database, so persisting it is treated as an internal invariant violation and aborted with this anyhow error.

Source

Thrown at crates/infrastructure/src/sql/cache.rs:1318

    ) -> anyhow::Result<()> {
        let mut snapshot = if position.fill_voids.is_empty() {
            PositionSnapshot::from(position, unrealized_pnl)
        } else {
            PositionSnapshot::from_replay_state(position, unrealized_pnl)
        };
        snapshot.ts_init = ts_snapshot;
        self.add_position_snapshot(&snapshot)
    }

    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(..) {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure every account receives its initial AccountState event (apply it) before calling add_account/update_account on the cache.
  2. Check account.last_event().is_some() before persisting; if None, skip persistence or defer until the first event arrives.
  3. Trace the account id from the error message to where it was constructed and fix the code path that failed to apply events.
  4. Verify reconstruction/deserialization logic isn't dropping the account's event log.

Example fix

// before
cache.add_account(&account).await?;
// after
if account.last_event().is_some() {
    cache.add_account(&account).await?;
} else {
    tracing::warn!("skipping persistence of event-less account {}", account.id());
}
Defensive patterns

Strategy: validation

Validate before calling

if account.last_event().is_none() {
    return Err(anyhow::anyhow!(
        "refusing to persist account {} with no events",
        account.id()
    ));
}

Type guard

fn has_events(account: &AccountAny) -> bool {
    account.last_event().is_some()
}

Try / catch

match cache.add_account(&account).await {
    Err(e) if e.to_string().contains("no events") => {
        tracing::warn!("skipping event-less account {}: {e}", account.id());
    }
    result => result?,
}

Prevention

When it happens

Trigger: Calling `cache.add_account(account)` or `cache.update_account(account)` (PostgresCacheAdapter) with an account that has never had an `AccountState` event applied: a hand-constructed account object, a deserialized/reconstructed account whose event log was dropped, or an adapter that failed to emit the initial state event.

Common situations: Custom broker/adapter code that builds an `AccountAny` manually in tests and registers it with the cache before any state update; restoring accounts from an external system without replaying their initial state event; a bug in an event-generating client that never publishes the first AccountState.

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/fa226fde7f8a438b. Report an issue: GitHub.