nautechsystems/nautilus_trader · error · anyhow::Error
failed to resolve positive quantity for stream order update
Error message
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={:?}) What it means
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.
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.
Example fix
// before
let report = parse_order_status_report(&account_id, &uo, ts_init)?;
// after
let qty = stream_order_quantity(&uo);
if qty <= Decimal::ZERO {
log::debug!("skipping zero-quantity stream update for order {}", uo.id);
return Ok(None);
}
let report = parse_order_status_report(&account_id, &uo, ts_init)?; Defensive patterns
Strategy: validation
Validate before calling
fn stream_update_has_qty(uo: &UnmatchedOrder) -> bool {
match uo.ot {
OrderType::MarketOnClose => uo.bsp_liability.map_or(false, |l| l > 0.0),
_ => uo.size.map_or(false, |s| s > 0.0)
|| uo.size_matched.map_or(false, |m| m > 0.0)
|| uo.size_remaining.map_or(false, |r| r > 0.0),
}
} Type guard
fn is_zero_qty_update(uo: &UnmatchedOrder) -> bool {
uo.size.unwrap_or(0.0) <= 0.0
&& uo.bsp_liability.unwrap_or(0.0) <= 0.0
&& uo.size_matched.unwrap_or(0.0) <= 0.0
&& uo.size_remaining.unwrap_or(0.0) <= 0.0
} Try / catch
match parse_order_status_report(&account_id, &uo, ts_init) {
Ok(r) => r,
Err(e) if e.to_string().contains("failed to resolve positive quantity") => {
log::debug!("ignoring zero-quantity stream update for order {}", uo.id);
return Ok(None);
}
Err(e) => return Err(e.into()),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- failed to resolve positive quantity for current order
- missing persistence type for order update
- Cannot extract market ID from
- Cannot extract selection ID from
- Failed to parse order report for
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/fa675b3f34250a15.
Report an issue: GitHub.
Appendix: source
Thrown at crates/adapters/betfair/src/stream/parse.rs:816
let size_matched = uo.sm.unwrap_or(Decimal::ZERO);
let size_cancelled = uo.sc.unwrap_or(Decimal::ZERO);
let size_lapsed = uo.sl.unwrap_or(Decimal::ZERO);
let size_voided = uo.sv.unwrap_or(Decimal::ZERO);
// Include lapsed/voided in the closed quantity for status resolution
let size_closed = size_cancelled + size_lapsed + size_voided;
let order_status = if uo.status == StreamingOrderStatus::ExecutionComplete
&& size_voided > Decimal::ZERO
&& size_cancelled.is_zero()
&& size_lapsed.is_zero()
{
OrderStatus::Voided
} else {
resolve_streaming_order_status(uo.status, size_matched, size_closed)
};
let quantity_decimal = stream_order_quantity(uo);
anyhow::ensure!(
quantity_decimal > Decimal::ZERO,
"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={:?})",
uo.id,
uo.ot,
uo.pt,
uo.s,
uo.bsp,
uo.sm,
uo.sr,
uo.sc,
uo.sl,
uo.sv,
);
let quantity = parse_betfair_quantity(quantity_decimal)?;
let filled_qty = parse_betfair_quantity(size_matched)?;
View on GitHub (pinned to 18893faf8b)