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
- Normalize the expiry to YYYYMMDD (or 'YYYYMMDD HH:MM:SS') before calling the parser.
- Strip/reformat ISO-style strings (2026-06-19 -> 20260619) at the data source boundary.
- Check the IB API version is producing the documented expiry format; upgrade or add parsing for the new variant upstream.
- 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
- Normalize all expiry strings to YYYYMMDD (or YYYYMMDD HH:MM:SS) upstream
- Don't assume ISO formats — IB uses compact YYYYMMDD
- Add regression tests for each expiry format your data source emits
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
- Empty expiry string
- Failed to parse execution timestamp '{time_str}': {e}
- Unsupported security type: {:?}
- {e}
- Invalid `external_order_claims` instrument ID {claim}: {e}
AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08).
Data as JSON: /api/errors/1e313611645390ed.
Report an issue: GitHub.