{"record":{"id":"e11f539cc9886ef3","repo":"nautechsystems/nautilus_trader","slug":"failed-to-parse-px","errorCode":null,"errorMessage":"Failed to parse px='{}': {}","messagePattern":"Failed to parse px='(.+?)': (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/okx/src/websocket/parse.rs","lineNumber":1798,"sourceCode":"    let is_quote_qty_heuristic = msg.tgt_ccy.is_none()\n        && (msg.inst_type == OKXInstrumentType::Spot || msg.inst_type == OKXInstrumentType::Margin)\n        && msg.side == OKXSide::Buy\n        && order_type == OrderType::Market;\n\n    let (quantity, filled_qty) = if is_quote_qty_explicit || is_quote_qty_heuristic {\n        // Quote-quantity order: sz is in quote currency, need to convert to base\n        let sz_quote_dec = Decimal::from_str(&msg.sz).map_err(|e| {\n            anyhow::anyhow!(\"Failed to parse sz='{}' as quote quantity: {}\", msg.sz, e)\n        })?;\n\n        // Determine the price to use for conversion\n        // Priority: 1) limit price (px) for limit orders, 2) avg_px for market orders\n        let conversion_price_dec =\n            if !is_market_price(&msg.px) {\n                // Limit order: use the limit price (msg.px)\n                Some(\n                    Decimal::from_str(&msg.px)\n                        .map_err(|e| anyhow::anyhow!(\"Failed to parse px='{}': {}\", msg.px, e))?,\n                )\n            } else if !msg.avg_px.is_empty() && msg.avg_px != \"0\" {\n                // Market order with fills: use average fill price\n                Some(Decimal::from_str(&msg.avg_px).map_err(|e| {\n                    anyhow::anyhow!(\"Failed to parse avg_px='{}': {}\", msg.avg_px, e)\n                })?)\n            } else {\n                None\n            };\n\n        // Convert quote quantity to base: quantity_base = sz_quote / price\n        let quantity_base = if let Some(price) = conversion_price_dec {\n            if price.is_zero() {\n                parse_quantity(&msg.sz, size_precision)?\n            } else {\n                Quantity::from_decimal_dp(sz_quote_dec / price, size_precision)?\n            }\n        } else {","sourceCodeStart":1780,"sourceCodeEnd":1816,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/okx/src/websocket/parse.rs#L1780-L1816","documentation":"When converting a quote-quantity sz to base units, the parser prefers the order's limit price msg.px whenever px is not the market-price sentinel (\"-1\"). If that px string cannot be parsed as a Decimal, this error is raised. It means the limit price on a quote-quantity order is present but malformed/non-numeric.","triggerScenarios":"A quote-quantity order (tgt_ccy=QuoteCcy or the SPOT BUY market heuristic) whose msg.px is a non-sentinel string that fails Decimal::from_str — e.g. empty but not \"-1\", whitespace-padded, or otherwise corrupt px on the order update.","commonSituations":"Corrupt or hand-edited recorded WebSocket fixtures; OKX emitting px in an unexpected format for certain instrument types; tests constructing OKXOrderMsg with an invalid px while leaving the quote-qty path active.","solutions":["Log the raw msg.px value from the failing message to confirm the malformed content.","Treat any non-numeric px the same as the market sentinel (fall through to avg_px conversion).","Trim/normalize the px string before parsing.","Fix test fixtures or message construction that put invalid values in px."],"exampleFix":"// before (parse.rs:1796)\nSome(Decimal::from_str(&msg.px)\n    .map_err(|e| anyhow::anyhow!(\"Failed to parse px='{}': {}\", msg.px, e))?),\n\n// after\nmatch Decimal::from_str(msg.px.trim()) {\n    Ok(px) => Some(px),\n    Err(e) => {\n        tracing::warn!(px = %msg.px, \"Unparseable px, falling back to avg_px: {e}\");\n        None // let the avg_px branch handle conversion\n    }\n}","handlingStrategy":"validation","validationCode":"// Rust: ensure px is either the market sentinel or a valid decimal before conversion\nfn usable_limit_px(px: &str) -> Option<Decimal> {\n    if is_market_price(px) {\n        None\n    } else {\n        Decimal::from_str(px.trim()).ok()\n    }\n}","typeGuard":"fn parseable_decimal(s: &str) -> Option<Decimal> {\n    Decimal::from_str(s.trim()).ok()\n}","tryCatchPattern":"match parse_order_status_report(&msg, &instrument, account_id, ts_init) {\n    Ok(report) => handle(report),\n    Err(e) if e.to_string().contains(\"Failed to parse px=\") => {\n        tracing::warn!(ord_id = %msg.ord_id, px = %msg.px, \"malformed px on order update\");\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Treat any non-numeric px identically to the \"-1\" market sentinel when converting quote quantities.","Validate px fields in test OKXOrderMsg constructors.","Keep px normalization (trim/strip) in a single helper used everywhere.","Add round-trip tests covering px=\"-1\", empty, and valid decimal values."],"tags":["rust","okx","decimal-parsing","price-parsing","quote-quantity"],"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"}