nautechsystems/nautilus_trader · critical

Invalid `TimeInForce`

Error message

Invalid `TimeInForce`

What it means

The `time_in_force` column string is parsed with `TimeInForce::from_str` and unwrapped with expect() while loading an order row. The panic indicates the stored TIF value (expected e.g. 'GTC','IOC','FOK','GTD','DAY','AT_THE_OPEN'...) is not a recognized variant, so the row cannot be reconstructed.

Source

Thrown at crates/infrastructure/src/sql/models/orders.rs:1094

            .try_get::<Option<&str>, _>("trigger_type")
            .ok()
            .and_then(parse_trigger_type);
        let limit_offset = row
            .try_get::<Option<&str>, _>("limit_offset")
            .ok()
            .and_then(|x| x.and_then(|s| Decimal::from_str(s).ok()));
        let trailing_offset = row
            .try_get::<Option<&str>, _>("trailing_offset")
            .ok()
            .and_then(|x| x.and_then(|s| Decimal::from_str(s).ok()));
        let trailing_offset_type = row
            .try_get::<Option<TrailingOffsetTypePg>, _>("trailing_offset_type")
            .ok()
            .flatten()
            .and_then(|value| value.0);
        let time_in_force = row
            .try_get::<&str, _>("time_in_force")
            .map(|x| TimeInForce::from_str(x).expect("Invalid `TimeInForce`"))?;
        let expire_time = row
            .try_get::<Option<&str>, _>("expire_time")
            .ok()
            .and_then(|x| x.map(UnixNanos::from));
        let filled_qty = row.try_get::<&str, _>("filled_qty").map(Quantity::from)?;
        let liquidity_side = row
            .try_get::<Option<&str>, _>("liquidity_side")
            .ok()
            .and_then(|x| x.map(|x| LiquiditySide::from_str(x).expect("Invalid `LiquiditySide`")));
        let avg_px = row.try_get::<Option<Decimal>, _>("avg_px").ok().flatten();
        let slippage = row.try_get::<Option<Decimal>, _>("slippage").ok().flatten();
        let commissions = row
            .try_get::<Option<Vec<String>>, _>("commissions")?
            .map_or_else(Vec::new, |c| {
                c.into_iter().map(|s| Money::from(&s)).collect()
            });
        let status = row
            .try_get::<&str, _>("status")

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Locate bad rows with `SELECT id, time_in_force FROM orders WHERE time_in_force NOT IN (...)` and correct them
  2. Fix the insertion path to serialize TimeInForce canonically
  3. Trim/uppercase the stored values via migration if formatting is the issue
  4. Return a parse error rather than expect for untrusted data sources

Example fix

// before
TimeInForce::from_str(x).expect("Invalid `TimeInForce`")
// after
TimeInForce::from_str(x.trim()).map_err(|e| ModelError::Parse(format!("invalid time_in_force '{x}': {e}")))?
Defensive patterns

Strategy: type-guard

Validate before calling

const VALID_TIF: &[&str] = &["GTC","IOC","FOK","GTD","DAY","AT_THE_OPEN","AT_THE_CLOSE"];
assert!(VALID_TIF.contains(&tif_str.trim()), "bad time_in_force: {tif_str}");

Type guard

fn is_known_time_in_force(s: &str) -> bool {
    TimeInForce::from_str(s.trim()).is_ok()
}

Try / catch

let tif = TimeInForce::from_str(x.trim())
    .map_err(|e| ModelError::Parse(format!("row {}: invalid time_in_force '{x}': {e}", row_id)))?;

Prevention

When it happens

Trigger: Calling `OrderModel::from_row` where `time_in_force` contains an unknown/misspelled/case-mismatched value, e.g. 'Good Till Cancel', 'gtc ' with whitespace, or an exchange-specific TIF not in the enum.

Common situations: Older schema versions or migrations that rewrote TIF values; adapter-specific persistence layers; manual CSV imports into the orders table.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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