{"record":{"id":"b933849aeb027691","repo":"nautechsystems/nautilus_trader","slug":"duplicate-position-fill-void-for","errorCode":null,"errorMessage":"duplicate position fill void for {}","messagePattern":"duplicate position fill void for (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/model/src/position.rs","lineNumber":763,"sourceCode":"            .fold(Quantity::zero(self.size_precision), |total, fill| {\n                total + fill.last_qty\n            });\n        anyhow::ensure!(\n            !voided_qty.is_zero() && voided_qty <= fragment_qty,\n            \"position fill void exceeds known fragments for {}\",\n            event.trade_id,\n        );\n\n        if let Some(previous) = self.fill_voids.iter().rev().find(|record| {\n            record.event.client_order_id == event.client_order_id\n                && record.event.trade_id == event.trade_id\n        }) {\n            anyhow::ensure!(\n                voided_qty >= previous.voided_qty,\n                \"stale position fill void for {}\",\n                event.trade_id,\n            );\n            anyhow::ensure!(\n                voided_qty != previous.voided_qty\n                    || commission_voided != previous.commission_voided,\n                \"duplicate position fill void for {}\",\n                event.trade_id,\n            );\n        }\n\n        self.fill_voids.push(PositionFillVoid {\n            event,\n            voided_qty,\n            commission_voided,\n        });\n\n        Ok(self.rebuild_from_replay())\n    }\n\n    /// Returns durable fill fragments matching an order trade in local application order.\n    #[must_use]","sourceCodeStart":745,"sourceCodeEnd":781,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/position.rs#L745-L781","documentation":"Position::apply_fill_void rejects a void event identical to the most recent one already applied for the same client_order_id/trade_id (same voided_qty and commission_voided). Applying the identical cumulative void twice would double-count it, so exact duplicates are treated as invalid idempotency violations rather than no-ops.","triggerScenarios":"Re-applying the exact same void event twice — e.g. an event bus delivering a duplicate message, a replay engine feeding the same historical trade-adjustment twice, or retrying apply_fill_void after an ambiguous failure where the first call actually succeeded.","commonSituations":"Historical data containing duplicate trade-adjustment records; at-least-once event delivery from a message broker; manually re-running a replay script that already mutated the position.","solutions":["Track applied void events by (client_order_id, trade_id, voided_qty, commission_voided) and skip exact duplicates before calling apply_fill_void.","Deduplicate the event stream upstream (e.g. by event id/sequence) before feeding position mutations.","If a duplicate should be idempotent in your flow, detect it via the position's fill_voids and treat it as a no-op instead of an error path."],"exampleFix":"// before\nposition.apply_fill_void(ev)?; // errors on replayed duplicate\n// after: idempotent skip\nlet dup = position.fill_voids.iter().rev().any(|r| {\n    r.event.client_order_id == ev.client_order_id\n        && r.event.trade_id == ev.trade_id\n        && r.voided_qty == ev.voided_qty\n        && r.commission_voided == ev.commission_voided\n});\nif !dup { position.apply_fill_void(ev)?; }","handlingStrategy":"validation","validationCode":"let is_dup = position.fill_voids.iter().rev().any(|r| {\n    r.event.client_order_id == ev.client_order_id\n        && r.event.trade_id == ev.trade_id\n        && r.voided_qty == ev.voided_qty\n        && r.commission_voided == ev.commission_voided\n});\nif is_dup { return Ok(()); } // idempotent no-op","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Deduplicate event streams by id/sequence before applying position mutations.","Make void application idempotent at the call site by checking fill_voids first.","Avoid re-running replay scripts against already-mutated positions."],"tags":["position","pnl","duplicate-events","idempotency","rust"],"backgroundTag":"invalid-state-transition","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"}