nautechsystems/nautilus_trader · error · anyhow::Error

AX timestamp_ns must be non-negative, was {nanos}

Error message

AX timestamp_ns must be non-negative, was {nanos}

What it means

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.

Source

Thrown at crates/adapters/architect_ax/src/common/parse.rs:75

/// Returns an error if `seconds` is negative (malformed data from AX).
pub fn ax_timestamp_stn_to_unix_nanos(seconds: i64, nanos: i64) -> anyhow::Result<UnixNanos> {
    anyhow::ensure!(
        seconds >= 0,
        "AX timestamp must be non-negative, was {seconds}"
    );
    let nanos_part = nanos.max(0) as u64;
    Ok(UnixNanos::from(
        seconds as u64 * NANOSECONDS_IN_SECOND + nanos_part,
    ))
}

/// Converts an AX nanosecond timestamp to [`UnixNanos`].
///
/// # Errors
///
/// Returns an error if `nanos` is negative (malformed data from AX).
pub fn ax_timestamp_ns_to_unix_nanos(nanos: i64) -> anyhow::Result<UnixNanos> {
    anyhow::ensure!(
        nanos >= 0,
        "AX timestamp_ns must be non-negative, was {nanos}"
    );
    Ok(UnixNanos::from(nanos as u64))
}

/// Domain separator for the market-data trade identity digest.
///
/// Changing this invalidates every AX `TradeId` already published or persisted, so bump the
/// version suffix only as a deliberate decision.
const TRADE_ID_DOMAIN: &[u8] = b"nautilus-architect-ax/trade-id/v1";

/// Creates a [`TradeId`] for an AX market-data trade.
///
/// AX publishes no trade identifier for market data: `GET /trades` and the market-data WebSocket
/// both carry only `ts`, `tn`, `s`, `p`, `q`, and `d`, and `tn` is the nanosecond component of the
/// timestamp rather than a sequence number. The composed timestamp alone is not unique either,
/// because one aggressor sweeping several levels reports multiple prints at an identical `ts` and

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Report the raw message to maintainers with the decoded field value
  2. Replace negative sentinels in recorded/test data with valid ns-since-epoch values
  3. Upgrade the architect_ax adapter to the latest release matching the venue API

Example fix

// before (fixture)
let ts = ax_timestamp_ns_to_unix_nanos(-1)?; // AX timestamp_ns must be non-negative, was -1

// after
let ts = ax_timestamp_ns_to_unix_nanos(1_700_000_000_000_000_000)?;
Defensive patterns

Strategy: try-catch

Type guard

fn is_valid_ax_nanos(nanos: i64) -> bool {
    nanos >= 0
}

Try / catch

match ax_timestamp_ns_to_unix_nanos(ts_ns) {
    Ok(nanos) => nanos,
    Err(e) => {
        tracing::error!("dropping event with negative ns timestamp {ts_ns}: {e}");
        return Ok(());
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16). Data as JSON: /api/errors/b7fcd4ee30bf5880. Report an issue: GitHub.