{"record":{"id":"ade768c2271b7f4a","repo":"nautechsystems/nautilus_trader","slug":"duplicate-fill-event-for-position-position-id","errorCode":null,"errorMessage":"Duplicate fill event for position {position_id}: {trade_id}","messagePattern":"Duplicate fill event for position (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/infrastructure/src/redis/queries.rs","lineNumber":954,"sourceCode":"            .map(|payload| Self::deserialize_payload(encoding, payload))\n            .collect::<anyhow::Result<_>>()?;\n        let Some((first_fill, remaining_fills)) = fills.split_first() else {\n            return Ok(None);\n        };\n        let Some(instrument) =\n            Self::load_instrument(con, trader_key, &first_fill.instrument_id, encoding).await?\n        else {\n            log::error!(\n                \"Instrument not found for position {position_id}: {}\",\n                first_fill.instrument_id\n            );\n            return Ok(None);\n        };\n\n        let mut position = Position::new(&instrument, first_fill.clone());\n        for fill in remaining_fills {\n            if position.trade_ids().contains(&fill.trade_id) {\n                anyhow::bail!(\n                    \"Duplicate fill event for position {position_id}: {}\",\n                    fill.trade_id\n                );\n            }\n            position.apply(fill);\n        }\n\n        Ok(Some(position))\n    }\n\n    fn get_collection_key(key: &str) -> anyhow::Result<&str> {\n        key.split_once(REDIS_DELIMITER)\n            .map(|(collection, _)| collection)\n            .ok_or_else(|| {\n                anyhow::anyhow!(\"Invalid `key`, missing a '{REDIS_DELIMITER}' delimiter, was {key}\")\n            })\n    }\n","sourceCodeStart":936,"sourceCodeEnd":972,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/infrastructure/src/redis/queries.rs#L936-L972","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n// duplicate fills persisted: LOADS fill twice, load_position bails\n// after\nif !position.trade_ids().contains(&fill.trade_id) {\n    cache.add_order_fill(&fill)?;\n}","handlingStrategy":"try-catch","validationCode":"// before load, scan cached fills for duplicate trade_ids\nlet ids: HashSet<&str> = fills.iter().map(|f| f.trade_id.as_str()).collect();\nif ids.len() != fills.len() { /* duplicates present — rebuild cache */ }","typeGuard":null,"tryCatchPattern":"match cache.load_position(&position_id) {\n    Ok(p) => p,\n    Err(e) if e.to_string().contains(\"Duplicate fill event\") => {\n        log::error!(\"cache corrupted for {position_id}; rebuilding from events\");\n        rebuild_position_from_events(&position_id)?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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"],"tags":["redis","cache","position","duplicate-data","data-corruption"],"backgroundTag":"internal-invariant-violation","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}