{"record":{"id":"8ec83b08310fdad5","repo":"nautechsystems/nautilus_trader","slug":"invalid-funding-interval-cannot-be-negative","errorCode":null,"errorMessage":"Invalid funding_interval, cannot be negative","messagePattern":"Invalid funding_interval, cannot be negative","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/okx/src/common/parse.rs","lineNumber":568,"sourceCode":"/// Returns an error if the `funding_rate` field fails\n/// to parse into a Decimal value or `next_funding_time` fails to parse into a positive, in bounds interval.\npub fn parse_funding_rate_msg(\n    msg: &OKXFundingRateMsg,\n    instrument_id: InstrumentId,\n    ts_init: UnixNanos,\n) -> anyhow::Result<FundingRateUpdate> {\n    let funding_rate = msg\n        .funding_rate\n        .as_str()\n        .parse::<Decimal>()\n        .map_err(|e| anyhow::anyhow!(\"Invalid funding_rate value: {e}\"))?;\n\n    let funding_time = parse_millisecond_timestamp(msg.funding_time);\n    let next_funding_time = parse_millisecond_timestamp(msg.next_funding_time);\n    let funding_interval_nanos =\n        next_funding_time\n            .duration_since(&funding_time)\n            .ok_or(anyhow::anyhow!(\n                \"Invalid funding_interval, cannot be negative\"\n            ))?;\n    let funding_interval = u16::try_from(funding_interval_nanos.as_mins())\n        .context(\"funding_interval out of bounds\")?;\n    let ts_event = parse_millisecond_timestamp(msg.ts);\n\n    Ok(FundingRateUpdate::new(\n        instrument_id,\n        funding_rate,\n        Some(funding_interval),\n        Some(funding_time),\n        ts_event,\n        ts_init,\n    ))\n}\n\n/// Parses a [`OKXFundingRateHistory`] into a [`FundingRateUpdate`].\n///","sourceCodeStart":550,"sourceCodeEnd":586,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/okx/src/common/parse.rs#L550-L586","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Log the raw msg.funding_time and msg.next_funding_time values to confirm the negative delta before treating it as an adapter bug","Skip or ignore the offending message and wait for the next funding rate update instead of failing the whole stream","Validate that next_funding_time > funding_time (and both non-zero) in your ingestion layer before calling parse_funding_rate_msg","If OKX changed the field semantics, pin/upgrade the nautilus OKX adapter version matching the current API docs"],"exampleFix":"// before\nlet next = parse_millisecond_timestamp(msg.next_funding_time);\nlet update = parse_funding_rate_msg(&msg, instrument_id, ts_init)?;\n// after\nlet funding_time = parse_millisecond_timestamp(msg.funding_time);\nlet next = parse_millisecond_timestamp(msg.next_funding_time);\nif next == 0 || next <= funding_time {\n    log::warn!(\"Skipping funding rate msg with invalid times: funding_time={funding_time}, next_funding_time={next}\");\n    return Ok(None);\n}\nlet update = parse_funding_rate_msg(&msg, instrument_id, ts_init)?;","handlingStrategy":"validation","validationCode":"let funding_time = parse_millisecond_timestamp(msg.funding_time);\nlet next = parse_millisecond_timestamp(msg.next_funding_time);\nif next == 0 || funding_time == 0 || next <= funding_time {\n    // skip or normalize this message before calling parse_funding_rate_msg\n}","typeGuard":"fn has_valid_funding_times(msg: &OKXFundingRateMsg) -> bool {\n    let t = parse_millisecond_timestamp(msg.funding_time);\n    let nt = parse_millisecond_timestamp(msg.next_funding_time);\n    t > 0 && nt > t\n}","tryCatchPattern":"match parse_funding_rate_msg(&msg, instrument_id, ts_init) {\n    Ok(update) => handle(update),\n    Err(e) if e.to_string().contains(\"cannot be negative\") => log::warn!(\"skipping bad funding msg: {e}\"),\n    Err(e) => return Err(e),\n}","preventionTips":["Validate timestamp pairs before parsing","Skip rather than fail on individual bad websocket messages","Watch OKX release notes for funding-time field changes","Pin and regularly update the OKX adapter version"],"tags":["okx","rust","parsing","timestamp","funding-rate"],"backgroundTag":"invalid-argument-value","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}