nautechsystems/nautilus_trader · error

Invalid `UnixNanos` for 'end'

Error message

Invalid `UnixNanos` for 'end'

What it means

`Data::end` reads the metadata "end" key and parses it as `UnixNanos`, panicking with "Invalid `UnixNanos` for 'end'" if the string cannot be parsed. Absence of the key returns None; only a present-but-invalid value panics. It mirrors the `start` accessor and enforces canonical epoch-nanosecond metadata.

Source

Thrown at crates/model/src/data/mod.rs:939

    /// - The `start` value contained in the metadata is invalid.
    #[must_use]
    pub fn start(&self) -> Option<UnixNanos> {
        let metadata = self.metadata.as_ref()?;
        let start_str = metadata.get_str("start")?;
        Some(UnixNanos::from_str(start_str).expect("Invalid `UnixNanos` for 'start'"))
    }

    /// Returns an [`Option<UnixNanos>`] parsed from the metadata `end` field.
    ///
    /// # Panics
    ///
    /// This function panics if:
    /// - The `end` value contained in the metadata is invalid.
    #[must_use]
    pub fn end(&self) -> Option<UnixNanos> {
        let metadata = self.metadata.as_ref()?;
        let end_str = metadata.get_str("end")?;
        Some(UnixNanos::from_str(end_str).expect("Invalid `UnixNanos` for 'end'"))
    }

    /// Returns an [`Option<usize>`] parsed from the metadata `limit` field.
    ///
    /// # Panics
    ///
    /// This function panics if:
    /// - The `limit` value contained in the metadata is invalid.
    #[must_use]
    pub fn limit(&self) -> Option<usize> {
        let metadata = self.metadata.as_ref()?;
        metadata.get_usize("limit").or_else(|| {
            metadata
                .get_str("limit")
                .map(|s| s.parse::<usize>().expect("Invalid `usize` for 'limit'"))
        })
    }
}

View on GitHub (pinned to 18893faf8b)

Solutions

  1. Write end as a Unix-nanosecond string, e.g. metadata["end"] = str(int(dt.timestamp() * 1e9))
  2. Use the library's timestamp conversion helpers instead of hand-formatted strings
  3. Validate metadata before constructing the Data payload

Example fix

// before
metadata["end"] = "1704067200"  // seconds, not nanoseconds — parses as valid but wrong? no: valid int, but if "1.7040672e9" it panics
// after
metadata["end"] = "1704067200000000000"  // Unix nanoseconds as integer string
Defensive patterns

Strategy: validation

Validate before calling

def validate_end_metadata(metadata: dict) -> None:
    raw = metadata.get("end")
    if raw is not None:
        int(raw)  # raises ValueError if not a valid integer string
        if int(raw) < 0:
            raise ValueError("end must be a non-negative Unix-nanosecond value")

Type guard

def is_unix_nanos_str(s: str) -> bool:
    try:
        return int(s) >= 0
    except (ValueError, TypeError):
        return False

Try / catch

try:
    end = data.end()
except Exception as e:
    logger.error(f"malformed 'end' metadata: {metadata.get('end')!r}")
    raise

Prevention

When it happens

Trigger: Setting metadata["end"] to a non-numeric string (ISO timestamp, float with decimals, empty string, ms-instead-of-ns epoch) and then calling `.end()` on the Data object.

Common situations: Custom data adapters writing human-readable end dates; config templates with placeholder values; mixed ms/ns timestamps across data sources.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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