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

  1. Filter terminal updates with zero matched size before feeding them to the parser, or skip this error case in the stream handler.
  2. 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.
  3. Verify the stream filter isn't requesting already-terminated orders whose size fields are all None.
  4. 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

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


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)