nautechsystems/nautilus_trader · error · anyhow::Error

Binance Futures goodTillDate must be smaller than {BINANCE_G

Error message

Binance Futures goodTillDate must be smaller than {BINANCE_GTD_MAX_MILLIS}

What it means

Binance caps goodTillDate below the maximum representable exchange timestamp: the millisecond value must stay under BINANCE_GTD_MAX_MILLIS = 253_402_300_799_000 (execution.rs:141), i.e. before 9999-12-31T23:59:59Z. The client converts the whole-second expiry to milliseconds and rejects anything at or beyond the cap before sending, avoiding a venue-side parse failure.

Source

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

    let expire_time = expire_time.context("Binance Futures GTD requires an expire_time")?;
    let expire_ns = expire_time.as_u64();
    anyhow::ensure!(
        expire_ns.is_multiple_of(NANOSECONDS_IN_SECOND),
        "Binance Futures goodTillDate requires whole-second precision"
    );

    let minimum_ns = ts_now
        .as_u64()
        .checked_add(BINANCE_GTD_MIN_LEAD_SECS * NANOSECONDS_IN_SECOND)
        .context("Binance Futures GTD minimum timestamp overflow")?;
    anyhow::ensure!(
        expire_ns > minimum_ns,
        "Binance Futures goodTillDate must be strictly greater than current time plus {BINANCE_GTD_MIN_LEAD_SECS} seconds"
    );

    let good_till_date = expire_ns / NANOSECONDS_IN_MILLISECOND;
    anyhow::ensure!(
        good_till_date < BINANCE_GTD_MAX_MILLIS,
        "Binance Futures goodTillDate must be smaller than {BINANCE_GTD_MAX_MILLIS}"
    );

    Ok(FuturesOrderLifetime {
        time_in_force,
        good_till_date: Some(i64::try_from(good_till_date)?),
    })
}

fn is_grouped_order(order: &OrderAny) -> bool {
    matches!(
        order.contingency_type(),
        Some(contingency_type) if contingency_type != ContingencyType::NoContingency
    ) || order
        .linked_order_ids()
        .is_some_and(|linked_order_ids| !linked_order_ids.is_empty())
}

View on GitHub (pinned to a4b06ed870)

Solutions

  1. Use a realistic expiry date (same day/session horizon) instead of a far-future sentinel
  2. For 'no expiry', submit GTC rather than a maximal goodTillDate
  3. Verify the expire_time is in nanoseconds and within ordinary date ranges before submission

Example fix

// before
let expire = UnixNanos::from(4_102_444_800_000 * 1_000_000_000); // ms value treated as seconds -> year ~130000

// after
let expire = UnixNanos::from(1_767_225_600 * 1_000_000_000); // 2026-01-01T00:00:00Z in ns
Defensive patterns

Strategy: validation

Validate before calling

const MAX_GTD_MS: u64 = 253_402_300_799_000; // 9999-12-31T23:59:59Z
let gtd_ms = expire.as_u64() / 1_000_000;
anyhow::ensure!(gtd_ms < MAX_GTD_MS, "goodTillDate beyond exchange maximum");

Type guard

fn within_gtd_max(expire_ns: u64) -> bool {
    expire_ns / 1_000_000 < 253_402_300_799_000
}

Prevention

When it happens

Trigger: Submitting a GTD order whose expire_time corresponds to a year >= 10000 — e.g. an expiry accidentally built in microseconds/milliseconds instead of nanoseconds (making it astronomically large), or a sentinel 'far future' value like u64::MAX/i64::MAX passed as the expiry.

Common situations: Unit confusion when constructing UnixNanos from ms/us values; 'never expire' sentinels expressed as max integers instead of GTC; date libraries defaulting to far-future dates on parse errors.

Related errors


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