{"record":{"id":"a2aff8e5de5d000c","repo":"nautechsystems/nautilus_trader","slug":"missing-fee-for-fill-report-inst-id","errorCode":null,"errorMessage":"missing fee for fill report inst_id={}","messagePattern":"missing fee for fill report inst_id=(.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/okx/src/websocket/parse.rs","lineNumber":2151,"sourceCode":"                current_filled\n            }\n        } 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!(","sourceCodeStart":2133,"sourceCodeEnd":2169,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/okx/src/websocket/parse.rs#L2133-L2169","documentation":"OKX sends the fee on every order fill, and the adapter requires it to build the fill report's commission (Money). This error is thrown when the order message's fee field is absent (None) or whitespace-only, so there is no fee string to parse and the fill report is rejected rather than emitted with a zero/unknown fee.","triggerScenarios":"parse_fill_report receives an OKX 'orders' message whose fee: Option<String> is None or Some(\"\"/whitespace). The error message includes the msg.inst_id of the offending fill.","commonSituations":"OKX sometimes omits fee on certain order-state pushes (e.g. canceled or newly-created order updates with no fill); using a message variant/channel where fee is not populated; OKX API change removing the field on some fill events.","solutions":["Filter order updates so only messages with actual fill data (fillSz/accFillSz changed) reach parse_fill_report; canceled orders typically carry no fee.","Check the raw payload for the given inst_id to confirm fee is genuinely missing from the exchange push.","If OKX legitimately stopped sending fee on fills for your account tier, update the adapter to fetch fees from the REST fills endpoint or emit the fill without commission.","Keep the adapter updated to the OKX API version you are targeting."],"exampleFix":"// before: feeding every order update to parse_fill_report\nlet report = parse_fill_report(&msg, ...)?;\n// after: only fill-bearing updates\nif msg.fill_sz.is_empty() && msg.acc_fill_sz.map_or(true, |s| s == \"0\") { return Ok(None); }\nlet report = parse_fill_report(&msg, ...)?;","handlingStrategy":"try-catch","validationCode":"// Rust: ensure fee is present before requesting a fill report\nfn has_fee(fee: &Option<String>) -> bool {\n    fee.as_deref().map_or(false, |f| !f.trim().is_empty())\n}","typeGuard":"fn fee_present(msg: &OkxOrderMsg) -> Option<&str> {\n    msg.fee.as_deref().filter(|f| !f.trim().is_empty())\n}","tryCatchPattern":"match parse_fill_report(&msg, ...) {\n    Ok(r) => emit(r),\n    Err(e) if e.to_string().contains(\"missing fee\") => {\n        log::debug!(\"no fee on update for {} (likely non-fill push); skipping\", msg.inst_id);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Only feed messages with real fill data into parse_fill_report; canceled/unfilled pushes lack fees.","Confirm your OKX account tier exposes fee fields on the orders channel.","Track OKX changelog for fee-field changes."],"tags":["okx","fee","order-fill","missing-field"],"backgroundTag":"missing-required-argument","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"}