nautechsystems/nautilus_trader · error · anyhow::Error

fill {} maps to position {position_id} with a different acco

Error message

fill {} maps to position {position_id} with a different account or instrument

What it means

The cached position resolved for this fill has an account_id or instrument_id that differs from the fill report's. A fill can only be applied to a position on the same account and instrument, so the manager rejects the report as misattributed.

Source

Thrown at crates/live/src/execution/manager.rs:2892

        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,
            "fill {} without a venue position ID would cross position {position_id}",
            report.trade_id,
        );

        report.venue_position_id = Some(position_id);
        Ok(PositionFillReportPreparation::Ready)

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Scope adapter-generated position IDs by instrument (and account) so they cannot collide
  2. Fix instrument symbology mapping so the same venue position resolves to one nautilus instrument
  3. Verify account_id derivation in the adapter matches the account that owns the position
  4. Clear corrupt cache mappings and re-reconcile with venue position state

Example fix

// before (global counter)
let position_id = VenuePositionId::new(format!("{n}"));
// after (scoped)
let position_id = VenuePositionId::new(format!("{instrument}-{account}-{n}"));
Defensive patterns

Strategy: validation

Validate before calling

if let Some(pos) = cache.position(&position_id) {
    assert_eq!(pos.account_id, report.account_id);
    assert_eq!(pos.instrument_id, report.instrument_id);
}

Type guard

fn position_matches_report(pos: &Position, report: &TradeReport) -> bool {
    pos.account_id == report.account_id && pos.instrument_id == report.instrument_id
}

Prevention

When it happens

Trigger: cache.position(position_id) returns a position whose account_id != report.account_id or instrument_id != report.instrument_id. Caused by position ID collisions in a custom adapter's mapping, wrong instrument symbology, or multi-account routing errors.

Common situations: Adapters that generate position IDs without instrument/account scoping so IDs collide across instruments; an instrument mapping change mid-session; fills from a sub-account routed to a parent-account position.

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


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/b0cc0146ebf8e0e1. Report an issue: GitHub.