{"record":{"id":"78d48cd1ace3df6d","repo":"nautechsystems/nautilus_trader","slug":"type-name-timestamps-must-be-in-ascending-order","errorCode":null,"errorMessage":"{type_name} timestamps must be in ascending order","messagePattern":"(.+?) timestamps must be in ascending order","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/persistence/src/backend/catalog.rs","lineNumber":1154,"sourceCode":"    /// Validates that data timestamps are in ascending order.\n    ///\n    /// # Parameters\n    ///\n    /// - `data`: Slice of data records to validate.\n    /// - `type_name`: Name of the data type for error messages.\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if any adjacent timestamps are out of ascending order.\n    pub fn check_ascending_timestamps<T: HasTsInit>(\n        data: &[T],\n        type_name: &str,\n    ) -> anyhow::Result<()> {\n        if !data\n            .array_windows()\n            .all(|[a, b]| a.ts_init() <= b.ts_init())\n        {\n            anyhow::bail!(\"{type_name} timestamps must be in ascending order\");\n        }\n\n        Ok(())\n    }\n\n    fn instrument_type_name(instrument: &InstrumentAny) -> &'static str {\n        match instrument {\n            InstrumentAny::Betting(_) => \"BettingInstrument\",\n            InstrumentAny::BinaryOption(_) => \"BinaryOption\",\n            InstrumentAny::Cfd(_) => \"Cfd\",\n            InstrumentAny::Commodity(_) => \"Commodity\",\n            InstrumentAny::CryptoFuture(_) => \"CryptoFuture\",\n            InstrumentAny::CryptoFuturesSpread(_) => \"CryptoFuturesSpread\",\n            InstrumentAny::CryptoOption(_) => \"CryptoOption\",\n            InstrumentAny::CryptoOptionSpread(_) => \"CryptoOptionSpread\",\n            InstrumentAny::CryptoPerpetual(_) => \"CryptoPerpetual\",\n            InstrumentAny::CurrencyPair(_) => \"CurrencyPair\",\n            InstrumentAny::Equity(_) => \"Equity\",","sourceCodeStart":1136,"sourceCodeEnd":1172,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/persistence/src/backend/catalog.rs#L1136-L1172","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ncatalog.write_data_enum(&data, None, None, None)?;\n// after\nlet mut data = data.clone();\ndata.sort_by_key(|d| d.ts_init());\ncatalog.write_data_enum(&data, None, None, None)?;","handlingStrategy":"validation","validationCode":"fn sorted_by_ts(data: &[impl HasTsInit]) -> bool {\n    data.array_windows().all(|[a, b]| a.ts_init() <= b.ts_init())\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = catalog.write_data_enum(&data, None, None, None) {\n    if e.to_string().ends_with(\"timestamps must be in ascending order\") {\n        let mut sorted = data.clone();\n        sorted.sort_by_key(|d| d.ts_init());\n        return catalog.write_data_enum(&sorted, None, None, None).map_err(Into::into);\n    }\n    return Err(e.into());\n}","preventionTips":["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"],"tags":["rust","persistence","parquet","timestamps","validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}