{"record":{"id":"ff2a69c64059bda0","repo":"nautechsystems/nautilus_trader","slug":"decimal-average-price-must-parse-as-f64","errorCode":null,"errorMessage":"Decimal average price must parse as f64","messagePattern":"Decimal average price must parse as f64","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/model/src/orderbook/analysis.rs","lineNumber":142,"sourceCode":"    for (book_price, level) in levels {\n        let size_this_level = level.size_raw().min(qty.raw - cumulative_size_raw);\n        let size_this_level_decimal = Quantity::raw_as_decimal(size_this_level);\n        cumulative_size_raw += size_this_level;\n        cumulative_size += size_this_level_decimal;\n        cumulative_value += book_price.value.as_decimal() * size_this_level_decimal;\n\n        if cumulative_size_raw >= qty.raw {\n            break;\n        }\n    }\n\n    if cumulative_size_raw == 0 {\n        0.0\n    } else {\n        (cumulative_value / cumulative_size)\n            .to_string()\n            .parse::<f64>()\n            .expect(\"Decimal average price must parse as f64\")\n    }\n}\n\n/// Calculates the worst (last-touched) price while filling a specified quantity\n/// from order book levels.\n///\n/// For buy-side traversal this is the highest ask touched; for sell-side traversal\n/// this is the lowest bid touched. Returns `None` when no quantity can be matched.\n#[must_use]\npub fn get_worst_px_for_quantity(\n    qty: Quantity,\n    levels: &BTreeMap<BookPrice, BookLevel>,\n) -> Option<Price> {\n    let mut cumulative_size_raw: QuantityRaw = 0;\n    let mut worst_price: Option<Price> = None;\n\n    for (book_price, level) in levels {\n        let size_this_level = level.size_raw().min(qty.raw - cumulative_size_raw);","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/model/src/orderbook/analysis.rs#L124-L160","documentation":"In `get_avg_px_for_quantity` (crates/model/src/orderbook/analysis.rs), the cumulative Decimal average is converted to a string and parsed back to f64, panicking if parsing fails. Because a Decimal produced by dividing cumulative value by size always round-trips through its string representation, this panic signals an internal invariant break (e.g. a Decimal implementation emitting an unparseable representation such as NaN/Infinity text). Users hit it only via such a library-internal defect, not through invalid input.","triggerScenarios":"Calling get_avg_px_for_quantity with cumulative value/size that produce a non-finite Decimal (e.g. overflow of the internal Decimal representation from extremely large raw values), yielding a string like \"Inf\" that fails parse::<f64>... or a Decimal formatting regression.","commonSituations":"Deep books with enormous aggregated sizes/values on high-precision instruments, or after upgrading dependency versions where the Decimal type's Display output changed format.","solutions":["Reduce the magnitude of inputs — cap quantity or operate on a shallower price range to avoid Decimal overflow.","Check the rust_decimal version for known Display/parse round-trip regressions and pin a known-good version.","Reproduce with the specific book snapshot and file an issue with the failing level data.","As a workaround, compute the average in f64 incrementally instead of relying on the Decimal round-trip."],"exampleFix":"// before\nlet avg = book.get_avg_px_for_quantity(OrderSide.BUY, huge_qty); // may panic\n// after\nlet qty = huge_qty.min(book.total_volume(OrderSide.BUY));\nlet avg = book.get_avg_px_for_quantity(OrderSide.BUY, qty);","handlingStrategy":"fallback","validationCode":"// Rust caller: bound the requested quantity to what the book can fill\nlet max_qty = book.cumulative_qty(side, book.best_price(side));\nlet qty = requested.min(max_qty);","typeGuard":"fn is_parseable_f64(s: &str) -> bool {\n    s.parse::<f64>().is_ok()\n}","tryCatchPattern":"// Cannot catch a Rust panic from Python; avoid triggering inputs.\nqty = min(qty, book.total_volume(side))\navg_px = book.get_avg_px_for_quantity(side, qty)","preventionTips":["Bound requested quantities to available book depth.","Pin rust_decimal to a version with known-good Display/parse round-trip.","Report overflowing inputs (huge cumulative sizes) upstream with reproduction data."],"tags":["rust","panic","decimal","orderbook","parsing"],"backgroundTag":"internal-invariant-violation","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}