nautechsystems/nautilus_trader · error

Timer '{name}' stop time {} must be after start time {}

Error message

Timer '{name}' stop time {} must be after start time {}

What it means

set_timer_ns supports an optional stop_time_ns that terminates the timer. Validation requires the stop time to be strictly after the start time; if stop_time <= start_time_ns the timer would stop before or exactly when it begins, which is contradictory, so the call fails.

Source

Thrown at crates/common/src/clock.rs:870

    let next_event_time = if fire_immediately {
        start_time_ns
    } else {
        start_time_ns.checked_add(interval_ns).ok_or_else(|| {
            anyhow::anyhow!("Timer '{name}' first event time exceeds UnixNanos range")
        })?
    };

    if !allow_past && next_event_time < ts_now {
        anyhow::bail!(
            "Timer '{name}' next event time {} would be in the past (current time is {ts_now})",
            next_event_time.to_rfc3339(),
        );
    }

    if let Some(stop_time) = stop_time_ns {
        if stop_time <= start_time_ns {
            anyhow::bail!(
                "Timer '{name}' stop time {} must be after start time {}",
                stop_time.to_rfc3339(),
                start_time_ns.to_rfc3339(),
            );
        }

        if !allow_past && stop_time <= ts_now {
            anyhow::bail!(
                "Timer '{name}' stop time {} is in the past (current time is {ts_now})",
                stop_time.to_rfc3339(),
            );
        }
    }

    Ok((
        name,
        start_time_ns,
        stop_time_ns,

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Ensure stop_time_ns > start_time_ns, e.g. stop = start + planned_duration_ns, before calling set_timer_ns.
  2. Check unit conversion: Nautilus times are Unix nanoseconds; multiply seconds by 1_000_000_000.
  3. Guard the computation: bail or warn in your code if stop_time_ns <= start_time_ns and drop the stop_time (pass None) for an unbounded timer.
  4. Correct the strategy/session config so the stop deadline postdates the start.

Example fix

// before
let stop = start_time_ns; // wrong: equals start
clock.set_timer_ns("t", interval_ns, start_time_ns, Some(stop), None, false, false);
// after
let stop = start_time_ns + duration_ns; // must be > start
clock.set_timer_ns("t", interval_ns, start_time_ns, Some(stop), None, false, false);
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_stop_after_start(start_time_ns: u64, stop_time_ns: Option<u64>) -> anyhow::Result<()> {
    if let Some(stop) = stop_time_ns {
        anyhow::ensure!(stop > start_time_ns, "stop {stop} must be after start {start_time_ns}");
    }
    Ok(())
}

Try / catch

match clock.set_timer_ns(name, interval_ns, start, stop, None, false, false) {
    Err(e) if e.to_string().contains("must be after start time") => {
        log::error!("bad timer config: {e}");
        // fix config or pass None for unbounded timer
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling set_timer_ns with Some(stop_time_ns) that is less than or equal to start_time_ns.

Common situations: Off-by-one or unit mix-ups (seconds vs nanoseconds when computing stop = start + duration); copying a stop time from a different schedule; config where stop time was authored for a different start time.

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/24fa46e8b9936128. Report an issue: GitHub.