{"record":{"id":"b7fcd4ee30bf5880","repo":"nautechsystems/nautilus_trader","slug":"ax-timestamp-ns-must-be-non-negative-was-nanos","errorCode":null,"errorMessage":"AX timestamp_ns must be non-negative, was {nanos}","messagePattern":"AX timestamp_ns must be non-negative, was (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/architect_ax/src/common/parse.rs","lineNumber":75,"sourceCode":"/// Returns an error if `seconds` is negative (malformed data from AX).\npub fn ax_timestamp_stn_to_unix_nanos(seconds: i64, nanos: i64) -> anyhow::Result<UnixNanos> {\n    anyhow::ensure!(\n        seconds >= 0,\n        \"AX timestamp must be non-negative, was {seconds}\"\n    );\n    let nanos_part = nanos.max(0) as u64;\n    Ok(UnixNanos::from(\n        seconds as u64 * NANOSECONDS_IN_SECOND + nanos_part,\n    ))\n}\n\n/// Converts an AX nanosecond timestamp to [`UnixNanos`].\n///\n/// # Errors\n///\n/// Returns an error if `nanos` is negative (malformed data from AX).\npub fn ax_timestamp_ns_to_unix_nanos(nanos: i64) -> anyhow::Result<UnixNanos> {\n    anyhow::ensure!(\n        nanos >= 0,\n        \"AX timestamp_ns must be non-negative, was {nanos}\"\n    );\n    Ok(UnixNanos::from(nanos as u64))\n}\n\n/// Domain separator for the market-data trade identity digest.\n///\n/// Changing this invalidates every AX `TradeId` already published or persisted, so bump the\n/// version suffix only as a deliberate decision.\nconst TRADE_ID_DOMAIN: &[u8] = b\"nautilus-architect-ax/trade-id/v1\";\n\n/// Creates a [`TradeId`] for an AX market-data trade.\n///\n/// AX publishes no trade identifier for market data: `GET /trades` and the market-data WebSocket\n/// both carry only `ts`, `tn`, `s`, `p`, `q`, and `d`, and `tn` is the nanosecond component of the\n/// timestamp rather than a sequence number. The composed timestamp alone is not unique either,\n/// because one aggressor sweeping several levels reports multiple prints at an identical `ts` and","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/architect_ax/src/common/parse.rs#L57-L93","documentation":"ax_timestamp_ns_to_unix_nanos converts an AX nanosecond timestamp (i64) to the unsigned UnixNanos type. Negative nanosecond values indicate malformed AX data, so the function errors before casting to u64, which would otherwise wrap around.","triggerScenarios":"Calling ax_timestamp_ns_to_unix_nanos with a negative i64, typically from parse_funding_rate when the AX funding rate payload carries a negative timestamp.","commonSituations":"AX sending sentinel/corrupt timestamps in funding rate updates; schema or field-shift issues in the upstream payload; tests exercising the negative-input path.","solutions":["Inspect the AX funding rate message to find the source of the negative timestamp","Skip the malformed update and log the raw payload","Filter known sentinel values (e.g. -1) before conversion"],"exampleFix":"// before\nlet ts = ax_timestamp_ns_to_unix_nanos(fr.ts_ns)?;\n// after\nif fr.ts_ns < 0 { log::warn!(\"bad funding ts\"); return Ok(None); }\nlet ts = ax_timestamp_ns_to_unix_nanos(fr.ts_ns)?;","handlingStrategy":"validation","validationCode":"// Rust\nif nanos < 0 { /* skip update */ }","typeGuard":null,"tryCatchPattern":"// Rust\nlet ts = ax_timestamp_ns_to_unix_nanos(n)\n    .map_err(|e| { log::warn!(\"bad AX ts_ns: {e}\"); fallback_ts() })?;","preventionTips":["Sanitize funding rate payloads before conversion","Filter known AX sentinel negative timestamps","Alert on repeated negative timestamps — it usually indicates an upstream schema change"],"tags":["timestamp","validation","adapter"],"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-23T08:17:48.524Z"}