{"record":{"id":"9278b330df901a11","repo":"nautechsystems/nautilus_trader","slug":"failed-to-parse-price-fill-px-avg-px","errorCode":null,"errorMessage":"Failed to parse price (fill_px='{}', avg_px='{}', px='{}'): {}","messagePattern":"Failed to parse price \\(fill_px='(.+?)', avg_px='(.+?)', px='(.+?)'\\): (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/okx/src/websocket/parse.rs","lineNumber":2094,"sourceCode":"        TradeId::new(&synthetic)\n    } else {\n        TradeId::new(&msg.trade_id)\n    };\n\n    let order_side = OrderSide::from(msg.side);\n\n    let price_precision = instrument.price_precision();\n    let size_precision = instrument.size_precision();\n\n    let price_str = if !msg.fill_px.is_empty() {\n        &msg.fill_px\n    } else if !msg.avg_px.is_empty() {\n        &msg.avg_px\n    } else {\n        &msg.px\n    };\n    let last_px = parse_price(price_str, price_precision).map_err(|e| {\n        anyhow::anyhow!(\n            \"Failed to parse price (fill_px='{}', avg_px='{}', px='{}'): {}\",\n            msg.fill_px,\n            msg.avg_px,\n            msg.px,\n            e\n        )\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}\",)","sourceCodeStart":2076,"sourceCodeEnd":2112,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/okx/src/websocket/parse.rs#L2076-L2112","documentation":"While building an OrderFilled report for a regular (non-spread) OKX order update, the adapter picks the fill price from fill_px, else avg_px, else px, and parses it with the instrument's price precision. This error is thrown when none of those price strings parse into a valid Price at the instrument's precision, so the fill report cannot be constructed.","triggerScenarios":"parse_fill_report (called from parse_order_event / parse_order_msg) receives an OKX 'orders' channel message where the chosen price field (fill_px or avg_px or px) is non-empty but fails parse_price — e.g. empty string when other fields are empty too, non-numeric text, negative price, or more decimals than price_precision.","commonSituations":"Canceled orders carrying empty price fields; a stale instrument definition whose price_precision (tickSz) is tighter than the received price; OKX API format changes; corrupted test fixtures.","solutions":["Check which of fill_px/avg_px/px appeared in the message (the error prints all three) and whether the value is numeric and non-negative.","Reload the instrument definition so price_precision matches OKX's current tickSz for the instrument.","If the order never traded (e.g. canceled with empty fill_px), ensure only filled order updates reach parse_fill_report.","Update the adapter if OKX changed the order push schema."],"exampleFix":"// before: stale tickSz => price_precision rejects '112345.5'\n// after: refresh instrument defs before subscribing\nlet instrument = await provider.instrument(inst_id).await?; // fresh tickSz","handlingStrategy":"validation","validationCode":"// Rust: validate the chosen price string before the adapter does\nfn is_parseable_price(fill_px: &str, avg_px: &str, px: &str, precision: u32) -> bool {\n    let s = if !fill_px.is_empty() { fill_px } else if !avg_px.is_empty() { avg_px } else { px };\n    rust_decimal::Decimal::from_str(s).map(|d| d.scale() as u32 <= precision).unwrap_or(false)\n}","typeGuard":null,"tryCatchPattern":"match parse_fill_report(&msg, ...) {\n    Ok(r) => emit(r),\n    Err(e) => { log::warn!(\"fill price parse failed: {e}\"); resync_fills_via_rest(&msg.inst_id); }\n}","preventionTips":["Refresh instrument tickSz/price_precision before subscribing to order channels.","Route only filled order updates (state='filled'/partial) into fill parsing.","Watch for OKX changelog updates affecting the orders channel schema."],"tags":["parsing","okx","price","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"}