{"record":{"id":"bb6fe81372783127","repo":"nautechsystems/nautilus_trader","slug":"list-of-positions-is-empty","errorCode":null,"errorMessage":"List of Positions is empty","messagePattern":"List of Positions is empty","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/portfolio/src/manager.rs","lineNumber":95,"sourceCode":"        // Snapshot only what the balance update can mutate: cloning the account would\n        // deep-copy its event log, which grows by one entry per fill.\n        let base = base_account(&account);\n        let original_balances = base.balances.clone();\n        let original_commissions = base.commissions.clone();\n        let position_id = if let Some(position_id) = fill.position_id {\n            position_id\n        } else {\n            let cache = self.cache.borrow();\n            let positions_open = cache.positions_open(\n                None,\n                Some(&fill.instrument_id),\n                None,\n                Some(&fill.account_id),\n                None,\n            );\n            positions_open\n                .first()\n                .unwrap_or_else(|| panic!(\"List of Positions is empty\"))\n                .id\n        };\n\n        let position = self\n            .cache\n            .borrow()\n            .position(&position_id)\n            .map(|position| position.clone_without_events());\n\n        let pnls = match account.calculate_pnls(instrument, fill, position) {\n            Ok(pnls) => pnls,\n            Err(e) => {\n                log::error!(\n                    \"Cannot update balances for fill {}: failed to calculate PnL: {e}\",\n                    fill.trade_id\n                );\n                let state = self.generate_account_state(&account, fill.ts_event);\n                return (account, state);","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/portfolio/src/manager.rs#L77-L113","documentation":"PortfolioManager::update_balances looks up the open positions for an account (filtered by instrument_id/instrument_ids arguments) and takes the first position to derive the position id for balance recalculation. If the cache reports positions that then produce an empty list, it panics with 'List of Positions is empty'. The error indicates an internal inconsistency: the cache said there were positions, but none were retrievable.","triggerScenarios":"Calling portfolio.update_balances(...) for an account that actually has no open positions (or for an instrument_id filter that matches none) so positions_open.first() is None.","commonSituations":"Calling update_balances at startup before any fills created positions; passing an instrument_id that has no position while the account does elsewhere; a cache/position index out of sync after reconciliation or order-event race.","solutions":["Check that at least one open position exists for the account/instrument before calling update_balances (e.g. cache.positions_open(&venue, Some(&instrument_id)).is_some())","Only call update_balances in response to fill/position events where a position definitely exists","Verify the instrument_id and account_id arguments match the filled instrument","If the mismatch persists, inspect cache position indexing for a reconciliation bug"],"exampleFix":"// before\nportfolio.update_balances(None, Some(&account_id), None, Some(&instrument_id));\n// after\nif !cache.borrow().positions_open(Some(&venue), Some(&instrument_id)).is_empty() {\n    portfolio.update_balances(None, Some(&account_id), None, Some(&instrument_id));\n}","handlingStrategy":"type-guard","validationCode":"let has_position = !cache.borrow()\n    .positions_open(Some(&venue), instrument_id.as_ref())\n    .is_empty();","typeGuard":"fn has_open_position(cache: &Cache, venue: &Venue, instrument_id: Option<&InstrumentId>) -> bool {\n    !cache.positions_open(Some(venue), instrument_id).is_empty()\n}","tryCatchPattern":null,"preventionTips":["Only call update_balances after a fill/position event confirmed a position exists","Check positions_open for the specific instrument before the call","Ensure account_id and instrument_id filters actually match existing positions","Investigate cache/position index desynchronization after reconciliation"],"tags":["rust","portfolio","positions","panic"],"backgroundTag":"empty-result-set","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"}