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
- Swap the start/end arguments so start <= end.
- Verify your configured window actually overlaps the timestamps of the data added to the engine.
- 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
- Validate the configured run window (start <= end) in your config loader.
- Confirm the window overlaps the data's first/last timestamps.
- Beware unit confusion (ms vs ns) when building UnixNanos bounds.
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
- bar_type.aggregation_source must be External, was {:?}
- Data has been added but not sorted, call `engine.sort_data()
- Cannot handle command: {command:?}
- Latency model should be initialized
- Execution client should be initialized
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/7a95bf9302ccac16.
Report an issue: GitHub.