nautechsystems/nautilus_trader · error · anyhow::Error
{type_name} timestamps must be in ascending order
Error message
{type_name} timestamps must be in ascending order What it means
`check_ascending_timestamps` verifies that `ts_init` values in a batch are non-decreasing (each element <= the next via adjacent windows) before a Parquet write, since the on-disk format assumes sorted time. If any adjacent pair regresses, the write is rejected.
Source
Thrown at crates/persistence/src/backend/catalog.rs:1154
/// Validates that data timestamps are in ascending order.
///
/// # Parameters
///
/// - `data`: Slice of data records to validate.
/// - `type_name`: Name of the data type for error messages.
///
/// # Errors
///
/// Returns an error if any adjacent timestamps are out of ascending order.
pub fn check_ascending_timestamps<T: HasTsInit>(
data: &[T],
type_name: &str,
) -> anyhow::Result<()> {
if !data
.array_windows()
.all(|[a, b]| a.ts_init() <= b.ts_init())
{
anyhow::bail!("{type_name} timestamps must be in ascending order");
}
Ok(())
}
fn instrument_type_name(instrument: &InstrumentAny) -> &'static str {
match instrument {
InstrumentAny::Betting(_) => "BettingInstrument",
InstrumentAny::BinaryOption(_) => "BinaryOption",
InstrumentAny::Cfd(_) => "Cfd",
InstrumentAny::Commodity(_) => "Commodity",
InstrumentAny::CryptoFuture(_) => "CryptoFuture",
InstrumentAny::CryptoFuturesSpread(_) => "CryptoFuturesSpread",
InstrumentAny::CryptoOption(_) => "CryptoOption",
InstrumentAny::CryptoOptionSpread(_) => "CryptoOptionSpread",
InstrumentAny::CryptoPerpetual(_) => "CryptoPerpetual",
InstrumentAny::CurrencyPair(_) => "CurrencyPair",
InstrumentAny::Equity(_) => "Equity",View on GitHub (pinned to 18893faf8b)
Solutions
- Sort the batch by `ts_init` before writing.
- Partition out-of-order late arrivals into separate writes at their correct time positions.
- Add a sort step in your ingestion pipeline between collection and `write_data_enum`/write calls.
Example fix
// before catalog.write_data_enum(&data, None, None, None)?; // after let mut data = data.clone(); data.sort_by_key(|d| d.ts_init()); catalog.write_data_enum(&data, None, None, None)?;
Defensive patterns
Strategy: validation
Validate before calling
fn sorted_by_ts(data: &[impl HasTsInit]) -> bool {
data.array_windows().all(|[a, b]| a.ts_init() <= b.ts_init())
} Try / catch
if let Err(e) = catalog.write_data_enum(&data, None, None, None) {
if e.to_string().ends_with("timestamps must be in ascending order") {
let mut sorted = data.clone();
sorted.sort_by_key(|d| d.ts_init());
return catalog.write_data_enum(&sorted, None, None, None).map_err(Into::into);
}
return Err(e.into());
} Prevention
- Always sort by ts_init immediately before catalog writes
- Merge multi-source streams with a merge-sort, not concatenation
- Handle late-arriving events in a separate buffered write
- Check clock skew at the collector level
When it happens
Trigger: Writing data (instruments, quotes, trades, custom data) whose timestamps are out of order — e.g. merging streams from multiple sources without re-sorting, appending late-arriving events, or clock skew between sources.
Common situations: Combining feeds from two exchanges into one batch; replaying buffered data out of order; constructing test fixtures with unsorted timestamps; multi-threaded collectors appending without sorting.
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
- Cannot write {type_name} data with mixed identities: element
- Unsupported Data::Defi variant for catalog writes
- Unsupported Data variant for catalog writes
- Writing file {filename} with interval ({start_ts}, {end_ts})
- Intervals are not disjoint after extending a file
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/78d48cd1ace3df6d.
Report an issue: GitHub.