nautechsystems/nautilus_trader · error · anyhow::Error

Invalid expiry format: {}

Error message

Invalid expiry format: {}

What it means

expiry_timestring_to_unix_nanos accepts only two expiry formats: plain YYYYMMDD (8 chars) and a datetime form like YYYYMMDD HH:MM:SS (optionally with a timezone suffix). Any other string shape cannot be parsed into a date/time and triggers this error with the raw input.

Source

Thrown at crates/adapters/interactive_brokers/src/providers/parse.rs:148

            let time_part = parts[1];
            let year = &date_part[0..4];
            let month = &date_part[4..6];
            let day = &date_part[6..8];

            let time_parts: Vec<&str> = time_part.split(':').collect();
            let hour = time_parts.first().unwrap_or(&"0").parse::<u8>()?;
            let minute = time_parts.get(1).unwrap_or(&"0").parse::<u8>()?;
            let second = time_parts.get(2).unwrap_or(&"0").parse::<u8>()?;

            let date = time::Date::from_calendar_date(
                year.parse()?,
                time::Month::try_from(month.parse::<u8>()?)?,
                day.parse()?,
            )?;
            let time_obj = time::Time::from_hms(hour, minute, second)?;
            time::PrimitiveDateTime::new(date, time_obj)
        } else {
            anyhow::bail!("Invalid expiry format: {}", expiry);
        }
    };

    // Treat the parsed expiry timestamp as UTC. NautilusTrader expects IB timestamps
    // to be configured and interpreted in UTC.
    let offset_dt = dt.assume_utc();
    let nanos = offset_dt.unix_timestamp_nanos();
    Ok(UnixNanos::new(nanos as u64))
}

/// Parse an IB ContractDetails to a Nautilus instrument.
///
/// # Errors
///
/// Returns an error if parsing fails.
pub fn parse_ib_contract_to_instrument(
    details: &ibapi::contracts::ContractDetails,
    instrument_id: InstrumentId,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Normalize the expiry to YYYYMMDD (or 'YYYYMMDD HH:MM:SS') before calling the parser.
  2. Strip/reformat ISO-style strings (2026-06-19 -> 20260619) at the data source boundary.
  3. Check the IB API version is producing the documented expiry format; upgrade or add parsing for the new variant upstream.
  4. Log the offending string in the message and fix that specific field in your data pipeline.

Example fix

// before: ISO format fails
expiry_timestring_to_unix_nanos("2026-06-19", None)?;
// after: normalized IB format
expiry_timestring_to_unix_nanos("20260619", None)?;
Defensive patterns

Strategy: validation

Validate before calling

fn expiry_is_parseable(expiry: &str) -> bool {
    expiry.len() == 8 && expiry.chars().all(|c| c.is_ascii_digit())
        || (expiry.len() >= 19 && expiry.as_bytes()[8] == b' ')
}

Prevention

When it happens

Trigger: parse_futures_contract or parse_option_contract passes an expiry string that is neither 8-char YYYYMMDD nor matches the datetime format IB sometimes returns (unexpected separators, partial dates, or a foreign format).

Common situations: Custom/mock data using ISO format like 2026-06-19, timezone variants the parser doesn't handle, truncated strings, or IB format changes across API versions.

Related errors


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