nautechsystems/nautilus_trader · error

Invalid step: {step} (must be non-zero)

Error message

Invalid step: {step} (must be non-zero)

What it means

BarAggregation's constructor `new_checked` requires the aggregation step to be non-zero. The step is stored as `NonZeroUsize` because a zero step would make bar aggregation meaningless (or cause division-by-zero logic downstream). Passing 0 is rejected immediately with this anyhow error.

Source

Thrown at crates/model/src/data/bar.rs:490

impl BarSpecification {
    /// Creates a new [`BarSpecification`] instance with correctness checking.
    ///
    /// # Errors
    ///
    /// Returns an error if `step` is not positive (> 0), if `step` is not
    /// valid for a fixed-subunit time aggregation, or if a time-aggregated
    /// `step` overflows the representable duration or nanosecond interval.
    ///
    /// # Notes
    ///
    /// PyO3 requires a `Result` type for proper error handling and stacktrace printing in Python.
    pub fn new_checked(
        step: usize,
        aggregation: BarAggregation,
        price_type: PriceType,
    ) -> anyhow::Result<Self> {
        let step = NonZeroUsize::new(step)
            .ok_or(anyhow::anyhow!("Invalid step: {step} (must be non-zero)"))?;
        Self::validate_step(step.get(), aggregation)?;

        Ok(Self {
            step,
            aggregation,
            price_type,
        })
    }

    fn validate_step(step: usize, aggregation: BarAggregation) -> anyhow::Result<()> {
        match aggregation {
            BarAggregation::Millisecond => {
                Self::validate_periodic_step(step, aggregation, 1000, false)?;
            }
            BarAggregation::Second | BarAggregation::Minute => {
                Self::validate_periodic_step(step, aggregation, 60, false)?;
            }
            BarAggregation::Hour => Self::validate_periodic_step(step, aggregation, 24, false)?,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Check the step value before constructing: reject or default any step == 0 in your config-loading code.
  2. If step comes from a config file, verify the field is present and set to >= 1 (e.g. `step = 100` for 100-tick bars).
  3. If step is computed, add an assertion/log when the computation yields 0 so the bug is caught at the source.
  4. Prefer constructing via `NonZeroUsize::new(step)` yourself at the call site to surface zero values early with your own context.

Example fix

// before
let agg = BarAggregation::new_checked(cfg.step, BarAggregation::Tick, PriceType::Last)?;

// after
let step = cfg.step.max(1);
let agg = BarAggregation::new_checked(step, BarAggregation::Tick, PriceType::Last)?;
Defensive patterns

Strategy: validation

Validate before calling

fn valid_step(step: usize) -> bool { step != 0 }
if !valid_step(cfg.step) { return Err(anyhow!("step must be non-zero, got {}", cfg.step)); }

Prevention

When it happens

Trigger: Calling `BarAggregation::new_checked(0, aggregation, price_type)` — or building one from a config value that defaulted to or was parsed as 0 — triggers the error before `validate_step` even runs.

Common situations: Config files where a step field was omitted and defaulted to 0; deserializing a strategy config where step was typed as a plain usize with a Default derive; a calculation producing a 0 step (e.g. multiplying by 0 or an empty tick count).

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/660bac8d9dee39b2. Report an issue: GitHub.