nautechsystems/nautilus_trader · error

GTD time in force requires expire_time parameter

Error message

GTD time in force requires expire_time parameter

What it means

Kraken spot order TIF mapping requires a GTD order to carry an expiry time. When a request specifies `TimeInForce::Gtd` but `expire_time` is `None`, this anyhow error is returned instead of producing GTD parameters. It enforces that GTD orders always include a concrete expiration.

Source

Thrown at crates/adapters/kraken/src/http/spot/client.rs:3252

        .unwrap_or_else(|| Currency::new(normalized, 2, 0, normalized, CurrencyType::Fiat))
}

fn compute_time_in_force(
    is_limit_order: bool,
    time_in_force: TimeInForce,
    expire_time: Option<UnixNanos>,
) -> anyhow::Result<(Option<String>, Option<String>)> {
    if !is_limit_order {
        return Ok((None, None));
    }

    match time_in_force {
        TimeInForce::Gtc => Ok((None, None)),
        TimeInForce::Ioc => Ok((Some("IOC".to_string()), None)),
        TimeInForce::Fok => Ok((Some("FOK".to_string()), None)),
        TimeInForce::Gtd => {
            let expire = expire_time.ok_or_else(|| {
                anyhow::anyhow!("GTD time in force requires expire_time parameter")
            })?;
            let expire_secs = expire.as_u64() / NANOSECONDS_IN_SECOND;
            Ok((Some("GTD".to_string()), Some(expire_secs.to_string())))
        }
        _ => anyhow::bail!("Unsupported time in force: {time_in_force:?}"),
    }
}

#[cfg(test)]
mod tests {
    use std::{sync::Arc, time::Duration};

    use nautilus_model::instruments::CurrencyPair;
    use rstest::rstest;

    use super::*;

    #[rstest]

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Set `expire_time` (Unix nanoseconds) on the order request when using GTD.
  2. Use `TimeInForce::Gtc` instead if the order should not expire.
  3. Ensure the order factory/strategy populates `expire_time` for all GTD orders before submission.

Example fix

// before
let tif = TimeInForce::Gtd; // expire_time: None
// after
let tif = TimeInForce::Gtd;
let expire_time = Some(UnixNanos::from(expiry_ts_unix_ns));
Defensive patterns

Strategy: validation

Validate before calling

fn validate_gtd(tif: TimeInForce, expire_time: Option<u64>) -> Result<(), String> {
    if tif == TimeInForce::Gtd && expire_time.is_none() {
        Err("GTD orders require expire_time".into())
    } else { Ok(()) }
}

Type guard

fn has_expiry(o: &OrderRequest) -> bool {
    o.time_in_force != TimeInForce::Gtd || o.expire_time.is_some()
}

Try / catch

match client.submit_order(req).await {
    Err(e) if e.to_string().contains("GTD time in force requires expire_time") => {
        let mut req = req.clone();
        req.expire_time = Some(default_expiry_unix_ns());
        client.submit_order(&req).await.map_err(Into::into)
    }
    other => other,
}

Prevention

When it happens

Trigger: Submitting or amending an order with time_in_force = GTD while the request's `expire_time` field is None/unset; converting a Nautilus GTD order whose `expire_time` was never populated.

Common situations: Strategy configured GTD without setting an expiry; order factory created GTD orders relying on defaults that don't exist; upstream code dropped expire_time when translating order requests.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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