{"record":{"id":"4311c076dd2f38c0","repo":"nautechsystems/nautilus_trader","slug":"gtd-time-in-force-requires-expire-time-parameter-4311c0","errorCode":null,"errorMessage":"GTD time in force requires expire_time parameter","messagePattern":"GTD time in force requires expire_time parameter","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/adapters/kraken/src/http/spot/client.rs","lineNumber":3252,"sourceCode":"        .unwrap_or_else(|| Currency::new(normalized, 2, 0, normalized, CurrencyType::Fiat))\n}\n\nfn compute_time_in_force(\n    is_limit_order: bool,\n    time_in_force: TimeInForce,\n    expire_time: Option<UnixNanos>,\n) -> anyhow::Result<(Option<String>, Option<String>)> {\n    if !is_limit_order {\n        return Ok((None, None));\n    }\n\n    match time_in_force {\n        TimeInForce::Gtc => Ok((None, None)),\n        TimeInForce::Ioc => Ok((Some(\"IOC\".to_string()), None)),\n        TimeInForce::Fok => Ok((Some(\"FOK\".to_string()), None)),\n        TimeInForce::Gtd => {\n            let expire = expire_time.ok_or_else(|| {\n                anyhow::anyhow!(\"GTD time in force requires expire_time parameter\")\n            })?;\n            let expire_secs = expire.as_u64() / NANOSECONDS_IN_SECOND;\n            Ok((Some(\"GTD\".to_string()), Some(expire_secs.to_string())))\n        }\n        _ => anyhow::bail!(\"Unsupported time in force: {time_in_force:?}\"),\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::{sync::Arc, time::Duration};\n\n    use nautilus_model::instruments::CurrencyPair;\n    use rstest::rstest;\n\n    use super::*;\n\n    #[rstest]","sourceCodeStart":3234,"sourceCodeEnd":3270,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/kraken/src/http/spot/client.rs#L3234-L3270","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set `expire_time` (Unix nanoseconds) on the order request when using GTD.","Use `TimeInForce::Gtc` instead if the order should not expire.","Ensure the order factory/strategy populates `expire_time` for all GTD orders before submission."],"exampleFix":"// before\nlet tif = TimeInForce::Gtd; // expire_time: None\n// after\nlet tif = TimeInForce::Gtd;\nlet expire_time = Some(UnixNanos::from(expiry_ts_unix_ns));","handlingStrategy":"validation","validationCode":"fn validate_gtd(tif: TimeInForce, expire_time: Option<u64>) -> Result<(), String> {\n    if tif == TimeInForce::Gtd && expire_time.is_none() {\n        Err(\"GTD orders require expire_time\".into())\n    } else { Ok(()) }\n}","typeGuard":"fn has_expiry(o: &OrderRequest) -> bool {\n    o.time_in_force != TimeInForce::Gtd || o.expire_time.is_some()\n}","tryCatchPattern":"match client.submit_order(req).await {\n    Err(e) if e.to_string().contains(\"GTD time in force requires expire_time\") => {\n        let mut req = req.clone();\n        req.expire_time = Some(default_expiry_unix_ns());\n        client.submit_order(&req).await.map_err(Into::into)\n    }\n    other => other,\n}","preventionTips":["Configure the order factory to always set expire_time for GTD orders","Prefer GTC when no natural expiry exists in strategy logic","Validate TIF/expiry consistency in strategy pre-trade checks"],"tags":["time-in-force","gtd","expire-time","kraken","order-submission"],"backgroundTag":"missing-required-argument","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}