nautechsystems/nautilus_trader · error

start was > end

Error message

start was > end

What it means

run_impl derives the simulation window: start_ns defaults to the first event timestamp and end_ns to the last data timestamp (or start). If the resolved start is greater than end — typically because explicit start/end arguments are inverted or fall outside the data range — the engine refuses to run with this bare message.

Source

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

                let missing_book_data = !self.has_book_data.contains(instrument_id)
                    && !self.has_book_processed.contains(instrument_id);

                if has_data && missing_book_data {
                    anyhow::bail!(
                        "No order book data found for instrument '{instrument_id}' when `book_type` \
                         is '{:?}'. Set the venue `book_type` to 'L1_MBP' (for top-of-book data \
                         like quotes, trades, and bars) or provide order book data for this \
                         instrument.",
                        exchange.book_type()
                    );
                }
            }
        }

        // Determine time boundaries
        let start_ns = start.unwrap_or_else(|| self.ts_first.unwrap_or_default());
        let end_ns = end.unwrap_or_else(|| self.ts_last_data.unwrap_or(start_ns));
        anyhow::ensure!(start_ns <= end_ns, "start was > end");
        self.end_ns = end_ns;
        self.last_ns = start_ns;
        self.last_module_ns = None;

        // Set all component clocks to start
        let clocks = self.collect_all_clocks();
        Self::set_all_clocks_time(&clocks, start_ns);

        // First-iteration initialization
        if self.iteration == 0 {
            self.set_instrument_expiration_timers()?;

            self.run_config_id = run_config_id;
            self.run_id = Some(UUID4::new());
            self.run_started = Some(UnixNanos::from(nanos_since_unix_epoch()));
            self.backtest_start = Some(start_ns);

            for exchange in self.venues.values() {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Swap the start/end arguments so start <= end.
  2. Verify your configured window actually overlaps the timestamps of the data added to the engine.
  3. Check unit conversion (seconds vs milliseconds vs nanoseconds) when computing start/end UnixNanos values.

Example fix

// before
engine.run(Some(UnixNanos::from(end_ms)), Some(UnixNanos::from(start_ms)), None, false);
// after
engine.run(Some(UnixNanos::from(start_ms)), Some(UnixNanos::from(end_ms)), None, false);
Defensive patterns

Strategy: validation

Validate before calling

if let (Some(s), Some(e)) = (start, end) {
    assert!(s <= e, "run window invalid: start {s} > end {e}");
}

Prevention

When it happens

Trigger: engine.run(start=Some(later_ns), end=Some(earlier_ns)) with an inverted range, or start beyond ts_last_data while ts_first/ts_last defaults resolve end before start.

Common situations: Swapped start/end parameters (ms vs ns confusion aside, simply reversed); a run window entirely after the loaded data's last timestamp; computing bounds from config with the wrong variable order.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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