nautechsystems/nautilus_trader · error · anyhow::Error

Empty expiry string

Error message

Empty expiry string

What it means

expiry_timestring_to_unix_nanos converts an IB contract expiry string (e.g. a futures or option last-trade date) into a UnixNanos timestamp. IB expiry strings can legitimately be empty for some contracts, but a parseable expiry is required to build the instrument, so an empty string is rejected immediately.

Solutions

  1. Provide an expiry string (YYYYMMDD, optionally with time) in the contract details before parsing.
  2. For perpetual/index instruments that genuinely have no expiry, route to the appropriate parser (e.g. index/continuous future) rather than the futures/options parser.
  3. Check IB contract details are complete — re-request contractDetails if fields came back empty.
  4. Guard: skip contracts with empty expiry instead of failing the whole batch.

Example fix

// before: empty expiry
let details = ContractDetails { last_trade_date_or_contract_month: "".into(), .. };
// after: expiry supplied
let details = ContractDetails { last_trade_date_or_contract_month: "20260619".into(), .. };
Defensive patterns

Strategy: validation

Validate before calling

fn has_expiry(details: &ContractDetails) -> bool {
    !details.last_trade_date_or_contract_month.is_empty()
}

Prevention

When it happens

Trigger: parse_futures_contract or parse_option_contract calls expiry_timestring_to_unix_nanos with contract details whose last_trade_date_or_contract_month (or local expiry field) is empty.

Common situations: Continuous/index-style contracts with no discrete expiry, incomplete contract details returned from IB, or manually built ContractDetails omitting the expiry field.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

        parts[1].len().min(8) as u8
    } else {
        0
    }
}

/// Convert timestamp string to UnixNanos.
///
/// Handles formats like "20230101" or "20230101 00:00:00 UTC".
///
/// # Errors
///
/// Returns an error if the timestamp cannot be parsed.
pub fn expiry_timestring_to_unix_nanos(
    expiry: &str,
    details: Option<&ibapi::contracts::ContractDetails>,
) -> anyhow::Result<UnixNanos> {
    if expiry.is_empty() {
        anyhow::bail!("Empty expiry string");
    }

    // Parse timestamp string - Most contract expirations are %Y%m%d format
    // Some exchanges have expirations in %Y%m%d %H:%M:%S %Z
    let dt = if expiry.len() == 8 {
        // Format: YYYYMMDD
        let year = &expiry[0..4];
        let month = &expiry[4..6];
        let day = &expiry[6..8];
        let date = time::Date::from_calendar_date(
            year.parse()?,
            time::Month::try_from(month.parse::<u8>()?)?,
            day.parse()?,
        )?;

        // If we have trading hours, try to extract the last trade time
        // Trading hours format: "20240411:0000-20240411:1800;..."
        let mut expiry_time = time::Time::MIDNIGHT;

View on GitHub (pinned to 18893faf8b)