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
- Run reconciliation on startup so existing venue positions are loaded into the cache before fills are processed
- Enable a persistent cache backend and ensure it retains open positions across restarts
- Delay/queue fill processing until position reconciliation completes
- 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
- Run reconciliation at node startup before consuming reports
- Use a persistent cache backend that survives restarts
- Avoid manual cache flushes while positions are open
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
- Cannot update position {}: not found in cache
- Cannot update position {position_id}: not found in cache
- Duplicate position fill
- Cannot calculate inverse points: open price is not positive
- Cannot calculate inverse points: close price is not positive
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/96ceb3dd038e3433.
Report an issue: GitHub.