{"record":{"id":"e3d74fc94ce76ca7","repo":"nautechsystems/nautilus_trader","slug":"snapshot-interval-must-be-positive","errorCode":null,"errorMessage":"snapshot interval must be positive","messagePattern":"snapshot interval must be positive","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/adapters/tardis/src/csv/convert.rs","lineNumber":125,"sourceCode":"            let record: TardisOptionsChainRecord = csv_record\n                .deserialize(None)\n                .with_context(|| format!(\"failed to parse CSV file {}\", filepath.display()))?;\n            let instrument_id = parse_instrument_id(&record.exchange, record.symbol);\n            precision_by_instrument\n                .entry(instrument_id)\n                .or_insert_with(|| {\n                    OptionsChainPrecision::new(config.price_precision, config.size_precision)\n                })\n                .update(&record, config.price_precision, config.size_precision);\n            instrument_states\n                .entry(instrument_id)\n                .and_modify(|state| state.update_activation(record.local_timestamp))\n                .or_insert_with(|| InstrumentBuildState::new(record.clone()));\n\n            if let Some(interval) = config.snapshot_interval {\n                let interval_us = u64::try_from(interval.as_micros())\n                    .context(\"snapshot interval exceeds u64 microseconds\")?;\n                anyhow::ensure!(interval_us > 0, \"snapshot interval must be positive\");\n                let bucket = record.local_timestamp / interval_us;\n\n                if let Some(current_bucket) = current_bucket {\n                    anyhow::ensure!(\n                        bucket >= current_bucket,\n                        \"options_chain CSV rows must be ordered by local_timestamp when thinning\"\n                    );\n                }\n\n                if current_bucket.is_none_or(|current| bucket > current) {\n                    flush_pending_records_before(\n                        &catalog,\n                        &mut pending_records,\n                        &mut data_buffers,\n                        &precision_by_instrument,\n                        bucket,\n                        config.extract_bbo_as_quotes,\n                    )?;","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/nautechsystems/nautilus_trader/blob/18893faf8b356be3320add8de2f861b0b647cf06/crates/adapters/tardis/src/csv/convert.rs#L107-L143","documentation":"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.","triggerScenarios":"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)).","commonSituations":"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.","solutions":["Set `snapshot_interval` to a positive duration with at least 1 microsecond, e.g. `Some(Duration::from_secs(1))`.","Validate/parse user input so zero or sub-microsecond values are rejected before calling the converter.","If no thinning is desired, pass `snapshot_interval: None` instead of Some(zero)."],"exampleFix":"// before\nlet config = OptionsChainCsvConfig { snapshot_interval: Some(Duration::from_secs(0)), .. };\n// after\nlet config = OptionsChainCsvConfig { snapshot_interval: Some(Duration::from_secs(1)), .. };","handlingStrategy":"validation","validationCode":"from datetime import timedelta, timedelta as td\n\ndef validate_snapshot_interval(interval: timedelta | None) -> None:\n    if interval is not None and interval <= timedelta(0):\n        raise ValueError(f\"snapshot_interval must be positive, got {interval}\")","typeGuard":"def is_valid_interval(interval: timedelta | None) -> bool:\n    return interval is None or interval > timedelta(0)","tryCatchPattern":null,"preventionTips":["Never default snapshot_interval to zero; use None when thinning is unwanted","Parse durations with an explicit unit and reject sub-microsecond values","Add a config-validation step that checks positivity before conversion"],"tags":["rust","configuration","validation","csv"],"backgroundTag":"invalid-argument-value","analyzedSha":"18893faf8b356be3320add8de2f861b0b647cf06","analyzedAt":"2026-09-08T20:49:34.690Z","contentChangedAt":"2026-09-08T20:49:34.690Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}