{"record":{"id":"cb52925784bf02bc","repo":"nautechsystems/nautilus_trader","slug":"order-side-differs-across-fill-group","errorCode":null,"errorMessage":"order side differs across fill group","messagePattern":"order side differs across fill group","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/live/src/execution/manager.rs","lineNumber":1291,"sourceCode":"\n        for fill in fills.iter().skip(1) {\n            anyhow::ensure!(\n                fill.account_id == first.account_id,\n                \"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();","sourceCodeStart":1273,"sourceCodeEnd":1309,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/live/src/execution/manager.rs#L1273-L1309","documentation":"When synthesizing an OrderStatusReport from a group of FillReports, the resulting report represents a single order with one side. This ensure! checks that every fill in the group has the same order_side; conflicting sides within one group means the fills cannot describe one coherent order and reconciliation aborts.","triggerScenarios":"Two or more FillReports grouped as belonging to the same order (same account, instrument, venue/client order ID) carry different OrderSide values (e.g. one BUY and one SELL), such as when closes of opposite-side positions are mis-grouped together.","commonSituations":"Hedged-mode venues where opposite-side fills share a position but belong to different orders; adapter grouping fills by account+instrument instead of by order; corrupted or hand-edited replayed fill data.","solutions":["Group fills by the full order key (account, instrument, venue_order_id, client_order_id, order_side) instead of instrument or position alone.","Check the adapter's fill-report parsing to confirm order_side is read from the correct venue field and not defaulted or inferred inconsistently.","Split mixed-side groups into separate orphan orders, one per side, before calling create_orphan_fill_order_report.","Validate the venue's raw execution reports: if the venue itself reports conflicting sides for one order ID, capture that payload and report it to the adapter maintainers."],"exampleFix":"// before: key = (account, instrument)\nlet key = (account_id, instrument_id);\n// after: include side in the group key\nlet key = (account_id, instrument_id, venue_order_id, order_side);","handlingStrategy":"validation","validationCode":"let sides: std::collections::HashSet<_> = fills.iter().map(|f| f.order_side).collect();\nassert!(sides.len() == 1, \"fill group has mixed order sides: {:?}\", sides);","typeGuard":"fn single_side_group(fills: &[&FillReport]) -> bool {\n    fills.iter().all(|f| f.order_side == fills[0].order_side)\n}","tryCatchPattern":null,"preventionTips":["Include order_side in the fill-group key, especially on hedging-mode accounts.","Confirm the adapter reads order_side from the venue's canonical field, not an inferred default.","Split opposite-side fills into separate orphan orders before aggregation."],"tags":["reconciliation","rust","order-side","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"}