nautechsystems/nautilus_trader · error · anyhow::Error

Binance Futures goodTillDate must be strictly greater than…

Error message

Binance Futures goodTillDate must be strictly greater than current time plus {BINANCE_GTD_MIN_LEAD_SECS} seconds

What it means

This error is raised by determine_futures_order_lifetime in the Binance Futures execution client when a GTD (Good-Till-Date) order's expire_time is not far enough in the future. Binance requires the native goodTillDate timestamp to be strictly greater than the current time plus BINANCE_GTD_MIN_LEAD_SECS, so NautilusTrader validates this locally before submitting and rejects orders that would be immediately rejected or misinterpreted by the exchange. It is a pre-submission sanity check to avoid a guaranteed API rejection.

Solutions

  1. Set the order's expire_time (TIF GTD) to strictly more than BINANCE_GTD_MIN_LEAD_SECS seconds in the future relative to submission time.
  2. Compute expire_time as close to submit as possible, e.g. using the live clock (clock.timestamp_ns()) rather than a cached value captured earlier in the strategy.
  3. If you do not need native exchange-side expiry, disable manage_gtd_expiry / set use_gtd=false so GTD orders are downgraded to GTC with a warning instead of an error.
  4. Check machine clock synchronization (NTP) if expire_time appears correct but still fails.

Example fix

// before
let expire = self.clock.timestamp_ns() + 5_000_000_000; // 5s lead, too short
order_factory.limit(..., time_in_force=TimeInForce::Gtd, expire_time=expire);

// after
let expire = self.clock.timestamp_ns() + 120_000_000_000; // >= BINANCE_GTD_MIN_LEAD_SECS + margin
order_factory.limit(..., time_in_force=TimeInForce::Gtd, expire_time=expire);
Defensive patterns

Strategy: validation

Validate before calling

const MIN_LEAD_SECS: u64 = /* BINANCE_GTD_MIN_LEAD_SECS */;
let now_ns = clock.timestamp_ns().as_u64();
anyhow::ensure!(
    expire_ns > now_ns + MIN_LEAD_SECS * 1_000_000_000,
    "GTD expire_time must exceed now + {MIN_LEAD_SECS}s"
);

Prevention

When it happens

Trigger: Submitting a Limit/StopLimit/LimitIfTouched order with TimeInForce::Gtd and manage_gtd_expiry enabled (use_gtd=true) on USD-M Futures where expire_time is set to now, in the past, or within BINANCE_GTD_MIN_LEAD_SECS seconds of now. Also hit by validate_order paths and batch order building when a strategy computes expire_time from a stale clock value.

Common situations: A strategy sets a GTD expiry too close to 'now' (e.g. expire = now + a few seconds), backtest logic reuses a cached timestamp, clock skew between the machine and exchange time, or a config where expire_time was computed before a long preprocessing delay so it is stale by submission time.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


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

Appendix: source

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

    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 {
    order.contingency_type().is_some()
        || order
            .linked_order_ids()
            .is_some_and(|linked_order_ids| !linked_order_ids.is_empty())
}

fn binance_side_wire(side: BinanceSide) -> &'static str {

View on GitHub (pinned to 18893faf8b)