{"record":{"id":"5df2f314f2d379e1","repo":"nautechsystems/nautilus_trader","slug":"no-completed-fx-rollover-batch-to-acknowledge","errorCode":null,"errorMessage":"no completed FX rollover batch to acknowledge","messagePattern":"no completed FX rollover batch to acknowledge","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/backtest/src/modules/fx_rollover.rs","lineNumber":663,"sourceCode":"        let adjustments = batch.iter().map(|adjustment| adjustment.amount).collect();\n        let mut day = self.rollover_day.borrow_mut();\n        let day = day.as_mut().expect(\"rollover day initialized\");\n        day.pending_adjustments = Some(batch);\n        day.pending_end_date = Some(batch_end_date);\n        day.attempt_time = Some(ts_now);\n        Ok(SimulationModuleResult::Completed(adjustments))\n    }\n\n    fn acknowledge(&self, outcomes: &[AccountAdjustmentOutcome]) -> anyhow::Result<()> {\n        let (adjustments, attempt_time, batch_end_date) = {\n            let mut day = self.rollover_day.borrow_mut();\n            let day = day\n                .as_mut()\n                .ok_or_else(|| anyhow::anyhow!(\"FX rollover day is not initialized\"))?;\n            let adjustment_count = day\n                .pending_adjustments\n                .as_ref()\n                .ok_or_else(|| anyhow::anyhow!(\"no completed FX rollover batch to acknowledge\"))?\n                .len();\n            anyhow::ensure!(\n                outcomes.len() == adjustment_count,\n                \"FX rollover acknowledgement count {}, expected {}\",\n                outcomes.len(),\n                adjustment_count\n            );\n            let adjustments = day\n                .pending_adjustments\n                .take()\n                .ok_or_else(|| anyhow::anyhow!(\"no completed FX rollover batch to acknowledge\"))?;\n            (\n                adjustments,\n                day.attempt_time\n                    .take()\n                    .ok_or_else(|| anyhow::anyhow!(\"FX rollover attempt time was not recorded\"))?,\n                day.pending_end_date.ok_or_else(|| {\n                    anyhow::anyhow!(\"FX rollover batch end date was not recorded\")","sourceCodeStart":645,"sourceCodeEnd":681,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/backtest/src/modules/fx_rollover.rs#L645-L681","documentation":"Raised in the FX rollover module's `acknowledge` when `day.pending_adjustments` is None — there is no completed rollover batch awaiting acknowledgement. Pending adjustments are set when a batch completes and taken on acknowledgement; acknowledging without a batch is an ordering/lifecycle violation.","triggerScenarios":"Calling `acknowledge` when no batch was completed for the day, calling it a second time for the same batch (first `take()` cleared it), or acking before `process_rollover` generated adjustments.","commonSituations":"Retry drivers that re-ack after failures; event pipelines delivering the ack out of order relative to batch completion; unit tests calling acknowledge directly without running the rollover pass.","solutions":["Only acknowledge outcomes produced by a completed rollover batch in the same processing cycle.","Track acknowledgement state in the driver to prevent duplicate ack calls.","If outcomes were lost, re-run the rollover batch generation instead of acking stale results.","Check that reset/init logic is not clearing pending state between batch completion and ack."],"exampleFix":"// before\nloop {\n    engine.acknowledge(&outcomes)?; // retry re-acks same batch\n}\n\n// after\nif !batch_acked {\n    engine.acknowledge(&outcomes)?;\n    batch_acked = true;\n}","handlingStrategy":"type-guard","validationCode":"anyhow::ensure!(engine.has_pending_fx_batch(), \"no completed FX rollover batch; run process_rollover first\");","typeGuard":"fn pending_fx_batch(engine: &FxRolloverModule) -> bool { engine.has_pending_batch() }","tryCatchPattern":"if let Err(e) = engine.acknowledge(&outcomes) {\n    if e.to_string().contains(\"no completed FX rollover batch\") {\n        log::warn!(\"ack without batch; check driver event ordering\");\n    } else { return Err(e); }\n}","preventionTips":["Pair every batch completion with exactly one acknowledgement","Never re-ack after failures; instead re-run batch generation","Verify resets do not clear pending state between completion and ack"],"tags":["backtest","fx","lifecycle"],"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-14T05:17:10.506Z"}