{"record":{"id":"c40669ea719ec373","repo":"nautechsystems/nautilus_trader","slug":"next-index-is-within-the-data-batch","errorCode":null,"errorMessage":"next index is within the data batch","messagePattern":"next index is within the data batch","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/backtest/src/data_iterator.rs","lineNumber":309,"sourceCode":"        // Multi-stream path using heap\n        let Some(entry) = self.heap.pop() else {\n            return;\n        };\n\n        let Some(stream) = self.streams.get(&entry.priority) else {\n            return;\n        };\n\n        // Advance cursor and push next entry\n        let next_index = entry.index + 1;\n        self.indices.insert(entry.priority, next_index);\n\n        if next_index < stream.len() {\n            self.heap.push(HeapEntry {\n                key: replay_key(\n                    stream\n                        .get(next_index)\n                        .expect(\"next index is within the data batch\"),\n                ),\n                priority: entry.priority,\n                index: next_index,\n            });\n        }\n    }\n\n    /// Returns the next backtest data element across all streams in replay order.\n    pub(crate) fn next_item(&mut self) -> Option<Data> {\n        let element = if let Some(p) = self.single_priority {\n            let data = self.streams.get(&p)?;\n            let idx = *self.indices.get(&p)?;\n            data.get_owned(idx)?\n        } else {\n            let entry = self.heap.peek()?;\n            self.streams.get(&entry.priority)?.get_owned(entry.index)?\n        };\n","sourceCodeStart":291,"sourceCodeEnd":327,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/backtest/src/data_iterator.rs#L291-L327","documentation":"During heap replay in the backtest DataIterator, an entry's cursor is advanced to next_index and the corresponding item is fetched from the stream batch before being pushed back onto the heap. The code asserts with expect() that next_index is in bounds because the caller only advances when next_index < stream.len(). Violation indicates an internal cursor/heap bookkeeping bug, not bad user input.","triggerScenarios":"Only reachable via a defect in DataIterator's heap replay logic where an entry is re-pushed with next_index >= stream.len() (stale index, double-advance, or mutated stream). Normal API use (iterating the backtest data stream) cannot trigger it.","commonSituations":"Encountered only after upgrading NautilusTrader versions where iterator internals changed, or when a custom data stream implementation mutates during iteration. End users should never see this.","solutions":["Verify no custom code mutates the underlying data batch while iterating","Reproduce with a minimal dataset and report the bug to NautilusTrader maintainers with the repro","Pin/upgrade to a version where the data_iterator heap bug is fixed"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// Guard custom data batches passed to the backtest iterator:\nassert!(stream.iter().all(|d| !is_stale(d)), \"stream contains stale entries\");","typeGuard":"fn in_bounds(idx: usize, stream_len: usize) -> bool { idx < stream_len }","tryCatchPattern":"// Panic is not catchable in normal Rust; verify with a custom Clock/TestClock harness\n// and report a repro to maintainers if hit.","preventionTips":["Do not mutate data batches while the backtest iterator is running","Use the standard DataEngine/data loading paths rather than custom stream injection","Test custom streams against a small fixed dataset before long runs"],"tags":["rust","panic","backtest","invariant","iterator"],"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-14T05:17:10.506Z"}