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
- 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.
- Ensure voided_qty is a positive quantity (not zero) and expressed in the same units/precision as the original fill quantities.
- Rebuild the position from the full event/replay history so all fill fragments are present before applying the void.
- 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
- Only emit void events for trades previously applied to the position.
- Keep void quantities in the same units and precision as fill quantities.
- Rebuild positions from complete event history before applying adjustments.
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
- Cannot calculate inverse points: open price is not positive
- Cannot calculate inverse points: close price is not positive
- stale position fill void for {}
- duplicate position fill void for {}
- inverse position {} has no base currency
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/9328cb9ce54494f7.
Report an issue: GitHub.