{"record":{"id":"4a704cfbd59e8716","repo":"nautechsystems/nautilus_trader","slug":"failed-to-parse-spread-fill-sz-e","errorCode":null,"errorMessage":"Failed to parse spread fill_sz='{}': {e}","messagePattern":"Failed to parse spread fill_sz='(.+?)': (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/okx/src/websocket/parse.rs","lineNumber":2007,"sourceCode":"            format!(\"cancel_source={source}\")\n        };\n        report = report.with_cancel_reason(reason);\n    }\n\n    Ok(report)\n}\n\nfn parse_spread_order_fill_report(\n    msg: &OKXSpreadOrder,\n    instrument: &InstrumentAny,\n    _account_id: AccountId,\n    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            }","sourceCodeStart":1989,"sourceCodeEnd":2025,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/okx/src/websocket/parse.rs#L1989-L2025","documentation":"The OKX WebSocket adapter failed to parse the order message's incremental fill size (`fill_sz`) into a Quantity rounded to the spread instrument's size precision while building an OrderFilled report for a spread instrument. This error is thrown because a malformed or unrepresentable fill size cannot produce a valid fill event, and silently dropping it would corrupt order state tracking.","triggerScenarios":"parse_spread_order_event / parse_spread_order_msg receive an OKX 'orders' channel message for a spread (SPRW/SPOT instrument family) where fill_sz is non-empty and not \"0\", but parse_quantity(fill_sz, size_precision) fails — e.g. fill_sz contains a non-numeric string, is negative, or has more decimals than the instrument's size_precision.","commonSituations":"OKX pushes an unexpected fill_sz format after an API change; the local spread InstrumentDef has a stale/incorrect lot size (size_precision) that rejects a legitimate fill size like '0.0001'; corrupted or hand-crafted test payloads.","solutions":["Refresh the instrument definition (lotSz from OKX spread instruments endpoint) so size_precision matches the exchange's actual precision, then reconnect the WebSocket.","Log the raw msg (channel/arg + data) and inspect fill_sz for non-numeric or over-precise values.","If OKX changed the field format, update parse_quantity / the OkxOrderMsg model in this adapter.","If the value is genuinely invalid from the exchange, treat it as an upstream data issue: skip the fill and resync order state via REST."],"exampleFix":"// before: instrument def with stale lotSz -> size_precision = 0 rejects '0.5'\n// after: reload instrument definitions before subscribing\nlet instrument = await provider.instrument(instrument_id).await?; // fresh lotSz\nws.subscribe_spread_orders(instrument);","handlingStrategy":"validation","validationCode":"// Rust: verify fill_sz parses at the instrument's precision before it reaches the stream handler\nfn is_valid_fill_sz(fill_sz: &str, precision: u32) -> bool {\n    !fill_sz.is_empty() && fill_sz != \"0\"\n        && rust_decimal::Decimal::from_str(fill_sz).map(|d| d.scale() as u32 <= precision).unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match parse_spread_order_event(msg, instrument, ...) {\n    Ok(Some(event)) => handle(event),\n    Ok(None) => {}\n    Err(e) => { log::warn!(\"skipping bad spread fill: {e:#}\"); resync_order_state(inst_id); }\n}","preventionTips":["Reload instrument definitions (lotSz/size_precision) before each WebSocket session.","Monitor adapter logs for parse failures as an early sign of OKX API schema drift.","Keep the OKX adapter version aligned with the API version in use."],"tags":["parsing","websocket","okx","order-fill"],"backgroundTag":"invalid-argument-format","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}