nautechsystems/nautilus_trader · error

negative candle timestamp: {}

Error message

negative candle timestamp: {}

What it means

parse_ws_bar casts the candle's timestamp (milliseconds, as i64) to u64 before converting to nanoseconds. A negative timestamp cannot be cast losslessly, so the adapter raises 'negative candle timestamp' rather than producing a corrupted event time.

Source

Thrown at crates/adapters/lighter/src/websocket/parse.rs:411

    resolution: LighterCandleResolution,
    ts_init: UnixNanos,
) -> anyhow::Result<Bar> {
    let price_precision = instrument.price_precision();
    let size_precision = instrument.size_precision();

    let open = Price::from_decimal_dp(candle.o, price_precision)
        .map_err(|e| anyhow::anyhow!("invalid candle open: {e}"))?;
    let high = Price::from_decimal_dp(candle.h, price_precision)
        .map_err(|e| anyhow::anyhow!("invalid candle high: {e}"))?;
    let low = Price::from_decimal_dp(candle.l, price_precision)
        .map_err(|e| anyhow::anyhow!("invalid candle low: {e}"))?;
    let close = Price::from_decimal_dp(candle.c, price_precision)
        .map_err(|e| anyhow::anyhow!("invalid candle close: {e}"))?;
    let volume = Quantity::from_decimal_dp(candle.v, size_precision)
        .map_err(|e| anyhow::anyhow!("invalid candle volume: {e}"))?;

    let t_ms = u64::try_from(candle.t)
        .map_err(|_| anyhow::anyhow!("negative candle timestamp: {}", candle.t))?;
    let ts_event = parse_millis_to_nanos(t_ms)?;

    let bar_type = BarType::new(
        instrument.id(),
        resolution.to_bar_spec(),
        AggregationSource::External,
    );

    Bar::new_checked(bar_type, open, high, low, close, volume, ts_event, ts_init)
        .map_err(|e| anyhow::anyhow!("invalid candle bar: {e}"))
}

fn parse_book_level_delta(
    level: &LighterPriceLevel,
    instrument: &InstrumentAny,
    side: OrderSide,
    sequence: u64,
    ts_event: UnixNanos,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Log the raw candle payload and check the venue schema for the timestamp field
  2. Reject or skip messages with negative timestamps upstream before parsing
  3. Update the parser if the venue changed the timestamp format/epoch
  4. Validate deserialization maps the correct JSON field to candle.t

Example fix

// before
let t_ms = u64::try_from(candle.t)?;
// after
if candle.t < 0 { log::warn!("skipping candle with negative timestamp {}", candle.t); return Ok(None); }
let t_ms = u64::try_from(candle.t)?;
Defensive patterns

Strategy: validation

Validate before calling

if candle.t < 0 {
    log::warn!("negative candle timestamp {}", candle.t);
    return; // skip bad candle
}

Type guard

fn has_valid_timestamp(candle: &WsCandle) -> bool { candle.t >= 0 }

Try / catch

match parse_ws_bar(candle, instrument, ...) {
    Err(e) if e.to_string().contains("negative candle timestamp") => {
        log::warn!("skipping malformed candle: {e}");
    }
    other => other?,
}

Prevention

When it happens

Trigger: A candle message with candle.t < 0 — malformed venue payload, integer underflow upstream, or a misparsed field causing the signed timestamp to go negative.

Common situations: Venue protocol/schema change; clock or epoch issues producing negative times; deserialization bug mapping the wrong JSON field to t.

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