{"record":{"id":"090177aa5bf6468d","repo":"nautechsystems/nautilus_trader","slug":"fx-rollover-attempt-time-was-not-recorded","errorCode":null,"errorMessage":"FX rollover attempt time was not recorded","messagePattern":"FX rollover attempt time was not recorded","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/backtest/src/modules/fx_rollover.rs","lineNumber":679,"sourceCode":"                .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\")\n                })?,\n            )\n        };\n\n        let mut failed = Vec::new();\n        {\n            let mut totals = self.rollover_totals.borrow_mut();\n            let mut unapplied_totals = self.unapplied_rollover_totals.borrow_mut();\n\n            for (adjustment, outcome) in adjustments.into_iter().zip(outcomes) {\n                match outcome {\n                    AccountAdjustmentOutcome::Applied => {\n                        let total = totals.entry(adjustment.amount.currency).or_insert(0.0);\n                        *total += adjustment.amount.as_f64();\n                    }\n                    AccountAdjustmentOutcome::Failed(error) => {","sourceCodeStart":661,"sourceCodeEnd":697,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/backtest/src/modules/fx_rollover.rs#L661-L697","documentation":"Thrown when the rollover day's `attempt_time` was not recorded but the acknowledge phase expects it. The module records the attempt time when it starts a rollover batch; if it is missing while pending adjustments exist, internal bookkeeping was bypassed or state was partially restored.","triggerScenarios":"`pending_adjustments` is Some but `attempt_time` is None in the rollover day state when `process_rollover` reaches the acknowledge phase — typically from hand-constructed state, incomplete deserialization, or code that sets adjustments without recording the attempt.","commonSituations":"Manually injecting rollover day state in tests; partially restored snapshots; third-party code mutating module internals.","solutions":["Never construct/mutate RolloverDay state manually; use the module's public start/process API","Check snapshot/restore code to confirm attempt_time is serialized and restored with pending_adjustments","Upgrade the module if a released version had an incomplete state-transition bug"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"debug_assert!(day.pending_adjustments.is_none() || day.attempt_time.is_some());","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always start batches via the module API so attempt_time is recorded","Serialize/restore attempt_time together with pending_adjustments","Never hand-construct internal rollover day state"],"tags":["backtest","fx-rollover","internal-state"],"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"}