nautechsystems/nautilus_trader · error

no position fragments found for fill {}

Error message

no position fragments found for fill {}

What it means

While splitting a fill across open positions (fragment computation for position events), the engine accumulates fragments from matching positions and then asserts via anyhow::ensure! that at least one fragment was produced. Zero fragments means the fill's quantity could not be attributed to any known open position, so position bookkeeping cannot proceed.

Source

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

                let PositionReplayEvent::Filled(fill) = replay_event else {
                    continue;
                };

                if fill.client_order_id != event.client_order_id || fill.trade_id != event.trade_id
                {
                    continue;
                }
                let split_rank = if fill.event_id == source_event_id {
                    0
                } else if fill.causation_id == Some(source_event_id) {
                    1
                } else {
                    continue;
                };
                fragments.push((position.id, split_rank, fill.last_qty, fill.commission));
            }
        }
        anyhow::ensure!(
            !fragments.is_empty(),
            "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;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Confirm the cache holds the open position(s) the fill applies to (check position ids and instrument in cache.positions).
  2. Re-run reconciliation/mass-status handling so local positions match the broker before processing further fills.
  3. Check for duplicate fills that already closed the target position earlier in the stream.
  4. Reload the cache or resubscribe/refresh position state, then replay the fill.
Defensive patterns

Strategy: validation

Validate before calling

// confirm the positions the fill targets are open before processing
let open = cache.positions(Some(instrument_id), None);
if open.is_empty() {
    // run reconciliation before applying the fill
}

Try / catch

if let Err(e) = process_fill(event) {
    if e.to_string().contains("no position fragments found") {
        // reconcile with broker state, then replay the fill
    }
}

Prevention

When it happens

Trigger: Processing a fill (event.trade_id) in the position-fragment path when every candidate position is skipped (e.g. position ids no longer open or quantities don't match), leaving the fragments vec empty at the ensure!.

Common situations: Cache does not contain the positions the fill should close (stale or rebuilt cache); fill arrives for a position already closed by a prior duplicate fill; OEMS position state diverged from broker state after reconnect; reconciliation applying fills out of order.

Understand the failure class

Background: EmptyResultError / "no results found": when an API or scraper succeeds but returns zero rows — this error's family across 9 libraries.

Related errors


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