nautechsystems/nautilus_trader · error · anyhow::Error
fill {} maps to non-open position {position_id}
Error message
fill {} maps to non-open position {position_id} What it means
The fill was mapped to a cached position that is already closed (is_open() is false). Applying a fill to a closed position would resurrect inconsistent state, so the manager fails the report preparation.
Source
Thrown at crates/live/src/execution/manager.rs:2898
}
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,
"fill {} without a venue position ID would cross position {position_id}",
report.trade_id,
);
report.venue_position_id = Some(position_id);
Ok(PositionFillReportPreparation::Ready)
}
#[cfg(feature = "node")]
fn has_active_inferred_fill(order: &OrderAny) -> anyhow::Result<bool> {
let events = order.events();
let trade_ids = order.trade_ids();View on GitHub (pinned to 18893faf8b)
Solutions
- Deduplicate trade reports by trade_id before processing so replayed fills are ignored
- Configure reconciliation/idempotency so late duplicate fills are dropped rather than applied
- Check venue netting behavior and configure the node's account type (netting vs hedging) to match
- Review adapter report replay logic to not re-emit already-processed fills
Example fix
// before (no dedup on replay)
for report in reports { self.handle_report(report); }
// after
for report in reports {
if self.processed_trade_ids.insert(report.trade_id) {
self.handle_report(report);
}
} Defensive patterns
Strategy: validation
Validate before calling
if let Some(pos) = cache.position(&position_id) {
if !pos.is_open() {
// drop or dedupe this late fill instead of applying it
}
} Try / catch
match result {
Err(e) if e.to_string().contains("non-open position") => {
// treat as duplicate/late fill: log and discard
}
other => other?,
} Prevention
- Deduplicate reports by trade_id before processing
- Handle reconnect replays idempotently
- Match netting/hedging config to venue behavior
When it happens
Trigger: A fill arrives referencing a position_id whose cached Position already emitted a Closed event - e.g. a late/duplicate venue fill after the position was flattened, or a duplicate report delivered after the closing fill was processed.
Common situations: Late fills from the venue arriving after an offsetting order closed the position; duplicate trade reports on reconnect/replay; venues emitting fills for positions the adapter already considered closed due to netting.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- fill {} client order ID {report_client_order_id} conflicts w
- fill {} order side or venue order ID differs from cached ord
- fill {} position ID {venue_position_id} conflicts with cache
- fill {} maps to position {position_id}, which is not cached
- fill {} maps to position {position_id} with a different acco
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/6bf243b85c22a635.
Report an issue: GitHub.