nautechsystems/nautilus_trader · error · anyhow::Error

fill report start time must not exceed end time

Error message

fill report start time must not exceed end time

What it means

generate_fill_reports validates the requested window before querying /fapi/v1/userTrades: the start time (ms) must not exceed the effective end time, where end defaults to the current clock time when cmd.end is None. An inverted range would paginate meaningless windows, so the client fails fast. This mirrors guards common to all historical-report generators that take start/end pairs.

Source

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

    ) -> anyhow::Result<Vec<FillReport>> {
        let Some(instrument_id) = cmd.instrument_id else {
            log::warn!("generate_fill_reports requires instrument_id for Binance Futures");
            return Ok(Vec::new());
        };

        let symbol = format_binance_symbol(&instrument_id);
        let mut trades = Vec::new();
        let mut seen_trade_ids = AHashSet::new();
        let requested_end_time = cmd
            .end
            .map(|end| end.as_i64() / NANOSECONDS_IN_MILLISECOND as i64);

        if let Some(start) = cmd.start {
            let query_start_time = start.as_i64() / NANOSECONDS_IN_MILLISECOND as i64;
            let query_end_time = requested_end_time.unwrap_or_else(|| {
                self.clock.get_time_ns().as_i64() / NANOSECONDS_IN_MILLISECOND as i64
            });
            anyhow::ensure!(
                query_start_time <= query_end_time,
                "fill report start time must not exceed end time"
            );
            let mut window_start = query_start_time;

            loop {
                let window_end = window_start
                    .saturating_add(USER_TRADES_MAX_INTERVAL_MS)
                    .min(query_end_time);
                let mut from_id = None;

                loop {
                    let mut builder = BinanceUserTradesParamsBuilder::default();
                    builder.symbol(symbol.clone());
                    builder.limit(USER_TRADES_PAGE_LIMIT);

                    if let Some(cursor) = from_id {
                        builder.from_id(cursor);

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Order the arguments so start <= end and validate before issuing the report
  2. When end is None, ensure start is in the past relative to the host clock
  3. Normalize/clamp the range (e.g. swap if inverted) in the code that builds the report command

Example fix

// before
let report = client
    .generate_fill_reports(GenerateFillReports::new(account_id, Some(future_start), None))
    .await?;

// after
let report = client
    .generate_fill_reports(GenerateFillReports::new(account_id, Some(past_start), Some(later_end)))
    .await?;
Defensive patterns

Strategy: validation

Validate before calling

let start_ms = cmd.start.map(|t| t.as_i64() / 1_000_000);
let end_ms = cmd.end.map(|t| t.as_i64() / 1_000_000)
    .unwrap_or_else(|| clock.get_time_ns().as_i64() / 1_000_000);
if let Some(s) = start_ms {
    anyhow::ensure!(s <= end_ms, "inverted fill-report window");
}

Type guard

fn valid_report_window(start_ms: i64, end_ms: i64) -> bool {
    start_ms <= end_ms
}

Prevention

When it happens

Trigger: Issuing GenerateFillReports with start > end, or with a future start and end omitted (since end then defaults to now, any start in the future trips the guard); also start/end swapped in caller code.

Common situations: Clock skew between the caller and the client host; datetime conversions mixing timezones so start lands after end; reconciliation code passing a session window the wrong way round.

Related errors


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