{"record":{"id":"dc6863e69d27c55b","repo":"nautechsystems/nautilus_trader","slug":"cumulative-fill-went-backwards-acc-fill-sz-acc","errorCode":null,"errorMessage":"Cumulative fill went backwards: acc_fill_sz='{acc_fill_sz}' < previous_filled_qty={prev_qty} (possible stale data after reconnect)","messagePattern":"Cumulative 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":2118,"sourceCode":"        )\n    })?;\n\n    // OKX provides fillSz (incremental fill) or accFillSz (cumulative total)\n    // If fillSz is provided, use it directly as the incremental fill quantity\n    let last_qty = if !msg.fill_sz.is_empty() && msg.fill_sz != \"0\" {\n        parse_quantity(&msg.fill_sz, size_precision)\n            .map_err(|e| anyhow::anyhow!(\"Failed to parse fill_sz='{}': {e}\", msg.fill_sz,))?\n    } else if let Some(ref acc_fill_sz) = msg.acc_fill_sz {\n        // If fillSz is missing but accFillSz is available, calculate incremental fill\n        if !acc_fill_sz.is_empty() && acc_fill_sz != \"0\" {\n            let current_filled = parse_quantity(acc_fill_sz, size_precision).map_err(|e| {\n                anyhow::anyhow!(\"Failed to parse acc_fill_sz='{acc_fill_sz}': {e}\",)\n            })?;\n\n            // Calculate incremental fill as: current_total - previous_total\n            if let Some(prev_qty) = previous_filled_qty {\n                if current_filled < prev_qty {\n                    anyhow::bail!(\n                        \"Cumulative fill went backwards: acc_fill_sz='{acc_fill_sz}' < previous_filled_qty={prev_qty} \\\n                         (possible stale data after reconnect)\"\n                    );\n                }\n                let incremental = current_filled - prev_qty;\n                if incremental.is_zero() {\n                    log::debug!(\n                        \"Skipping duplicate fill: acc_fill_sz='{acc_fill_sz}' unchanged from previous={prev_qty}\"\n                    );\n                    return Ok(None);\n                }\n                incremental\n            } else {\n                // First fill, use accumulated as incremental\n                current_filled\n            }\n        } else {\n            anyhow::bail!(","sourceCodeStart":2100,"sourceCodeEnd":2136,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/okx/src/websocket/parse.rs#L2100-L2136","documentation":"Regular order fill parsing also treats acc_fill_sz as a monotonically increasing cumulative total. An update whose cumulative value is below the previously observed filled quantity is rejected as stale data (commonly replayed after reconnect) rather than applied, protecting order state from regression.","triggerScenarios":"An orders-channel update with acc_fill_sz less than the previously tracked filled quantity for the same order — usually an out-of-order or replayed message after a WebSocket reconnect.","commonSituations":"Reconnect replaying buffered messages; duplicate subscriptions delivering old snapshots; clock/sequence skew between REST snapshot and WS stream.","solutions":["Check for duplicate/overlapping subscriptions and ensure the reconnect handler re-syncs state via REST before resuming the stream","Catch this error and drop the stale update if out-of-order delivery is expected in your deployment","Track a sequence/timestamp per order and ignore updates older than the last applied one"],"exampleFix":"// before\nlet report = parse_fill_report(&msg, instrument, Some(prev_qty), ...)?; // bails on regression\n// after\nmatch parse_fill_report(&msg, instrument, Some(prev_qty), ...) {\n    Ok(r) => handle(r),\n    Err(e) if e.to_string().contains(\"went backwards\") => tracing::warn!(\"stale fill ignored\"),\n    Err(e) => return Err(e),\n}","handlingStrategy":"try-catch","validationCode":"if let (Some(prev), Some(acc)) = (previous_filled_qty, &msg.acc_fill_sz) {\n    let cur: u64 = acc.parse().unwrap_or(0);\n    if cur < prev.raw() { /* stale: drop or resync via REST */ }\n}","typeGuard":null,"tryCatchPattern":"match parse_fill_report(&msg, instrument, prev_qty, ...) {\n    Ok(r) => handle(r),\n    Err(e) if e.to_string().contains(\"went backwards\") => tracing::warn!(\"stale fill dropped: {e}\"),\n    Err(e) => return Err(e),\n}","preventionTips":["Resync filled quantities from REST after every WebSocket reconnect","Avoid duplicate orders-channel subscriptions","Track last-seen update timestamps per instrument/order"],"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"}