nautechsystems/nautilus_trader · error · anyhow::Error

fill {} maps to position {position_id}, which is not cached

Error message

fill {} maps to position {position_id}, which is not cached

What it means

The fill's position was resolved via the order/venue mapping to position_id, but that position is absent from the cache. Since fills must be applied to the cached Position to update it, the manager returns an error instead of silently dropping or fabricating position state.

Source

Thrown at crates/live/src/execution/manager.rs:2886

        if let Some(order) = order
            && Self::has_active_inferred_fill(&order)?
        {
            return Ok(PositionFillReportPreparation::InferredOverlap);
        }

        if !hedge_context {
            return Ok(PositionFillReportPreparation::Ready);
        }

        if report.venue_position_id.is_some() {
            return Ok(PositionFillReportPreparation::Ready);
        }

        let Some(position_id) = mapped_position_id else {
            return Ok(PositionFillReportPreparation::Unattributed);
        };
        let position = cache.position(&position_id).ok_or_else(|| {
            anyhow::anyhow!(
                "fill {} maps to position {position_id}, which is not cached",
                report.trade_id,
            )
        })?;

        anyhow::ensure!(
            position.account_id == report.account_id
                && position.instrument_id == report.instrument_id,
            "fill {} maps to position {position_id} with a different account or instrument",
            report.trade_id,
        );
        anyhow::ensure!(
            position.is_open(),
            "fill {} maps to non-open position {position_id}",
            report.trade_id,
        );
        anyhow::ensure!(
            !position.is_opposite_side(report.order_side) || report.last_qty <= position.quantity,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Run reconciliation on startup so existing venue positions are loaded into the cache before fills are processed
  2. Enable a persistent cache backend and ensure it retains open positions across restarts
  3. Delay/queue fill processing until position reconciliation completes
  4. Check cache flush/eviction settings so open positions are not purged

Example fix

// before (node starts trading immediately)
let node = TradingNode::build(config)?;
node.run()?;
// after (reconcile first)
let node = TradingNode::build(config)?;
node.reconcile_execution_state().await?;
node.run()?;
Defensive patterns

Strategy: validation

Validate before calling

if let Some(pid) = mapped_position_id {
    if cache.position(&pid).is_none() {
        // reconcile or skip before processing the fill
    }
}

Try / catch

match result {
    Err(e) if e.to_string().contains("which is not cached") => {
        // trigger execution reconciliation, then requeue the report
    }
    other => other?,
}

Prevention

When it happens

Trigger: mapped_position_id is Some(position_id) but cache.position(&position_id) returns None - e.g. the position was cleared from cache (position closed and purged, cache reset, or a fresh process that received fills before reconciling existing positions).

Common situations: Restarting a live node with a non-persistent cache while the venue still has open positions; cache backends (Redis/Postgres) dropping entries; reconciliation not run before processing fills; positions flushed after close while late fills still arrive.

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/96ceb3dd038e3433. Report an issue: GitHub.