{"record":{"id":"265606ab85ee6f39","repo":"nautechsystems/nautilus_trader","slug":"cannot-persist-position-with-no-events-265606","errorCode":null,"errorMessage":"Cannot persist position with no events: {}","messagePattern":"Cannot persist position with no events: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/infrastructure/src/sql/cache.rs","lineNumber":1328,"sourceCode":"    fn heartbeat(&self, _timestamp: UnixNanos) -> anyhow::Result<()> {\n        todo!()\n    }\n}\n\nfn account_last_event(account: &AccountAny) -> anyhow::Result<AccountState> {\n    account\n        .last_event()\n        .ok_or_else(|| anyhow::anyhow!(\"Cannot persist account with no events: {}\", account.id()))\n}\n\nfn order_initialized_event(order: &OrderAny) -> OrderInitialized {\n    order.init_event().clone()\n}\n\nfn position_last_event(position: &Position) -> anyhow::Result<OrderFilled> {\n    position\n        .last_event()\n        .ok_or_else(|| anyhow::anyhow!(\"Cannot persist position with no events: {}\", position.id))\n}\n\n#[expect(\n    clippy::too_many_lines,\n    reason = \"database command dispatch enumerates each cache query variant explicitly\"\n)]\nasync fn drain_buffer(pool: &PgPool, buffer: &mut VecDeque<DatabaseQuery>) {\n    for cmd in buffer.drain(..) {\n        let result: anyhow::Result<()> = match cmd {\n            DatabaseQuery::Close => Ok(()),\n            DatabaseQuery::Add(key, value) => DatabaseQueries::add(pool, key, value).await,\n            DatabaseQuery::AddCurrency(currency) => {\n                DatabaseQueries::add_currency(pool, currency).await\n            }\n            DatabaseQuery::AddInstrument(instrument_any) => match instrument_any {\n                InstrumentAny::Betting(instrument) => {\n                    DatabaseQueries::add_instrument(pool, \"BETTING\", Box::new(instrument)).await\n                }","sourceCodeStart":1310,"sourceCodeEnd":1346,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/infrastructure/src/sql/cache.rs#L1310-L1346","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ncache.add_position(&position).await?;\n// after\nif position.last_event().is_some() {\n    cache.add_position(&position).await?;\n} else {\n    tracing::warn!(\"skipping persistence of event-less position {}\", position.id);\n}","handlingStrategy":"validation","validationCode":"if position.last_event().is_none() {\n    return Err(anyhow::anyhow!(\n        \"refusing to persist position {} with no events\",\n        position.id\n    ));\n}","typeGuard":"fn has_events(position: &Position) -> bool {\n    position.last_event().is_some()\n}","tryCatchPattern":"match cache.add_position(&position).await {\n    Err(e) if e.to_string().contains(\"no events\") => {\n        tracing::warn!(\"skipping event-less position {}: {e}\", position.id);\n    }\n    result => result?,\n}","preventionTips":["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."],"tags":["rust","database","cache","position","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}