nautechsystems/nautilus_trader · error

position fill void exceeds known fragments for {}

Error message

position fill void exceeds known fragments for {}

What it means

Position::apply_fill_void rejects a fill-void event whose voided quantity is zero or larger than the total quantity of known fill fragments recorded for that client_order_id/trade_id pair. The position model can only void fills it actually observed; voiding unknown or zero quantity would corrupt PnL and size accounting. This is an input-validation guard on position mutation.

Source

Thrown at crates/model/src/position.rs:748

    /// cycle boundaries its existing archive describes. `None` when the corrected history never
    /// goes flat, so the current cycle covers all of it.
    ///
    /// # Errors
    ///
    /// Returns an error when the allocation is stale, duplicated, or exceeds known fragments.
    pub fn apply_fill_void(
        &mut self,
        event: OrderFillVoided,
        voided_qty: Quantity,
        commission_voided: Option<Money>,
    ) -> anyhow::Result<Option<Money>> {
        let fragment_qty = self
            .fill_fragments(event.client_order_id, event.trade_id)
            .iter()
            .fold(Quantity::zero(self.size_precision), |total, fill| {
                total + fill.last_qty
            });
        anyhow::ensure!(
            !voided_qty.is_zero() && voided_qty <= fragment_qty,
            "position fill void exceeds known fragments for {}",
            event.trade_id,
        );

        if let Some(previous) = self.fill_voids.iter().rev().find(|record| {
            record.event.client_order_id == event.client_order_id
                && record.event.trade_id == event.trade_id
        }) {
            anyhow::ensure!(
                voided_qty >= previous.voided_qty,
                "stale position fill void for {}",
                event.trade_id,
            );
            anyhow::ensure!(
                voided_qty != previous.voided_qty
                    || commission_voided != previous.commission_voided,
                "duplicate position fill void for {}",

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify the void event's client_order_id and trade_id match a fill previously applied to this position (check fill_fragments) before calling apply_fill_void.
  2. Ensure voided_qty is a positive quantity (not zero) and expressed in the same units/precision as the original fill quantities.
  3. Rebuild the position from the full event/replay history so all fill fragments are present before applying the void.
  4. If the void legitimately exceeds locally known fragments, reconcile with the venue first (fetch authoritative fills) rather than forcing the void.

Example fix

// before: voiding a fill that was never applied to this position
position.apply_fill_void(void_event)?; // panics/errors: exceeds known fragments
// after: guard first
let known: Quantity = position
    .fill_fragments(void_event.client_order_id, void_event.trade_id)
    .iter()
    .fold(Quantity::zero(position.size_precision), |t, f| t + f.last_qty);
anyhow::ensure!(!void_event.voided_qty.is_zero() && void_event.voided_qty <= known,
    "skip void: known fragments {} < voided {}", known, void_event.voided_qty);
position.apply_fill_void(void_event)?;
Defensive patterns

Strategy: validation

Validate before calling

let known: Quantity = position
    .fill_fragments(ev.client_order_id, ev.trade_id)
    .iter()
    .fold(Quantity::zero(position.size_precision), |t, f| t + f.last_qty);
if ev.voided_qty.is_zero() || ev.voided_qty > known {
    return Err(anyhow!("cannot void {}: known fragments {}", ev.trade_id, known));
}

Prevention

When it happens

Trigger: Calling apply_fill_void with a void event whose voided_qty is Quantity::zero, or with a trade_id/client_order_id combination whose accumulated fragment fills sum to less than the requested voided_qty (e.g. voiding a fill never recorded on this position, or after fragments were omitted due to a missed/replayed event stream).

Common situations: Reconciling an exchange's trade-cancel/adjustment messages against an incomplete local fill history; replaying historical data where a duplicate trade id caused fragments to be skipped; wiring a void event to the wrong position or instrument; passing quantity in the wrong units (contracts vs base) so the void exceeds recorded fragments.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/9328cb9ce54494f7. Report an issue: GitHub.