{"record":{"id":"f410df1ef6513a7a","repo":"nautechsystems/nautilus_trader","slug":"cumulative-spread-fill-went-backwards-acc-fill-sz","errorCode":null,"errorMessage":"Cumulative spread fill went backwards: acc_fill_sz='{}' < previous_filled_qty={} (possible stale data after reconnect)","messagePattern":"Cumulative spread fill went backwards: acc_fill_sz='(.+?)' < previous_filled_qty=(.+?) \\(possible stale data after reconnect\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/okx/src/websocket/parse.rs","lineNumber":2019,"sourceCode":"    previous_filled_qty: Option<Quantity>,\n    _ts_init: UnixNanos,\n) -> anyhow::Result<Option<FillReport>> {\n    let size_precision = instrument.size_precision();\n    if !msg.fill_sz.is_empty() && msg.fill_sz != \"0\" {\n        parse_quantity(&msg.fill_sz, size_precision).map_err(|e| {\n            anyhow::anyhow!(\"Failed to parse spread fill_sz='{}': {e}\", msg.fill_sz)\n        })?;\n    } else if !msg.acc_fill_sz.is_empty() && msg.acc_fill_sz != \"0\" {\n        let current_filled = parse_quantity(&msg.acc_fill_sz, size_precision).map_err(|e| {\n            anyhow::anyhow!(\n                \"Failed to parse spread acc_fill_sz='{}': {e}\",\n                msg.acc_fill_sz\n            )\n        })?;\n\n        if let Some(prev_qty) = previous_filled_qty {\n            if current_filled < prev_qty {\n                anyhow::bail!(\n                    \"Cumulative spread fill went backwards: acc_fill_sz='{}' < previous_filled_qty={} \\\n                     (possible stale data after reconnect)\",\n                    msg.acc_fill_sz,\n                    prev_qty\n                );\n            }\n\n            if (current_filled - prev_qty).is_zero() {\n                log::debug!(\n                    \"Skipping duplicate spread fill: acc_fill_sz='{}' unchanged from previous={}\",\n                    msg.acc_fill_sz,\n                    prev_qty\n                );\n                return Ok(None);\n            }\n        }\n    } else {\n        anyhow::bail!(","sourceCodeStart":2001,"sourceCodeEnd":2037,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/okx/src/websocket/parse.rs#L2001-L2037","documentation":"OKX spread order updates report cumulative filled size (acc_fill_sz). The parser keeps the previously known filled quantity and rejects updates where the new cumulative value is less than the previous one, since that indicates stale/duplicate data (often after a reconnect) rather than a real fill regression.","triggerScenarios":"A sprd-orders update arrives with acc_fill_sz lower than the previously tracked filled quantity for the same spread order, typically after a WebSocket reconnect replaying older messages.","commonSituations":"WS reconnect delivering a delayed/older update out of order; subscribing to the same channel twice; replayed snapshots mixed with live updates.","solutions":["Verify only one spread-orders subscription is active and reconnect logic doesn't replay old messages","Discard/regenerate order state from a fresh REST snapshot after reconnect instead of trusting interleaved WS updates","If genuinely out-of-order data is expected, catch this error and drop the stale update rather than failing the stream"],"exampleFix":"// before\nlet report = parse_spread_order_fill_report(msg, prev_qty)?; // bails on regression\n// after\nmatch parse_spread_order_fill_report(msg, prev_qty) {\n    Ok(r) => handle(r),\n    Err(e) if e.to_string().contains(\"went backwards\") => tracing::warn!(\"stale spread update dropped\"),\n    Err(e) => return Err(e),\n}","handlingStrategy":"try-catch","validationCode":"if let Some(prev) = previous_filled_qty {\n    if let Some(acc) = &msg.acc_fill_sz {\n        if acc.parse::<f64>().map_or(true, |v| (v as u64) < prev.raw()) {\n            // stale/out-of-order update; drop before parsing\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"match parse_spread_order_fill_report(&msg, prev_qty) {\n    Ok(Some(r)) => handle(r),\n    Ok(None) => {}\n    Err(e) if e.to_string().contains(\"went backwards\") => {\n        tracing::warn!(\"dropping stale spread update: {e}\");\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["After reconnect, rebuild order state from a REST snapshot before accepting WS updates","Ensure a single subscription per spread order stream","Stamp updates with timestamps and ignore older-than-last messages"],"tags":["okx","websocket","reconnect","stale-data"],"backgroundTag":"internal-invariant-violation","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"}