nautechsystems/nautilus_trader · error
Duplicate fill event for position {position_id}: {trade_id}
Error message
Duplicate fill event for position {position_id}: {trade_id} What it means
While rebuilding a Position from cached fill (order filled) events, the loader detected a trade_id that the position has already applied. Applying the same fill twice would corrupt position state (double-counted quantity/realized PnL), so the loader bails instead. It indicates corrupted or duplicated cache data for that position.
Source
Thrown at crates/infrastructure/src/redis/queries.rs:954
.map(|payload| Self::deserialize_payload(encoding, payload))
.collect::<anyhow::Result<_>>()?;
let Some((first_fill, remaining_fills)) = fills.split_first() else {
return Ok(None);
};
let Some(instrument) =
Self::load_instrument(con, trader_key, &first_fill.instrument_id, encoding).await?
else {
log::error!(
"Instrument not found for position {position_id}: {}",
first_fill.instrument_id
);
return Ok(None);
};
let mut position = Position::new(&instrument, first_fill.clone());
for fill in remaining_fills {
if position.trade_ids().contains(&fill.trade_id) {
anyhow::bail!(
"Duplicate fill event for position {position_id}: {}",
fill.trade_id
);
}
position.apply(fill);
}
Ok(Some(position))
}
fn get_collection_key(key: &str) -> anyhow::Result<&str> {
key.split_once(REDIS_DELIMITER)
.map(|(collection, _)| collection)
.ok_or_else(|| {
anyhow::anyhow!("Invalid `key`, missing a '{REDIS_DELIMITER}' delimiter, was {key}")
})
}
View on GitHub (pinned to 18893faf8b)
Solutions
- Inspect the cached fills list for the position in Redis and remove the duplicate trade_id entries
- Verify only one trader instance/process is writing to that cache database (check instance_id / trader_id separation)
- Clear and rebuild the cache for that trader from the event stream so fills are persisted exactly once
- If this recurs, add a dedup guard before persisting fills to the cache
Example fix
// before
// duplicate fills persisted: LOADS fill twice, load_position bails
// after
if !position.trade_ids().contains(&fill.trade_id) {
cache.add_order_fill(&fill)?;
} Defensive patterns
Strategy: try-catch
Validate before calling
// before load, scan cached fills for duplicate trade_ids
let ids: HashSet<&str> = fills.iter().map(|f| f.trade_id.as_str()).collect();
if ids.len() != fills.len() { /* duplicates present — rebuild cache */ } Try / catch
match cache.load_position(&position_id) {
Ok(p) => p,
Err(e) if e.to_string().contains("Duplicate fill event") => {
log::error!("cache corrupted for {position_id}; rebuilding from events");
rebuild_position_from_events(&position_id)?
}
Err(e) => return Err(e),
} Prevention
- Run a single trader instance per cache database/instance_id
- Use atomic/pipeline writes so fills are appended exactly once
- Validate cache contents after snapshot restore or manual merges
When it happens
Trigger: Calling load_position for a position whose cached fill events contain the same trade_id more than once — e.g. duplicate writes to the Redis list, a snapshot restored over live data, or replaying events into an already-built position.
Common situations: Redis cache keys shared by two trader instances writing the same fills; a crash mid-write causing partial duplicate entries; manually copying/merging cache snapshots and duplicating fill entries.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Cannot persist position with no events: {}
- Cannot update position {}: not found in cache
- Cannot update position {position_id}: not found in cache
- Failed to send to channel: {e}
- Unsupported operation: `insert` for collection '{collection}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/ade768c2271b7f4a.
Report an issue: GitHub.