nautechsystems/nautilus_trader · error · anyhow::Error

Binance user-trades pagination made no progress

Error message

Binance user-trades pagination made no progress

What it means

While paginating /fapi/v1/userTrades with fromId = max_trade_id + 1, the client asserts that the new cursor strictly exceeds the previous one, guaranteeing forward progress. The guard fires when a full 1000-trade page (USER_TRADES_PAGE_LIMIT) that did not pass the window end yields a next cursor no greater than the current one — i.e. the exchange returned a page that cannot advance the query — and it aborts the whole fill-report generation rather than looping forever.

Source

Thrown at crates/adapters/binance/src/futures/execution.rs:2041

                    let page_len = page.len();
                    let max_trade_id = page.iter().map(|trade| trade.id).max().unwrap();
                    let passed_window_end = page.iter().any(|trade| trade.time > window_end);

                    trades.extend(page.into_iter().filter(|trade| {
                        trade.time >= window_start
                            && trade.time <= window_end
                            && seen_trade_ids.insert(trade.id)
                    }));

                    if page_len < USER_TRADES_PAGE_LIMIT as usize || passed_window_end {
                        break;
                    }

                    let next_from_id = max_trade_id
                        .checked_add(1)
                        .context("Binance user trade ID overflow during pagination")?;
                    anyhow::ensure!(
                        from_id.is_none_or(|cursor| next_from_id > cursor),
                        "Binance user-trades pagination made no progress"
                    );
                    from_id = Some(next_from_id);
                }

                if window_end >= query_end_time {
                    break;
                }
                window_start = window_end.saturating_add(1);
            }
        } else {
            let mut from_id = 0;

            loop {
                let params = BinanceUserTradesParamsBuilder::default()
                    .symbol(symbol.clone())
                    .from_id(from_id)

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Retry the fill-report request — a transient API anomaly is the most common cause
  2. Narrow the start/end window so pages are smaller than the 1000-trade limit, changing pagination behavior
  3. If reproducible, capture the request params (symbol, fromId, window) and report it to the NautilusTrader maintainers as an adapter/exchange anomaly
Defensive patterns

Strategy: retry

Validate before calling

// nothing to validate client-side; the guard depends on exchange responses.
// mitigate by bounding windows so pages rarely hit the 1000-trade limit:
let window_ms = 24 * 60 * 60 * 1_000; // 1 day instead of the 7-day max interval

Try / catch

let mut attempts = 0u32;
loop {
    match client.generate_fill_reports(cmd.clone()).await {
        Ok(reports) => break reports,
        Err(e) if e.to_string().contains("pagination made no progress") && attempts < 3 => {
            attempts += 1;
            tokio::time::sleep(std::time::Duration::from_secs(2u64.pow(attempts))).await;
        }
        Err(e) => return Err(e),
    }
}

Prevention

When it happens

Trigger: The API (or an intermediary cache) repeatedly returning the identical full page for the same fromId; a page whose maximum trade id fails to advance max_trade_id + 1 beyond the previous cursor; only reachable when page_len == 1000 and no trade's time exceeded window_end.

Common situations: A caching proxy in front of api.binance.com serving stale identical pages; an exchange-side behavioral change in fromId semantics; pathological data with duplicated trade ids.

Related errors


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