{"record":{"id":"4ea319c15ee24666","repo":"nautechsystems/nautilus_trader","slug":"binance-user-trades-pagination-made-no-progress","errorCode":null,"errorMessage":"Binance user-trades pagination made no progress","messagePattern":"Binance user-trades pagination made no progress","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/binance/src/futures/execution.rs","lineNumber":2041,"sourceCode":"\n                    let page_len = page.len();\n                    let max_trade_id = page.iter().map(|trade| trade.id).max().unwrap();\n                    let passed_window_end = page.iter().any(|trade| trade.time > window_end);\n\n                    trades.extend(page.into_iter().filter(|trade| {\n                        trade.time >= window_start\n                            && trade.time <= window_end\n                            && seen_trade_ids.insert(trade.id)\n                    }));\n\n                    if page_len < USER_TRADES_PAGE_LIMIT as usize || passed_window_end {\n                        break;\n                    }\n\n                    let next_from_id = max_trade_id\n                        .checked_add(1)\n                        .context(\"Binance user trade ID overflow during pagination\")?;\n                    anyhow::ensure!(\n                        from_id.is_none_or(|cursor| next_from_id > cursor),\n                        \"Binance user-trades pagination made no progress\"\n                    );\n                    from_id = Some(next_from_id);\n                }\n\n                if window_end >= query_end_time {\n                    break;\n                }\n                window_start = window_end.saturating_add(1);\n            }\n        } else {\n            let mut from_id = 0;\n\n            loop {\n                let params = BinanceUserTradesParamsBuilder::default()\n                    .symbol(symbol.clone())\n                    .from_id(from_id)","sourceCodeStart":2023,"sourceCodeEnd":2059,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/a4b06ed870971b5671d12754ea138a3ab99b1dec/crates/adapters/binance/src/futures/execution.rs#L2023-L2059","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// nothing to validate client-side; the guard depends on exchange responses.\n// mitigate by bounding windows so pages rarely hit the 1000-trade limit:\nlet window_ms = 24 * 60 * 60 * 1_000; // 1 day instead of the 7-day max interval","typeGuard":null,"tryCatchPattern":"let mut attempts = 0u32;\nloop {\n    match client.generate_fill_reports(cmd.clone()).await {\n        Ok(reports) => break reports,\n        Err(e) if e.to_string().contains(\"pagination made no progress\") && attempts < 3 => {\n            attempts += 1;\n            tokio::time::sleep(std::time::Duration::from_secs(2u64.pow(attempts))).await;\n        }\n        Err(e) => return Err(e),\n    }\n}","preventionTips":["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"],"tags":["binance-futures","user-trades","pagination","fill-reports","infinite-loop-guard","rust"],"backgroundTag":"api-pagination-stuck","analyzedSha":"a4b06ed870971b5671d12754ea138a3ab99b1dec","analyzedAt":"2026-08-16T22:54:50.089Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}