nautechsystems/nautilus_trader · error

start must not be later than end

Error message

start must not be later than end

What it means

request_trade_ticks validates its time range up front: when both start and end timestamps are provided, start must not exceed end. An inverted range would produce an empty or nonsensical pagination window, so the call is rejected immediately.

Source

Thrown at crates/adapters/polymarket/src/http/data_api.rs:407

    #[expect(clippy::too_many_arguments)]
    pub async fn request_trade_ticks(
        &self,
        instrument_id: InstrumentId,
        condition_id: &str,
        token_id: &str,
        price_precision: u8,
        size_precision: u8,
        start: Option<UnixNanos>,
        end: Option<UnixNanos>,
        limit: Option<u32>,
    ) -> anyhow::Result<Vec<TradeTick>> {
        const PAGE_SIZE: u32 = 500;
        const MAX_OFFSET: u32 = 10_000;

        if let (Some(start), Some(end)) = (start, end)
            && start > end
        {
            anyhow::bail!("start must not be later than end");
        }

        if limit == Some(0) {
            anyhow::bail!("limit must be greater than zero");
        }

        let start_secs = start.map(|value| (value.as_u64() / 1_000_000_000) as i64);
        let end_secs = end.map(|value| (value.as_u64() / 1_000_000_000) as i64);
        let protocol = OffsetProtocol::new(
            "/trades",
            PAGE_SIZE as usize,
            trade_page_fingerprint,
            Some((
                MAX_OFFSET,
                TradeTickStop::VenueOffsetCeiling(OffsetCeilingSource::Local),
            )),
        );
        let reducer = TradeTickReducer {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Swap or recompute the arguments so start <= end.
  2. Check timestamp units: the API takes Unix milliseconds (nanoseconds are converted internally); ensure both bounds use the same unit.
  3. When start and end are computed at different times, capture 'now' once and derive both bounds from it.
  4. Guard the call site with an assertion before invoking the API.

Example fix

// before
let (start, end) = (end_ts, start_ts); // swapped
api.request_trade_ticks(Some(cid), Some(start), Some(end), None).await?;
// after
assert!(start_ts <= end_ts);
api.request_trade_ticks(Some(cid), Some(start_ts), Some(end_ts), None).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust: validate the range before calling
fn valid_range(start: Option<u64>, end: Option<u64>) -> bool {
    matches!((start, end), (None, _) | (_, None) | (Some(s), Some(e)) if s <= e)
}
assert!(valid_range(start_ts, end_ts));

Type guard

fn ordered_range(start: u64, end: u64) -> Option<(u64, u64)> {
    (start <= end).then_some((start, end))
}

Prevention

When it happens

Trigger: Calling request_trade_ticks with a start Unix-ms timestamp strictly greater than the end timestamp (e.g. (Some(start), Some(end)) where start > end).

Common situations: Swapped argument order at the call site; computing end from a 'now' variable captured before start in async code; unit mix-ups (seconds vs milliseconds) making start appear later; translating inclusive/exclusive bounds incorrectly across systems.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/971c8e017f7a90dc. Report an issue: GitHub.