nautechsystems/nautilus_trader · error · anyhow::Error

Timestamp out of range for trade {}

Error message

Timestamp out of range for trade {}

What it means

parse_trade_tick converts a trade's created_at timestamp into UnixNanos (u64). The error fires when u64::try_from fails because the timestamp is negative (before Unix epoch) or out of u64 nanosecond range, so the trade tick cannot be constructed.

Source

Thrown at crates/adapters/dydx/src/http/parse.rs:84

    trade: &Trade,
    instrument_id: InstrumentId,
    price_precision: u8,
    size_precision: u8,
    ts_init: UnixNanos,
) -> anyhow::Result<TradeTick> {
    let aggressor_side = match trade.side {
        OrderSide::Buy => AggressorSide::Buy,
        OrderSide::Sell => AggressorSide::Sell,
    };

    let price = Price::from_decimal_dp(trade.price, price_precision)
        .context(format!("failed to parse price for trade {}", trade.id))?;

    let size = Quantity::from_decimal_dp(trade.size, size_precision)
        .context(format!("failed to parse size for trade {}", trade.id))?;

    let ts_event_nanos = u64::try_from(trade.created_at.as_nanosecond())
        .map_err(|_| anyhow::anyhow!("Timestamp out of range for trade {}", trade.id))?;
    let ts_event = UnixNanos::from(ts_event_nanos);

    Ok(TradeTick::new(
        instrument_id,
        price,
        size,
        aggressor_side,
        TradeId::new(&trade.id),
        ts_event,
        ts_init,
    ))
}

/// Parses a dYdX [`Candle`] into a Nautilus [`Bar`].
///
/// When `timestamp_on_close` is true, `ts_event` is set to bar close time
/// (started_at + interval). When false, uses the venue-native open time.
///

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Log the offending trade payload (trade.id and raw created_at) to confirm the actual value
  2. Fix the DateTime deserialization of created_at if the format/epoch assumption is wrong
  3. Skip trades with out-of-range timestamps instead of failing the whole batch
  4. Check for dYdX API version changes affecting the timestamp field

Example fix

// before
let ts_event_nanos = u64::try_from(trade.created_at.as_nanosecond())
    .map_err(|_| anyhow::anyhow!("Timestamp out of range for trade {}", trade.id))?;
// after
let ts_event_nanos = u64::try_from(trade.created_at.as_nanosecond())
    .map_err(|_| anyhow::anyhow!(
        "Timestamp out of range for trade {}: raw created_at={:?}",
        trade.id, trade.created_at
    ))?;
Defensive patterns

Strategy: try-catch

Validate before calling

if u64::try_from(trade.created_at.as_nanosecond()).is_err() {
    log::warn!("trade {} has out-of-range created_at, skipping", trade.id);
    return Ok(None);
}

Type guard

fn trade_timestamp_in_range(trade: &DydxTrade) -> bool {
    u64::try_from(trade.created_at.as_nanosecond()).is_ok()
}

Try / catch

match parse_trade_tick(trade, instrument, price_precision, size_precision) {
    Ok(tick) => handle(tick),
    Err(e) if e.to_string().contains("Timestamp out of range") => {
        log::warn!("skipping trade with bad timestamp: {e}");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Parsing a dYdX trade whose created_at is negative or exceeds u64 nanoseconds, via parse_trade_tick (invoked from trade-tick requests).

Common situations: API returning a malformed or zeroed created_at; wrong datetime parsing producing pre-epoch values; upstream schema change in the trade feed; corrupted test fixture data (this symbol is exercised by test_parse_trade_tick).

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/b2bd2e81d3fa5eef. Report an issue: GitHub.