nautechsystems/nautilus_trader · error · anyhow::Error

instrument metadata does not match fill group

Error message

instrument metadata does not match fill group

What it means

create_orphan_fill_order_report receives the InstrumentAny whose metadata (precision, size precision, etc.) will be used to build the synthetic OrderStatusReport. This ensure! verifies that instrument.id() matches the instrument_id carried by the fill group; a mismatch means the wrong instrument definition was looked up for these fills, so the report cannot be constructed safely.

Solutions

  1. Verify the instrument lookup used for reconciliation resolves exactly the fill's instrument_id (compare instrument.id() with fill.instrument_id before calling).
  2. Reload the instrument definitions from the venue so cached instrument IDs match the IDs in current fill reports.
  3. Check for symbol aliasing or venue-name mismatches between the adapter's instrument IDs and those in your cache/catalog.
  4. If loading instruments from a saved catalog, regenerate them for the current venue and version instead of reusing stale files.

Example fix

// before: fetched by assumed symbol
let instrument = cache.instrument(&assumed_id)?;
// after: resolve by the fill's own instrument ID
let instrument = cache.instrument(&first.instrument_id)
    .ok_or_else(|| anyhow::anyhow!("instrument {} not in cache", first.instrument_id))?;
Defensive patterns

Strategy: validation

Validate before calling

assert_eq!(instrument.id(), first.instrument_id, "instrument {} does not match fill group instrument {}", instrument.id(), first.instrument_id);

Type guard

fn instrument_matches(instrument: &InstrumentAny, first: &FillReport) -> bool {
    instrument.id() == first.instrument_id
}

Prevention

When it happens

Trigger: During reconciliation, the instrument passed to create_orphan_fill_order_report was resolved from the cache or reconciliation_instrument_ids by a different instrument_id than the one on the FillReport group — e.g. a stale/aliased instrument definition or a cache keyed by the wrong ID.

Common situations: Instrument definitions reloaded with a changed symbol/venue after a venue rename; loading instruments for the wrong venue into the cache; symbol aliasing differences (e.g. perpetual suffixes) between the adapter and the cached instrument; replaying historical data with old instrument IDs.

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


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

Appendix: source

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

            anyhow::ensure!(
                fill.venue_order_id == first.venue_order_id,
                "venue order ID differs across fill group"
            );
            anyhow::ensure!(
                fill.client_order_id == first.client_order_id,
                "client order ID differs across fill group"
            );
            anyhow::ensure!(
                fill.order_side == first.order_side,
                "order side differs across fill group"
            );
            anyhow::ensure!(
                fill.venue_position_id == first.venue_position_id,
                "venue position ID differs across fill group"
            );
        }

        anyhow::ensure!(
            first.instrument_id == instrument.id(),
            "instrument metadata does not match fill group"
        );

        let (quantity, notional) = fills.iter().try_fold(
            (Decimal::ZERO, Decimal::ZERO),
            |(quantity, notional), fill| {
                let fill_quantity = fill.last_qty.as_decimal();
                let quantity = quantity.checked_add(fill_quantity).ok_or_else(|| {
                    anyhow::anyhow!("fill quantity overflow while aggregating fill group")
                })?;

                let fill_notional = fill_quantity
                    .checked_mul(fill.last_px.as_decimal())
                    .ok_or_else(|| {
                        anyhow::anyhow!("fill notional overflow while aggregating fill group")
                    })?;

View on GitHub (pinned to 18893faf8b)