nautechsystems/nautilus_trader · error · anyhow::Error

AX fills total_count changed during pagination: expected {ex

Error message

AX fills total_count changed during pagination: expected {expected}, was {total_count}

What it means

The client pins total_count from the first /fills page and requires every subsequent page in the cursor chain to report the identical value. A changed total_count means the pages no longer form a consistent set (rows were added, removed, or restated mid-traversal), so the traversal aborts. Fills are fetched newest-first, so live executions during pagination are the classic cause.

Source

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

            if let Some(limit) = response.limit {
                anyhow::ensure!(
                    (0..=PAGE_SIZE).contains(&limit),
                    "AX fills applied limit must be between 0 and {PAGE_SIZE}, was {limit}"
                );
                anyhow::ensure!(
                    page_len <= limit as usize,
                    "AX fills page length {page_len} exceeds applied limit {limit}"
                );
            }

            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 {

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Retry the full request_fill_reports call — it usually converges once trading pauses
  2. Anchor the query with an explicit start and end (both within the 7-day AX cap) taken before you begin, and prefer a window that is already historical
  3. Narrow the time window so the cursor chain is shorter and less exposed to concurrent inserts
  4. If it reproduces on a static, closed window, capture the raw page sequence with curl and report to AX — the total is being mutated server-side

Example fix

// before — open-ended window, races with live fills
let reports = client.request_fill_reports(account_id, None, None).await?;
// after — fixed historical window + retry
let end = clock.timestamp_ns();
let start = end - 6 * 24 * 60 * 60 * 1_000_000_000;
let reports = retry_on_pagination_error(3, || {
    client.request_fill_reports(account_id, Some(start), Some(end))
}).await?;
Defensive patterns

Strategy: retry

Try / catch

for attempt in 0..3 {
    match client.request_fill_reports(account_id, Some(start), Some(end)).await {
        Ok(reports) => return Ok(reports),
        Err(e) if e.to_string().contains("total_count changed during pagination") => {
            log::warn!("fills total drifted (attempt {}); backing off", attempt + 1);
            tokio::time::sleep(Duration::from_secs(2u64.pow(attempt))).await;
        }
        Err(e) => return Err(e.into()),
    }
}
bail!("fills pagination unstable after retries");

Prevention

When it happens

Trigger: request_fill_reports runs while the account is actively trading: a new fill lands between fetching page N and page N+1 and AX reports an incremented total_count. Also triggered when AX corrects or restates historical rows during the traversal.

Common situations: Running fills reconciliation (e.g. report/generation tasks) concurrently with live strategy execution; two strategy instances pulling fills for the same account; passing end=None so the window extends to call time while fills keep arriving.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16). Data as JSON: /api/errors/ff1b36cf092f1d43. Report an issue: GitHub.