nautechsystems/nautilus_trader · error

position fragments do not cover voided quantity for fill {}

Error message

position fragments do not cover voided quantity for fill {}

What it means

During fill-void (cancel/adjust) processing in the execution engine, the engine walks cached positions that were touched by the fill and subtracts each position's fragment of the voided quantity. If after consuming all matching fragments the remaining quantity is not zero, the position bookkeeping is inconsistent with the fill being voided, so the engine aborts the void with this error to preserve exact quantity arithmetic.

Source

Thrown at crates/execution/src/engine/mod.rs:3928

            "no position fragments found for fill {}",
            event.trade_id
        );
        fragments.sort_by_key(|(_, split_rank, _, _)| *split_rank);

        let mut allocations = IndexMap::<PositionId, (Quantity, Option<Money>)>::new();
        let mut remaining_qty = event.voided_qty;
        for (position_id, _, quantity, _) in fragments.iter().rev() {
            if remaining_qty.is_zero() {
                break;
            }
            let removed = remaining_qty.min(*quantity);
            allocations
                .entry(*position_id)
                .and_modify(|allocation| allocation.0 = allocation.0 + removed)
                .or_insert((removed, None));
            remaining_qty = remaining_qty - removed;
        }
        anyhow::ensure!(
            remaining_qty.is_zero(),
            "position fragments do not cover voided quantity for fill {}",
            event.trade_id
        );

        if let Some(mut remaining_commission) = event.commission_voided {
            for (position_id, _, _, commission) in fragments.iter().rev() {
                if remaining_commission.is_zero() {
                    break;
                }
                let Some(commission) = commission else {
                    continue;
                };
                anyhow::ensure!(
                    commission.currency == remaining_commission.currency,
                    "position commission currency differs for fill {}",
                    event.trade_id
                );

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify the voided event's trade_id/voided_qty matches the original fill quantities that were applied to the positions.
  2. Check for duplicated or replayed void events and make void application idempotent before dispatching to the engine.
  3. Rebuild the cache/position state from a consistent snapshot so position quantities reflect the fills being voided.
  4. Inspect the position_id->allocation loop in crates/execution/src/engine/mod.rs (~line 3920) and log allocations to find which fragment is missing or double-counted.

Example fix

// before: voiding with a quantity that doesn't match applied fills
let event = FillVoided { voided_qty: Quantity::from(10), .. };
engine.apply_fill_void(event)?; // panics/errors: fragments cover only 6
// after: reconcile voided_qty against the recorded fills
let voided_qty = recorded_fills.iter().map(|f| f.qty).sum();
let event = FillVoided { voided_qty, .. };
engine.apply_fill_void(event)?;
Defensive patterns

Strategy: validation

Validate before calling

// verify voided qty matches the fills recorded for this trade before applying
let applied: Quantity = positions_affected_by_fill(event.trade_id)
    .iter().map(|p| p.fill_qty_for(event.trade_id)).sum();
assert_eq!(applied, event.voided_qty, "voided quantity mismatch for fill");

Try / catch

match engine.apply_fill_void(event) {
    Err(e) if e.to_string().contains("do not cover voided quantity") => {
        log::error!("inconsistent void state for {}: {e}", event.trade_id);
        // halt event replay; snapshot state for diagnosis
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling the fill-void handling (e.g. applying a FillVoided/FillCancellation event) where the sum of quantity removed per matched position_id is less than event.voided_qty — e.g. one of the positions was already voided, adjusted, or the void event quantity disagrees with the originally recorded fills.

Common situations: Replaying out-of-order or duplicated fill/void events after a restart; manually editing or restoring cache state from a partial snapshot; adapter bugs (e.g. Betfair fill tracker) emitting voided quantities that do not match fills previously applied to positions.

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