nautechsystems/nautilus_trader · error · anyhow::Error

order side differs across fill group

Error message

order side differs across fill group

What it means

When synthesizing an OrderStatusReport from a group of FillReports, the resulting report represents a single order with one side. This ensure! checks that every fill in the group has the same order_side; conflicting sides within one group means the fills cannot describe one coherent order and reconciliation aborts.

Source

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

        for fill in fills.iter().skip(1) {
            anyhow::ensure!(
                fill.account_id == first.account_id,
                "account ID differs across fill group"
            );
            anyhow::ensure!(
                fill.instrument_id == first.instrument_id,
                "instrument ID differs across fill group"
            );
            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();

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Group fills by the full order key (account, instrument, venue_order_id, client_order_id, order_side) instead of instrument or position alone.
  2. Check the adapter's fill-report parsing to confirm order_side is read from the correct venue field and not defaulted or inferred inconsistently.
  3. Split mixed-side groups into separate orphan orders, one per side, before calling create_orphan_fill_order_report.
  4. Validate the venue's raw execution reports: if the venue itself reports conflicting sides for one order ID, capture that payload and report it to the adapter maintainers.

Example fix

// before: key = (account, instrument)
let key = (account_id, instrument_id);
// after: include side in the group key
let key = (account_id, instrument_id, venue_order_id, order_side);
Defensive patterns

Strategy: validation

Validate before calling

let sides: std::collections::HashSet<_> = fills.iter().map(|f| f.order_side).collect();
assert!(sides.len() == 1, "fill group has mixed order sides: {:?}", sides);

Type guard

fn single_side_group(fills: &[&FillReport]) -> bool {
    fills.iter().all(|f| f.order_side == fills[0].order_side)
}

Prevention

When it happens

Trigger: Two or more FillReports grouped as belonging to the same order (same account, instrument, venue/client order ID) carry different OrderSide values (e.g. one BUY and one SELL), such as when closes of opposite-side positions are mis-grouped together.

Common situations: Hedged-mode venues where opposite-side fills share a position but belong to different orders; adapter grouping fills by account+instrument instead of by order; corrupted or hand-edited replayed fill data.

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/cb52925784bf02bc. Report an issue: GitHub.