nautechsystems/nautilus_trader · error

Position {position_id} not found

Error message

Position {position_id} not found

What it means

This panic fires in the cache's bulk position fetch: each position_id in position_ids must exist in self.positions or the code panics via unwrap_or_else. It assumes callers pass only ids of open or retained positions, so a missing id is an invariant violation.

Source

Thrown at crates/common/src/cache/mod.rs:5938

    /// Each [`PositionRef`] in the returned vector borrows its underlying cell; mutating any of
    /// those positions while the vector is alive will panic at runtime. Drop the vector before
    /// issuing writes.
    ///
    /// # Panics
    ///
    /// Panics if any `position_id` in the set is not found in the cache.
    fn get_positions_for_ids(
        &self,
        position_ids: &AHashSet<PositionId>,
        side: Option<PositionSide>,
    ) -> Vec<PositionRef<'_>> {
        let mut positions = Vec::new();

        for position_id in position_ids {
            let position_cell = self
                .positions
                .get(position_id)
                .unwrap_or_else(|| panic!("Position {position_id} not found"));
            let position = PositionRef::new(position_cell.borrow());

            if side.is_none_or(|side| side == position.side) {
                positions.push(position);
            }
        }

        // Sort so callers receive a deterministic Vec across runs; the
        // underlying position_ids set is AHash-backed.
        positions.sort_by_key(|p| p.id);
        positions
    }

    /// Returns the `ClientOrderId`s of all orders.
    #[must_use]
    pub fn client_order_ids(
        &self,
        venue: Option<&Venue>,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Verify each position_id belongs to the current run and still exists in the cache
  2. Filter the id list against cache presence before the bulk fetch
  3. Avoid retaining position ids across cache resets; re-query open positions instead
  4. Log the missing position_id and cross-check with the originating source

Example fix

// before
let positions = cache.positions(&stale_position_ids, None);
// after
let ids: Vec<PositionId> = stale_position_ids
    .into_iter()
    .filter(|pid| cache.position(pid).is_some())
    .collect();
let positions = cache.positions(&ids, None);
Defensive patterns

Strategy: validation

Validate before calling

let known: Vec<&PositionId> = position_ids
    .iter()
    .filter(|pid| cache.position(pid).is_some())
    .collect();

Prevention

When it happens

Trigger: Calling the bulk position accessor with a position_id that was never opened, was closed and purged from the cache, or comes from another backtest/session.

Common situations: Holding position ids from a previous run or report while running a fresh backtest; flat/closed positions removed from the cache but still referenced by strategy state; id typos.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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