nautechsystems/nautilus_trader · error · anyhow::Error

AX fills pagination returned duplicate trade ID

Error message

AX fills pagination returned duplicate trade ID {}

What it means

The paginator tracks trade IDs it has already seen across all pages via a HashSet; if the same trade_id appears twice, the server is re-serving rows (bad cursor advancement or overlap) and the client aborts to prevent duplicated fills in the result.

Solutions

  1. Retry the whole pagination from scratch; a transient replica mismatch often resolves.
  2. Check whether a proxy/LB is serving inconsistent replicas and pin to a single endpoint.
  3. Compare the raw JSON of the previous and current pages to confirm the overlapping trade IDs and report to the AX backend team.
  4. Upgrade the client in case cursor-advance semantics changed server-side.
Defensive patterns

Strategy: try-catch

Try / catch

match client.paginate_fills(params).await {
    Ok(fills) => apply(fills),
    Err(e) if e.to_string().contains("duplicate trade ID") => {
        warn!("AX served overlapping pages; restarting pagination");
        client.paginate_fills(params).await
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Any fills page iteration where a response contains a fill whose trade_id was already collected from an earlier page — duplicate delivery caused by server-side cursor overlap or a cursor that advanced incorrectly.

Common situations: Server cursor semantics changed after a backend deploy; a load balancer routes page requests to different replicas with divergent snapshots; retry logic re-delivers the tail of the previous page.

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

Appendix: source

Thrown at crates/adapters/architect_ax/src/http/client.rs:2258

            if let Some(total_count) = response.total_count {
                anyhow::ensure!(
                    total_count >= 0,
                    "AX fills total_count must be non-negative, was {total_count}"
                );

                if let Some(expected) = expected_total {
                    anyhow::ensure!(
                        total_count == expected,
                        "AX fills total_count changed during pagination: expected {expected}, was {total_count}"
                    );
                } else {
                    expected_total = Some(total_count);
                }
            }

            for fill in response.fills {
                anyhow::ensure!(
                    seen_trade_ids.insert(fill.trade_id.clone()),
                    "AX fills pagination returned duplicate trade ID {}",
                    fill.trade_id
                );
                fills.push(fill);
            }

            if let Some(total_count) = expected_total {
                anyhow::ensure!(
                    fills.len() as i64 <= total_count,
                    "AX fills pagination returned more unique rows ({}) than total_count {total_count}",
                    fills.len()
                );
            }

            match response.next_cursor {
                Some(next_cursor) => {
                    anyhow::ensure!(

View on GitHub (pinned to 18893faf8b)