nautechsystems/nautilus_trader · error

Invalid funding_interval, cannot be negative

Error message

Invalid funding_interval, cannot be negative

What it means

parse_funding_rate_msg computes the funding interval as next_funding_time - funding_time from the OKX funding-rate websocket message. `duration_since` returns None when the next funding time is earlier than the current funding time, so the adapter rejects the message rather than emitting a FundingRateUpdate with a negative interval. This guards the downstream u16 minutes field and any interval-based scheduling logic.

Source

Thrown at crates/adapters/okx/src/common/parse.rs:568

/// Returns an error if the `funding_rate` field fails
/// to parse into a Decimal value or `next_funding_time` fails to parse into a positive, in bounds interval.
pub fn parse_funding_rate_msg(
    msg: &OKXFundingRateMsg,
    instrument_id: InstrumentId,
    ts_init: UnixNanos,
) -> anyhow::Result<FundingRateUpdate> {
    let funding_rate = msg
        .funding_rate
        .as_str()
        .parse::<Decimal>()
        .map_err(|e| anyhow::anyhow!("Invalid funding_rate value: {e}"))?;

    let funding_time = parse_millisecond_timestamp(msg.funding_time);
    let next_funding_time = parse_millisecond_timestamp(msg.next_funding_time);
    let funding_interval_nanos =
        next_funding_time
            .duration_since(&funding_time)
            .ok_or(anyhow::anyhow!(
                "Invalid funding_interval, cannot be negative"
            ))?;
    let funding_interval = u16::try_from(funding_interval_nanos.as_mins())
        .context("funding_interval out of bounds")?;
    let ts_event = parse_millisecond_timestamp(msg.ts);

    Ok(FundingRateUpdate::new(
        instrument_id,
        funding_rate,
        Some(funding_interval),
        Some(funding_time),
        ts_event,
        ts_init,
    ))
}

/// Parses a [`OKXFundingRateHistory`] into a [`FundingRateUpdate`].
///

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Log the raw msg.funding_time and msg.next_funding_time values to confirm the negative delta before treating it as an adapter bug
  2. Skip or ignore the offending message and wait for the next funding rate update instead of failing the whole stream
  3. Validate that next_funding_time > funding_time (and both non-zero) in your ingestion layer before calling parse_funding_rate_msg
  4. If OKX changed the field semantics, pin/upgrade the nautilus OKX adapter version matching the current API docs

Example fix

// before
let next = parse_millisecond_timestamp(msg.next_funding_time);
let update = parse_funding_rate_msg(&msg, instrument_id, ts_init)?;
// after
let funding_time = parse_millisecond_timestamp(msg.funding_time);
let next = parse_millisecond_timestamp(msg.next_funding_time);
if next == 0 || next <= funding_time {
    log::warn!("Skipping funding rate msg with invalid times: funding_time={funding_time}, next_funding_time={next}");
    return Ok(None);
}
let update = parse_funding_rate_msg(&msg, instrument_id, ts_init)?;
Defensive patterns

Strategy: validation

Validate before calling

let funding_time = parse_millisecond_timestamp(msg.funding_time);
let next = parse_millisecond_timestamp(msg.next_funding_time);
if next == 0 || funding_time == 0 || next <= funding_time {
    // skip or normalize this message before calling parse_funding_rate_msg
}

Type guard

fn has_valid_funding_times(msg: &OKXFundingRateMsg) -> bool {
    let t = parse_millisecond_timestamp(msg.funding_time);
    let nt = parse_millisecond_timestamp(msg.next_funding_time);
    t > 0 && nt > t
}

Try / catch

match parse_funding_rate_msg(&msg, instrument_id, ts_init) {
    Ok(update) => handle(update),
    Err(e) if e.to_string().contains("cannot be negative") => log::warn!("skipping bad funding msg: {e}"),
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: An OKX funding rate update arrives where next_funding_time < funding_time (after parse_millisecond_timestamp coercion), typically because the exchange sent a zero/stale/placeholder timestamp pair or timestamps crossed a partial-fill boundary (e.g. next_funding_time=0 coerced to a default earlier than funding_time).

Common situations: Exchange clock or message ordering anomalies on the OKX websocket; snapshots where next_funding_time is empty/zero and parse_millisecond_timestamp falls back to a value before funding_time; replaying out-of-order historical funding messages; temporary OKX API schema changes.

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