{"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":"anyhow::Error","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/a4b06ed870971b5671d12754ea138a3ab99b1dec/crates/adapters/architect_ax/src/common/parse.rs#L57-L93","documentation":"The ns-flavour guard: ax_timestamp_ns_to_unix_nanos takes a single nanosecond timestamp and rejects negative values as malformed venue data (UnixNanos is a u64-backed type, so a negative i64 would wrap catastrophically if cast blindly). The message names the field style ('timestamp_ns') to distinguish it from the seconds-based guards.","triggerScenarios":"An inbound AX message whose nanosecond timestamp field decodes negative — corrupt frame, protocol drift after an Architect API change, or fixture data using -1 sentinels.","commonSituations":"Adapter-level data integrity issue rather than a user error; encountered when testing against recorded venue data with placeholder values, or during an upstream protocol change.","solutions":["Report the raw message to maintainers with the decoded field value","Replace negative sentinels in recorded/test data with valid ns-since-epoch values","Upgrade the architect_ax adapter to the latest release matching the venue API"],"exampleFix":"// before (fixture)\nlet ts = ax_timestamp_ns_to_unix_nanos(-1)?; // AX timestamp_ns must be non-negative, was -1\n\n// after\nlet ts = ax_timestamp_ns_to_unix_nanos(1_700_000_000_000_000_000)?;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"fn is_valid_ax_nanos(nanos: i64) -> bool {\n    nanos >= 0\n}","tryCatchPattern":"match ax_timestamp_ns_to_unix_nanos(ts_ns) {\n    Ok(nanos) => nanos,\n    Err(e) => {\n        tracing::error!(\"dropping event with negative ns timestamp {ts_ns}: {e}\");\n        return Ok(());\n    }\n}","preventionTips":["Never use -1 (or other negatives) as timestamp sentinels in AX test data","Alert on this error in production: it signals upstream data corruption, not user error"],"tags":["rust","architect","adapter","timestamp","malformed-data","venue-feed"],"backgroundTag":"negative-timestamp-rejected","analyzedSha":"a4b06ed870971b5671d12754ea138a3ab99b1dec","analyzedAt":"2026-08-16T22:54:50.089Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}