nautechsystems/nautilus_trader · error

venue-leg fills are negative

Error message

venue-leg fills are negative

What it means

venue_leg_filled_before_and_quantity reconstructs the venue-leg filled quantity by summing/adjusting event quantities (including subtracting voided_qty). If the resulting total is negative — impossible for a filled quantity — the invariant is violated and the function errors. This guards order-status reconstruction used by reconciliation and order reports.

Source

Thrown at crates/adapters/polymarket/src/execution/reconciliation.rs:78

    order: &OrderAny,
    venue_order_id: VenueOrderId,
    size_precision: u8,
) -> anyhow::Result<(Quantity, Quantity)> {
    let mut filled = Decimal::ZERO;

    for event in order.events() {
        match event {
            OrderEventAny::Filled(event) if event.venue_order_id == venue_order_id => {
                filled += event.last_qty.as_decimal();
            }
            OrderEventAny::FillVoided(event) if event.venue_order_id == venue_order_id => {
                filled -= event.voided_qty.as_decimal();
            }
            _ => {}
        }
    }

    anyhow::ensure!(filled >= Decimal::ZERO, "venue-leg fills are negative");
    let current_leg_filled = Quantity::from_decimal_dp(filled, size_precision)
        .context("venue-leg fills exceed quantity precision")?;
    anyhow::ensure!(
        current_leg_filled.as_decimal() == filled,
        "venue-leg fills cannot be represented exactly"
    );
    let filled_before = order
        .filled_qty()
        .checked_sub(current_leg_filled)
        .context("current venue-leg fills exceed cumulative fills")?;
    let leg_quantity = order
        .quantity()
        .checked_sub(filled_before)
        .context("fills before current venue leg exceed logical quantity")?;
    Ok((filled_before, leg_quantity))
}

/// Shared context for trade-to-fill-report conversion.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Inspect the event sequence for the order and look for duplicate or oversized void_qty events.
  2. Delete and rebuild the cached order state so events are re-fetched from the venue.
  3. Clamp/validate voided_qty against accumulated filled qty before subtracting.

Example fix

// before
filled -= event.voided_qty.as_decimal();
// after
let new_filled = filled - event.voided_qty.as_decimal();
anyhow::ensure!(new_filled >= Decimal::ZERO, "voided qty exceeds fills");
filled = new_filled;
Defensive patterns

Strategy: try-catch

Validate before calling

if voided_qty > filled_qty { return Err(anyhow!("voided exceeds filled")); }

Try / catch

match venue_leg_filled_before_and_quantity(order, events, size_precision) {
    Ok(q) => q,
    Err(e) => { log::error!("fill reconstruction failed: {e:#}; rebuilding from venue"); rebuild_order_from_venue(order_id)? }
}

Prevention

When it happens

Trigger: Replaying order events where a void/cancel adjustment exceeds the accumulated filled quantity — e.g. duplicate void events, out-of-order event application, or corrupt cache in load_orders_from_cache / query_order_command / generate_order_status_report(s)_impl.

Common situations: Cache corruption after a crash; double-applying void events during replay; adapter bugs emitting voided_qty larger than actual fills.

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/9ca625ec50ea063d. Report an issue: GitHub.