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

  1. Sort the batch by `ts_init` before writing.
  2. Partition out-of-order late arrivals into separate writes at their correct time positions.
  3. 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

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


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