{"record":{"id":"fa675b3f34250a15","repo":"nautechsystems/nautilus_trader","slug":"failed-to-resolve-positive-quantity-for-stream-ord","errorCode":null,"errorMessage":"failed to resolve positive quantity for stream order update {} (order_type={:?}, persistence_type={:?}, size={}, bsp_liability={:?}, size_matched={:?}, size_remaining={:?}, size_cancelled={:?}, size_lapsed={:?}, size_voided={:?})","messagePattern":"failed to resolve positive quantity for stream order update (.+?) \\(order_type=(.+?), persistence_type=(.+?), size=(.+?), bsp_liability=(.+?), size_matched=(.+?), size_remaining=(.+?), size_cancelled=(.+?), size_lapsed=(.+?), size_voided=(.+?)\\)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/betfair/src/stream/parse.rs","lineNumber":816,"sourceCode":"    let size_matched = uo.sm.unwrap_or(Decimal::ZERO);\n    let size_cancelled = uo.sc.unwrap_or(Decimal::ZERO);\n    let size_lapsed = uo.sl.unwrap_or(Decimal::ZERO);\n    let size_voided = uo.sv.unwrap_or(Decimal::ZERO);\n\n    // Include lapsed/voided in the closed quantity for status resolution\n    let size_closed = size_cancelled + size_lapsed + size_voided;\n    let order_status = if uo.status == StreamingOrderStatus::ExecutionComplete\n        && size_voided > Decimal::ZERO\n        && size_cancelled.is_zero()\n        && size_lapsed.is_zero()\n    {\n        OrderStatus::Voided\n    } else {\n        resolve_streaming_order_status(uo.status, size_matched, size_closed)\n    };\n\n    let quantity_decimal = stream_order_quantity(uo);\n    anyhow::ensure!(\n        quantity_decimal > Decimal::ZERO,\n        \"failed to resolve positive quantity for stream order update {} \\\n         (order_type={:?}, persistence_type={:?}, size={}, bsp_liability={:?}, \\\n         size_matched={:?}, size_remaining={:?}, size_cancelled={:?}, size_lapsed={:?}, size_voided={:?})\",\n        uo.id,\n        uo.ot,\n        uo.pt,\n        uo.s,\n        uo.bsp,\n        uo.sm,\n        uo.sr,\n        uo.sc,\n        uo.sl,\n        uo.sv,\n    );\n    let quantity = parse_betfair_quantity(quantity_decimal)?;\n    let filled_qty = parse_betfair_quantity(size_matched)?;\n","sourceCodeStart":798,"sourceCodeEnd":834,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/betfair/src/stream/parse.rs#L798-L834","documentation":"When converting a Betfair streaming order-market-change (UnmatchedOrder) into an OrderStatusReport, no positive quantity could be resolved from the update's fields (size, bsp_liability, matched/remaining/cancelled/lapsed/voided sizes). The parser enforces a positive quantity invariant for all emitted order reports.","triggerScenarios":"parse_order_status_report processing a stream update whose stream_order_quantity resolution yields zero/non-positive: fully cancelled/lapsed/voided orders, SP orders with no bsp_liability, or duplicate/terminal updates carrying all-None size fields.","commonSituations":"Subscribing to order streams with historical/terminal order conflation enabled; market-on-close (SP) orders before liability is set; orders cancelled before matching arriving on the stream; replayed/duplicated updates.","solutions":["Filter terminal updates with zero matched size before feeding them to the parser, or skip this error case in the stream handler.","Check order_type (ot): for LIMIT_OR_WITH_RESERVE_SP/LIMIT_ON_CLOSE ensure size is present; for MARKET_ON_CLOSE ensure bsp_liability is set.","Verify the stream filter isn't requesting already-terminated orders whose size fields are all None.","Log the raw UnmatchedOrder payload and confirm Betfair is populating size fields as expected."],"exampleFix":"// before\nlet report = parse_order_status_report(&account_id, &uo, ts_init)?;\n// after\nlet qty = stream_order_quantity(&uo);\nif qty <= Decimal::ZERO {\n    log::debug!(\"skipping zero-quantity stream update for order {}\", uo.id);\n    return Ok(None);\n}\nlet report = parse_order_status_report(&account_id, &uo, ts_init)?;","handlingStrategy":"validation","validationCode":"fn stream_update_has_qty(uo: &UnmatchedOrder) -> bool {\n    match uo.ot {\n        OrderType::MarketOnClose => uo.bsp_liability.map_or(false, |l| l > 0.0),\n        _ => uo.size.map_or(false, |s| s > 0.0)\n            || uo.size_matched.map_or(false, |m| m > 0.0)\n            || uo.size_remaining.map_or(false, |r| r > 0.0),\n    }\n}","typeGuard":"fn is_zero_qty_update(uo: &UnmatchedOrder) -> bool {\n    uo.size.unwrap_or(0.0) <= 0.0\n        && uo.bsp_liability.unwrap_or(0.0) <= 0.0\n        && uo.size_matched.unwrap_or(0.0) <= 0.0\n        && uo.size_remaining.unwrap_or(0.0) <= 0.0\n}","tryCatchPattern":"match parse_order_status_report(&account_id, &uo, ts_init) {\n    Ok(r) => r,\n    Err(e) if e.to_string().contains(\"failed to resolve positive quantity\") => {\n        log::debug!(\"ignoring zero-quantity stream update for order {}\", uo.id);\n        return Ok(None);\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Filter out all-None/zero size updates before parsing.","Exclude historical/terminal orders from the stream subscription where possible.","Treat SP (market-on-close) orders specially: expect bsp_liability rather than size."],"tags":["streaming","parsing","betfair","orders"],"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-23T08:17:48.524Z"}