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
- Retry the fill-report request — a transient API anomaly is the most common cause
- Narrow the start/end window so pages are smaller than the 1000-trade limit, changing pagination behavior
- 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
- Chunk large fill-history requests into daily windows
- Avoid caching proxies between the client and api.binance.com
- Alert on repeated pagination-stall errors — persistent hits indicate an exchange/API change needing an adapter update
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
- fill report start time must not exceed end time
- Binance Spot account-trades pagination made no progress
- Binance historical bars require time aggregation
- invalid Binance Futures order-book depth {depth}; valid valu
- Binance Futures ticker custom data requires BINANCE venue in
AI-assisted analysis of nautechsystems/nautilus_trader@a4b06ed870 (2026-08-16).
Data as JSON: /api/errors/4ea319c15ee24666.
Report an issue: GitHub.