{"record":{"id":"e0f9bb99f2831c14","repo":"nautechsystems/nautilus_trader","slug":"failed-to-parse-fee-fee-str-e","errorCode":null,"errorMessage":"Failed to parse fee '{fee_str}': {e}","messagePattern":"Failed to parse fee '(.+?)': (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/okx/src/websocket/parse.rs","lineNumber":2153,"sourceCode":"        } else {\n            anyhow::bail!(\n                \"Cannot determine fill quantity: fill_sz is empty/zero and acc_fill_sz is empty/zero\"\n            );\n        }\n    } else {\n        anyhow::bail!(\n            \"Cannot determine fill quantity: fill_sz='{}' and acc_fill_sz is None\",\n            msg.fill_sz\n        );\n    };\n\n    let fee_str = msg\n        .fee\n        .as_deref()\n        .filter(|fee| !fee.trim().is_empty())\n        .ok_or_else(|| anyhow::anyhow!(\"missing fee for fill report inst_id={}\", msg.inst_id))?;\n    let fee_dec = Decimal::from_str(fee_str)\n        .map_err(|e| anyhow::anyhow!(\"Failed to parse fee '{fee_str}': {e}\"))?;\n\n    let fee_currency = parse_fee_currency(msg.fee_ccy.as_str(), fee_dec, || {\n        format!(\"fill report for inst_id={}\", msg.inst_id)\n    });\n\n    // OKX sends fees as negative numbers (e.g., \"-2.5\" for a $2.5 charge), parse_fee negates to positive\n    let total_fee = parse_fee(Some(fee_str), fee_currency)\n        .map_err(|e| anyhow::anyhow!(\"Failed to parse fee={:?}: {}\", msg.fee, e))?;\n\n    // OKX sends cumulative fees, so we subtract the previous total to get this fill's fee\n    let commission = if let Some(previous_fee) = previous_fee {\n        if total_fee.currency == previous_fee.currency {\n            let incremental = total_fee - previous_fee;\n\n            if incremental < Money::zero(fee_currency) {\n                log::debug!(\n                    \"Negative incremental fee detected - likely a maker rebate or fee refund: order_id={}, total_fee={}, previous_fee={}, incremental={}\",\n                    msg.ord_id.as_str(),","sourceCodeStart":2135,"sourceCodeEnd":2171,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/okx/src/websocket/parse.rs#L2135-L2171","documentation":"After confirming the fee string is present, the adapter converts it to a Decimal before computing the fee Money object. This error is thrown when Decimal::from_str fails on the fee string — the value OKX sent is not a valid decimal number.","triggerScenarios":"parse_fill_report receives a fee string that Decimal::from_str cannot parse: non-numeric text, scientific notation if unsupported, thousands separators, or locale-formatted values.","commonSituations":"OKX API format changes; corrupted or hand-modified test payloads; proxy/middleware mangling numeric fields in WebSocket JSON.","solutions":["Log msg.fee for the failing fill and check what non-decimal text was received.","Verify no middleware or JSON layer is altering the raw WebSocket payload.","If OKX changed the fee format (e.g. scientific notation), extend the adapter's parsing to normalize the string before Decimal::from_str.","Resync fills via REST to confirm the expected format."],"exampleFix":"// before\nlet fee_dec = Decimal::from_str(fee_str).map_err(...)?;\n// after: normalize scientific notation first\nlet normalized = if fee_str.contains('e') || fee_str.contains('E') {\n    rust_decimal::Decimal::from_scientific(fee_str)?.to_string()\n} else { fee_str.to_string() };\nlet fee_dec = Decimal::from_str(&normalized).map_err(...)?;","handlingStrategy":"validation","validationCode":"// Rust: check fee is a plain decimal before the adapter parses it\nfn fee_is_decimal(fee: &str) -> bool {\n    !fee.is_empty() && !fee.contains(['e', 'E', ','])\n        && rust_decimal::Decimal::from_str(fee).is_ok()\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = parse_fill_report(&msg, ...) {\n    log::warn!(\"fee parse failed (fee={:?}): {e}\", msg.fee);\n    fetch_fills_via_rest(&msg.inst_id); // fees available authoritatively there\n}","preventionTips":["Sanity-check fee format in the first fills after connecting (regression detector).","Disable any middleware that rewrites WebSocket JSON payloads.","Test against recorded real payloads rather than synthetic fixtures."],"tags":["okx","fee","parsing","decimal"],"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"}