nautechsystems/nautilus_trader · error

Lighter GTD expire_time must be no more than 30 days from no

Error message

Lighter GTD expire_time must be no more than 30 days from now

What it means

Lighter imposes venue-side limits on GTD (Good-Til-Date) order expiry timestamps. The adapter validates that expire_time is at least 5 minutes from now (plus a 1s transport margin) and at most 30 days out. This error fires when expiry_ms exceeds the 30-day maximum computed from current time.

Source

Thrown at crates/adapters/lighter/src/websocket/dispatch.rs:2072

    tif: &TimeInForce,
    expire_time: Option<UnixNanos>,
    now_ms: i64,
) -> anyhow::Result<i64> {
    if order_type == OrderType::Market {
        return Ok(ORDER_EXPIRY_IOC);
    }

    if matches!(tif, TimeInForce::Gtd)
        && let Some(ts) = expire_time
    {
        let expiry_ms = (ts.as_u64() / 1_000_000) as i64;
        let min_expiry_ms = now_ms.saturating_add(ORDER_EXPIRY_MIN_GTD_MS);
        let max_expiry_ms = now_ms.saturating_add(ORDER_EXPIRY_MAX_GTD_MS);
        anyhow::ensure!(
            expiry_ms >= min_expiry_ms,
            "Lighter GTD expire_time must be at least 5 minutes from now (plus 1 second transport margin)",
        );
        anyhow::ensure!(
            expiry_ms <= max_expiry_ms,
            "Lighter GTD expire_time must be no more than 30 days from now",
        );
        return Ok(expiry_ms);
    }

    if is_conditional_order(order_type) && matches!(tif, TimeInForce::Ioc) {
        return Ok(now_ms.saturating_add(ORDER_EXPIRY_DEFAULT_GTC_MS));
    }

    if matches!(tif, TimeInForce::Ioc | TimeInForce::Fok) {
        return Ok(ORDER_EXPIRY_IOC);
    }

    Ok(now_ms.saturating_add(ORDER_EXPIRY_DEFAULT_GTC_MS))
}

fn is_conditional_market_order(order_type: OrderType) -> bool {

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Reduce the GTD expire_time to 30 days from now or less
  2. If a longer horizon is needed, use a non-GTD time-in-force (e.g. GTC) or re-issue/extend the order periodically within 30 days
  3. Check any expire-time configuration for ms-vs-s or other unit errors inflating the value

Example fix

// before
let expire_time = now_ms + 60 * 24 * 3600 * 1000; // 60 days
// after
let expire_time = now_ms + 30 * 24 * 3600 * 1000; // 30 days (venue max)
Defensive patterns

Strategy: validation

Validate before calling

let max_expiry = now_ms + ORDER_EXPIRY_MAX_GTD_MS;
let min_expiry = now_ms + ORDER_EXPIRY_MIN_GTD_MS;
anyhow::ensure!(expiry_ms >= min_expiry && expiry_ms <= max_expiry, "GTD expiry out of venue range");

Type guard

fn is_valid_gtd_expiry(expiry_ms: i64, now_ms: i64) -> bool {
    expiry_ms >= now_ms + ORDER_EXPIRY_MIN_GTD_MS && expiry_ms <= now_ms + ORDER_EXPIRY_MAX_GTD_MS
}

Try / catch

match client.submit_order(order) {
    Err(e) if e.to_string().contains("no more than 30 days") => {
        // clamp expiry to 30 days and resubmit
    }
    other => other?,
}

Prevention

When it happens

Trigger: Submitting or modifying a GTD order whose expire_time is more than ORDER_EXPIRY_MAX_GTD_MS (30 days) in the future, e.g. a strategy passing a 60-day expiry or a hardcoded far-future timestamp.

Common situations: Configured order TTL longer than the venue permits; default strategy time_in_expiry values ported from another adapter (e.g. Binance allows longer); unit confusion (ms vs seconds) inflating the timestamp.

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/32b1e6ed8fa80729. Report an issue: GitHub.