nautechsystems/nautilus_trader · error

Data has been added but not sorted, call `engine.sort_data()

Error message

Data has been added but not sorted, call `engine.sort_data()` or use `engine.add_data(..., sort=true)` before running

What it means

The backtest engine requires its internal data to be chronologically sorted before run_impl executes. If data was added without sorting (the engine's `sorted` flag is false), running is refused so the time-driven simulation does not process out-of-order events.

Source

Thrown at crates/backtest/src/engine.rs:730

        // and flush callbacks that execute after the main data loop) so the
        // trader and engines actually stop.
        // Streaming batches retain commands deferred by other instruments,
        // and end() performs the unrestricted drain after all batches are loaded.
        if !streaming || self.force_stop || self.kernel.is_shutdown_requested() {
            self.end()?;
        }

        Ok(())
    }

    fn run_impl(
        &mut self,
        start: Option<UnixNanos>,
        end: Option<UnixNanos>,
        run_config_id: Option<String>,
        streaming: bool,
    ) -> anyhow::Result<()> {
        anyhow::ensure!(
            self.sorted,
            "Data has been added but not sorted, call `engine.sort_data()` or use \
             `engine.add_data(..., sort=true)` before running"
        );

        for exchange in self.venues.values() {
            let exchange = exchange.borrow();
            let book_type_has_depth = exchange.book_type() as u8 > BookType::L1_MBP as u8;
            if !book_type_has_depth {
                continue;
            }

            for instrument_id in exchange.instrument_ids() {
                let has_data = self.has_data.contains(instrument_id);
                let missing_book_data = !self.has_book_data.contains(instrument_id)
                    && !self.has_book_processed.contains(instrument_id);

                if has_data && missing_book_data {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Call engine.sort_data() before run().
  2. Pass sort=true to the last add_data/add_data_batch call.
  3. Ensure your data-loading path always ends with a sort; consider sorting once after all batches for efficiency.

Example fix

// before
engine.add_data_batch(data, false);
engine.run();
// after
engine.add_data_batch(data, false);
engine.sort_data();
engine.run();
Defensive patterns

Strategy: validation

Validate before calling

if !engine_is_sorted() {
    engine.sort_data(); // or re-add with sort=true
}
engine.run(None, None, None, false);

Try / catch

match engine.run(None, None, None, false) {
    Err(e) if e.to_string().contains("not sorted") => {
        engine.sort_data();
        engine.run(None, None, None, false)?;
    }
    r => r?,
}

Prevention

When it happens

Trigger: Calling engine.run(...) after add_data/add_data_batch with sort=false (or raw additions without engine.sort_data()); the flag stays false because sort was never invoked.

Common situations: Adding data in multiple batches with sort=false for performance and forgetting the final sort_data(); constructing the engine via a custom loader that bypasses the sorted flag; interleaving add_data and add_data_batch calls where the last one had sort=false.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/40dcd3d499de13e9. Report an issue: GitHub.