nautechsystems/nautilus_trader · error

AX timestamp_ns must be non-negative, was

Error message

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

What it means

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.

Solutions

  1. Inspect the AX funding rate message to find the source of the negative timestamp
  2. Skip the malformed update and log the raw payload
  3. Filter known sentinel values (e.g. -1) before conversion

Example fix

// before
let ts = ax_timestamp_ns_to_unix_nanos(fr.ts_ns)?;
// after
if fr.ts_ns < 0 { log::warn!("bad funding ts"); return Ok(None); }
let ts = ax_timestamp_ns_to_unix_nanos(fr.ts_ns)?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust
if nanos < 0 { /* skip update */ }

Try / catch

// Rust
let ts = ax_timestamp_ns_to_unix_nanos(n)
    .map_err(|e| { log::warn!("bad AX ts_ns: {e}"); fallback_ts() })?;

Prevention

When it happens

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

Common situations: AX sending sentinel/corrupt timestamps in funding rate updates; schema or field-shift issues in the upstream payload; tests exercising the negative-input path.

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/b7fcd4ee30bf5880. Report an issue: GitHub.

Appendix: 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 18893faf8b)