nautechsystems/nautilus_trader · error · anyhow::Error

snapshot interval must be positive

Error message

snapshot interval must be positive

What it means

When converting a Tardis options-chain CSV with thinning enabled, `snapshot_interval` controls the bucketing of rows into snapshots. The interval is converted to microseconds and must be greater than zero; a zero (or negative, unrepresentable) interval would make bucketing degenerate (division by zero) so the library rejects it with `anyhow::ensure!`. Note a separate error covers intervals too large for u64 microseconds.

Source

Thrown at crates/adapters/tardis/src/csv/convert.rs:125

            let record: TardisOptionsChainRecord = csv_record
                .deserialize(None)
                .with_context(|| format!("failed to parse CSV file {}", filepath.display()))?;
            let instrument_id = parse_instrument_id(&record.exchange, record.symbol);
            precision_by_instrument
                .entry(instrument_id)
                .or_insert_with(|| {
                    OptionsChainPrecision::new(config.price_precision, config.size_precision)
                })
                .update(&record, config.price_precision, config.size_precision);
            instrument_states
                .entry(instrument_id)
                .and_modify(|state| state.update_activation(record.local_timestamp))
                .or_insert_with(|| InstrumentBuildState::new(record.clone()));

            if let Some(interval) = config.snapshot_interval {
                let interval_us = u64::try_from(interval.as_micros())
                    .context("snapshot interval exceeds u64 microseconds")?;
                anyhow::ensure!(interval_us > 0, "snapshot interval must be positive");
                let bucket = record.local_timestamp / interval_us;

                if let Some(current_bucket) = current_bucket {
                    anyhow::ensure!(
                        bucket >= current_bucket,
                        "options_chain CSV rows must be ordered by local_timestamp when thinning"
                    );
                }

                if current_bucket.is_none_or(|current| bucket > current) {
                    flush_pending_records_before(
                        &catalog,
                        &mut pending_records,
                        &mut data_buffers,
                        &precision_by_instrument,
                        bucket,
                        config.extract_bbo_as_quotes,
                    )?;

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Set `snapshot_interval` to a positive duration with at least 1 microsecond, e.g. `Some(Duration::from_secs(1))`.
  2. Validate/parse user input so zero or sub-microsecond values are rejected before calling the converter.
  3. If no thinning is desired, pass `snapshot_interval: None` instead of Some(zero).

Example fix

// before
let config = OptionsChainCsvConfig { snapshot_interval: Some(Duration::from_secs(0)), .. };
// after
let config = OptionsChainCsvConfig { snapshot_interval: Some(Duration::from_secs(1)), .. };
Defensive patterns

Strategy: validation

Validate before calling

from datetime import timedelta, timedelta as td

def validate_snapshot_interval(interval: timedelta | None) -> None:
    if interval is not None and interval <= timedelta(0):
        raise ValueError(f"snapshot_interval must be positive, got {interval}")

Type guard

def is_valid_interval(interval: timedelta | None) -> bool:
    return interval is None or interval > timedelta(0)

Prevention

When it happens

Trigger: Calling `convert_options_chain_csv` (or `py_convert_tardis_options_chain_csv`) with `config.snapshot_interval = Some(Duration::ZERO)` (or a duration that rounds to 0 microseconds, e.g. Duration::from_nanos(1)).

Common situations: Reading the interval from config/CLI where a default of 0 was left in place; parsing a user-supplied value like "0s" or "0ms"; constructing the duration with the wrong unit so it truncates to zero microseconds.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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