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
- Inspect the event sequence for the order and look for duplicate or oversized void_qty events.
- Delete and rebuild the cached order state so events are re-fetched from the venue.
- 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
- Deduplicate void/cancel events during replay
- Apply events in venue-issued sequence order
- Rebuild cache from the venue after crash recovery
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
- venue-leg fills cannot be represented exactly
- Close command should not be drained
- Flush command should not be drained
- Order invariant violated: first event must be OrderInitializ
- noid '{}' does not match new order oid '{}'
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/9ca625ec50ea063d.
Report an issue: GitHub.