nautechsystems/nautilus_trader · error
position commission currency differs for fill {}
Error message
position commission currency differs for fill {} What it means
While distributing a voided commission across the positions touched by a fill, the engine requires that every position fragment's recorded commission is denominated in the same currency as the commission being voided. If a fragment's commission currency differs from remaining_commission.currency, exact Money arithmetic would be undefined across currencies, so the engine raises this error.
Source
Thrown at crates/execution/src/engine/mod.rs:3942
.and_modify(|allocation| allocation.0 = allocation.0 + removed)
.or_insert((removed, None));
remaining_qty = remaining_qty - removed;
}
anyhow::ensure!(
remaining_qty.is_zero(),
"position fragments do not cover voided quantity for fill {}",
event.trade_id
);
if let Some(mut remaining_commission) = event.commission_voided {
for (position_id, _, _, commission) in fragments.iter().rev() {
if remaining_commission.is_zero() {
break;
}
let Some(commission) = commission else {
continue;
};
anyhow::ensure!(
commission.currency == remaining_commission.currency,
"position commission currency differs for fill {}",
event.trade_id
);
let removed_raw = remaining_commission.raw.abs().min(commission.raw.abs());
let removed = Money::from_raw(
removed_raw * remaining_commission.raw.signum(),
remaining_commission.currency,
);
allocations
.entry(*position_id)
.and_modify(|allocation| {
allocation.1 = Some(
allocation
.1
.map_or(removed, |commission| commission + removed),
);
})View on GitHub (pinned to 18893faf8b)
Solutions
- Ensure the void event's commission currency matches the currency used when the original fill's commission was booked.
- Check strategy/instrument configuration so commission_currency is consistent for all fills of the instrument.
- If commissions genuinely span currencies, split the void per currency and apply separate void events per currency.
- Log each position's recorded commission currency at fill time and compare with the void event to locate the mismatched fragment.
Example fix
// before: voiding a USD commission against a fill booked in USD fee
let voided = Money::new(2.5, Currency::USD());
engine.apply_fill_void(FillVoided { commission_voided: Some(voided), .. })?;
// after: match the currency of the recorded commission
let voided = Money::new(2.5, recorded_fill.commission.currency);
engine.apply_fill_void(FillVoided { commission_voided: Some(voided), .. })?; Defensive patterns
Strategy: validation
Validate before calling
// ensure the void commission currency matches the fill's recorded commission
if let Some(voided) = &event.commission_voided {
assert_eq!(voided.currency, recorded_fill(event.trade_id).commission.currency,
"void commission currency differs from booked commission");
} Try / catch
match engine.apply_fill_void(event) {
Err(e) if e.to_string().contains("commission currency differs") => {
log::error!("currency mismatch voiding fill {}: {e}", event.trade_id);
// re-emit the void with the fill's booked commission currency
}
other => other?,
} Prevention
- Configure one commission currency per instrument and use it for every fill and void.
- Carry commission currency end-to-end in adapters instead of assuming account currency.
- Never hard-code currencies in strategy fee calculations.
When it happens
Trigger: Applying a fill void whose commission_voided currency does not equal the currency of the commission recorded on one of the positions involved — e.g. commissions were booked in quote currency but the void event carries base-currency commission, or multi-currency instruments with mixed commission currencies per fill.
Common situations: Configuring a strategy's commission currency inconsistently between backtest and live; adapters reporting commissions in different currencies across partial fills; changing instrument or account currency config between runs while replaying cached events.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- responsible execution client is unavailable
- position fragments do not cover voided commission for fill {
- Order for {} not found to determine position ID
- Client already registered with ID {client_id}
- Venue {venue} already routed to {existing_client_id}, cannot
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/2284a3ee699f3fcb.
Report an issue: GitHub.