nautechsystems/nautilus_trader · error

Cannot update position {}: not found in cache

Error message

Cannot update position {}: not found in cache

What it means

`update_position` requires the position to already exist in the cache; it mutates the existing shared cell in place. If no position with that ID is held, the method bails instead of creating one — updates are strictly for existing positions (new positions are added via `add_position`).

Source

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

    /// Updates the `order` as pending cancel locally.
    pub fn update_order_pending_cancel_local(&mut self, order: &OrderAny) {
        self.index
            .orders_pending_cancel
            .insert(order.client_order_id());
    }

    /// Updates a `position` already held in the cache.
    ///
    /// Reuses the existing cell so any held [`PositionRef`] handles continue to point at the
    /// canonical entry.
    ///
    /// # Errors
    ///
    /// Returns an error if the position is not already held in the cache, or if updating the
    /// position in the database fails.
    pub fn update_position(&mut self, position: &Position) -> anyhow::Result<()> {
        let Some(position_cell) = self.positions.get(&position.id).cloned() else {
            anyhow::bail!("Cannot update position {}: not found in cache", position.id);
        };

        self.refresh_position_indexes(position);

        *position_cell.borrow_mut() = position.clone();

        if let Some(database) = &mut self.database {
            database.update_position(position)?;
            // TODO: Implement order snapshots
            // if self.snapshot_orders {
            //     database.snapshot_order_state(order)?;
            // }
        }

        Ok(())
    }

    /// Updates a cached position by applying an order fill in place.

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Call `add_position` for new positions before any `update_position` call.
  2. Guard the update with `cache.position(&position.id)` / existence check and add if absent.
  3. Re-order purge logic so positions are not purged while updates for them are still expected.

Example fix

// before
cache.update_position(&position)?;
// after
if cache.position(&position.id).is_some() {
    cache.update_position(&position)?;
} else {
    cache.add_position(position.clone());
}
Defensive patterns

Strategy: validation

Validate before calling

if cache.position(&position.id).is_none() {
    cache.add_position(position.clone()); // or skip update
} else {
    cache.update_position(&position)?;
}

Prevention

When it happens

Trigger: Calling `update_position(&position)` for a PositionId never added to the cache, or one already removed/purged (e.g. after `purge_...` of closed positions); passing a position built locally rather than one returned by the cache.

Common situations: Replaying events after the cache was purged of closed positions; restoring state from a snapshot incompletely; constructing a Position in code and trying to 'update' it before any add.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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