{"record":{"id":"02173d92ce5730cf","repo":"nautechsystems/nautilus_trader","slug":"venue-position-id-differs-across-fill-group","errorCode":null,"errorMessage":"venue position ID differs across fill group","messagePattern":"venue position ID differs across fill group","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/live/src/execution/manager.rs","lineNumber":1295,"sourceCode":"                \"account ID differs across fill group\"\n            );\n            anyhow::ensure!(\n                fill.instrument_id == first.instrument_id,\n                \"instrument ID differs across fill group\"\n            );\n            anyhow::ensure!(\n                fill.venue_order_id == first.venue_order_id,\n                \"venue order ID differs across fill group\"\n            );\n            anyhow::ensure!(\n                fill.client_order_id == first.client_order_id,\n                \"client order ID differs across fill group\"\n            );\n            anyhow::ensure!(\n                fill.order_side == first.order_side,\n                \"order side differs across fill group\"\n            );\n            anyhow::ensure!(\n                fill.venue_position_id == first.venue_position_id,\n                \"venue position ID differs across fill group\"\n            );\n        }\n\n        anyhow::ensure!(\n            first.instrument_id == instrument.id(),\n            \"instrument metadata does not match fill group\"\n        );\n\n        let (quantity, notional) = fills.iter().try_fold(\n            (Decimal::ZERO, Decimal::ZERO),\n            |(quantity, notional), fill| {\n                let fill_quantity = fill.last_qty.as_decimal();\n                let quantity = quantity.checked_add(fill_quantity).ok_or_else(|| {\n                    anyhow::anyhow!(\"fill quantity overflow while aggregating fill group\")\n                })?;\n","sourceCodeStart":1277,"sourceCodeEnd":1313,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/live/src/execution/manager.rs#L1277-L1313","documentation":"A synthesized OrderStatusReport for an orphan fill group must resolve to one position. This ensure! checks that all fills in the group report the same venue_position_id (earlier code already bail!s if it is missing entirely); differing position IDs mean the group spans multiple positions and cannot be aggregated into one order report.","triggerScenarios":"FillReports grouped together share order/account/instrument keys but their venue_position_id values differ — e.g. fills that were assigned to different OMS positions by the venue, or None vs Some mismatches that slipped past grouping.","commonSituations":"Netting vs hedging account-mode mismatches between the venue account and the Nautilus reconciliation config; venue splitting one order's fills across positions after account mode changes; adapter changes to position-ID derivation between versions.","solutions":["Ensure the adapter's account configuration (netting vs hedging) matches the actual venue account mode so fills map to a single position ID per order.","Group fills by venue_position_id as part of the key so each group has one consistent position ID.","Check that venue_position_id is parsed from the venue's fill report rather than derived inconsistently per fill.","Re-run reconciliation after correcting account mode / position ID derivation so stale groups are rebuilt from fresh venue reports."],"exampleFix":"// before: group keyed without position\nlet key = (account_id, venue_order_id);\n// after: require a single position ID per group\nlet key = (account_id, venue_order_id, fill.venue_position_id?);","handlingStrategy":"validation","validationCode":"let pos_ids: std::collections::HashSet<_> = fills.iter().map(|f| f.venue_position_id).collect();\nassert!(pos_ids.len() == 1 && pos_ids.iter().all(|p| p.is_some()), \"fill group has missing or mixed venue position IDs: {:?}\", pos_ids);","typeGuard":"fn single_position_group(fills: &[&FillReport]) -> bool {\n    fills[0].venue_position_id.is_some()\n        && fills.iter().all(|f| f.venue_position_id == fills[0].venue_position_id)\n}","tryCatchPattern":null,"preventionTips":["Match the trader's account configuration (netting vs hedging) to the venue's actual account mode.","Group fills by venue_position_id so each group resolves to exactly one position.","Re-run reconciliation after any account-mode or adapter-version change instead of reusing old fill groups."],"tags":["reconciliation","rust","position-id","invariant-violation"],"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"}