Nautilus Coding Standards

Nautilus’s coding standards are short, specific, and apply across all source files (Rust, Python, Cython, shell). Universal rules: spaces only, ~100-char line limit, American English. Comments are sentence-case with a blank line above; single-line comments do not end with a period (unless the line ends with a URL or markdown link). Error messages avoid , got - use , was / , received / , found. The spelling is hardcoded (one word). Caught exceptions are bound to e in both Rust and Python (never err / error). Internal fields may abbreviate (_price_prec); public APIs and metric names must spell things out (price_precision). Hanging arguments break to the next logical indent (not parenthesis-aligned), with a trailing comma and the closing paren on its own line. Commit subjects ≤60 chars, imperative voice, no terminating period; body wrapped at 100 chars. Shell scripts use #!/usr/bin/env bash, must work on macOS bash 3.2 for user-facing scripts, and avoid GNU-only flags (sed -i, stat -c, grep -P, readlink -f). Rust doc comments use indicative mood (“Returns a cached client.”).

Why this page exists

The other Nautilus pages cover what the framework does. This page covers how the source is written. Two concrete reasons it matters for Cortana MK3:

  1. If we ever upstream the UW adapter to Nautilus’s crates/adapters/ tree (the eventual right move per nautilus-developer-guide.md), the PR has to match these conventions or it gets bounced. Pre-commit hooks enforce most of them and CI will fail.
  2. Internal consistency when reading Nautilus source code. The spike’s open carryovers (custom RiskEngine rule API, custom data subscription wiring, Actor message routing) require reading the Nautilus codebase. Knowing their conventions reduces friction - _price_prec is internal, price_precision is public, that’s not a typo.

This is also quoted by nautilus-developer-guide.md (“PEP-8 + ruff + numpy-docstring conventions are enforced by pre-commit hooks”) and by nautilus-rust.md (Rust-side conventions on the Rust hot paths). This page is the explicit reference both of those defer to.

Universal formatting rules

Apply to every source file in the repo, regardless of language.

  • Spaces only. No hard tabs, anywhere. .editorconfig enforces.
  • ~100-char line length. “Lines should generally stay below 100 characters; wrap thoughtfully when necessary.” Soft limit, not hard
    • readability wins over count.
  • American English spelling. color, serialize, behavior, initialize. Not colour, serialise, behaviour.

Naming conventions

This is the rule most likely to bite Cortana code if we don’t internalize it.

  • Internal / private fields may abbreviate. Hot-path code uses abbreviations to stay readable on screen: _price_prec, _size_prec, _qty_prec. The leading underscore is the signal that the abbreviated form is acceptable.
  • Public API must spell out. price_precision, size_precision, quantity_precision. Public properties, function parameters, return types, metric names, and log labels all use full words.
  • Why this rule exists. Abbreviations leak into dashboards, Prometheus metric names, and error messages - once they’re in a metric label they’re stuck there forever. The split prevents that.

Cortana MK3 implication. When we expose a custom ScoreUpdate custom data type, the @customdataclass field names are public. So: score_value, score_components, regime_label - not score_val, score_comps, regime. Internal cache keys inside the actor can still abbreviate.

Error messages and error variables

The , got rule. This is a stylistic preference but it’s enforced by hook-level grepping in some Nautilus PRs.

  • BAD: f"Expected string, got {type(value)}"
  • GOOD: f"Expected string, was {type(value)}"
  • ALSO GOOD: , received, , found - pick the one that reads naturally.

Caught-exception variable name is always e. Both languages.

  • Python: except SomeError as e: - never as err: or as error:.
  • Rust: Err(e) and |e| in closures - never Err(err) or Err(error).

Spelling. hardcoded is one word. Not hard-coded, not hard coded.

Comment conventions

The full set of comment rules:

  • Blank line above every comment block or docstring (visual separation from the code above).
  • Sentence case. Capitalize the first letter; lowercase the rest unless proper nouns or acronyms.
  • No double-space after period. Single space.
  • Single-line comments do not end with a period. Exception: if the line ends with a URL or inline markdown link, leave the punctuation exactly as the link requires.
  • Multi-line comments separate sentences with commas, not period-per-line. The final line ends with a period.
  • Concise. Less is more. Explain the non-obvious; don’t restate what the code says.
  • No emoji symbols in text. Anywhere in the source tree.

Example (good):

# Compute the volume-weighted score over the last N bars
score = vwap_score(bars[-n:])

Example (bad):

# Compute the volume-weighted score over the last N bars.   <-- trailing period
score = vwap_score(bars[-n:])

Doc comment mood (Rust)

Rust doc comments use indicative mood: “Returns a cached client.”, “Computes the implied volatility.”, not “Return a cached client.” or “Get the implied vol.”

This matches the prevailing Rust ecosystem convention (rustdoc, std, the cargo doc output) and produces natural-feeling generated documentation.

(Python docstrings follow numpy-style - see nautilus-developer-guide.md for the broader Python style enforcement via ruff.)

Formatting hanging arguments

The single Nautilus Python convention most likely to differ from the PEP 8 / Black default: hanging-indent style for multi-line calls.

  • The Nautilus rule: break to the next logical indent, not aligned to the opening parenthesis. The closing paren goes on its own line at that same logical indent. Trailing comma after the last argument.
# Nautilus convention
long_method_with_many_params(
    some_arg1,
    some_arg2,
    some_arg3,
)
 
# Black-default would also accept this; the difference shows up
# when arguments are long enough that someone might be tempted
# to vanity-align under the opening paren - Nautilus says no.

The rationale is given explicitly: it conserves horizontal space, keeps the important code central in view, and survives function/method name changes (renaming the function doesn’t force a re-indent of every argument line). This is one of the few cases where the Nautilus style diverges meaningfully from idiomatic Python.

Commit message format

  • Subject line ≤ 60 characters (note: stricter than the typical 72).
  • Imperative voice (“Add UW adapter retry logic”, not “Added UW adapter retry logic” or “Adds UW adapter retry logic”).
  • Capitalize the subject line.
  • No terminating period on the subject.
  • Body separated from subject with a blank line.
  • Body wrapped at 100 characters width.
  • Bullet points OK with or without terminating periods (be consistent within the message).
  • #references to issues / tickets are encouraged.
  • Hyperlinks where informative.

A gitlint config ships at the repo root enforcing 60-char subject and 79-char body width. Currently opt-in (not CI-enforced) but “may be enforced in CI in the future.”

Cortana implication. Our current commit style (60-90 char subjects, mixed past/imperative) is mostly compatible. If we upstream PRs, the 60-char hard cap is the one we’ll consistently violate.

Shell script portability

This section matters for any scripts/ work that might be reused. Three concrete rules:

  1. Shebang is always #!/usr/bin/env bash. Not /bin/bash, not /bin/sh. The repo uses bash, not POSIX sh.
  2. User-facing scripts must work on macOS bash 3.2 (because that’s what macOS still ships). Avoid bash 4+ features:
    • No declare -A (associative arrays). Use simple arrays or files.
    • No readarray / mapfile. Use while read loops.
    • No ${var,,} / ${var^^}. Use tr '[:upper:]' '[:lower:]'.
  3. CI scripts (scripts/ci/*) run on Linux runners - bash 4+ and GNU tools are fine there.

The GNU-vs-BSD differences that bite cross-platform:

NeedLinux (GNU)macOS (BSD)Portable answer
In-place sedsed -i 's/…'sed -i '' 's/…'Use sed -i.bak 's/…' (works on both)
File sizestat -c%s filestat -f%z fileDetect with stat --version
SHA-256sha256sum filen/aUse shasum -a 256 or detect
Resolve symlinksreadlink -fn/aUse realpath instead
PCRE grepgrep -Pn/aUse grep -E (extended regex)
Nanosecond datedate +%Nn/aUse $RANDOM for cache-busting

Cortana implication. Our scripts/run.sh, scripts/check_*.sh, and the launchd loader are all bash-3.2-safe today and would already pass this rule.

Pre-commit enforcement (what hooks actually run)

Per nautilus-developer-guide.md (cited from coding-standards page in context):

  • check_anyhow_usage.sh - Rust error handling consistency.
  • check_logging_macro_usage.sh - log::info! etc., always fully qualified, never bare info!.
  • check_error_conventions.sh - lowercase error strings, no , got.
  • check_tokio_usage.sh - block plain tokio::spawn; require nautilus_common::live::get_runtime().spawn(...).
  • check_copyright_year.sh - current year on every file’s LGPL-3.0 header.

Plus the language-level enforcement: Python is ruff + numpy-style docstrings, Rust is cargo fmt + cargo clippy + the standardized LGPL-3.0 header.

These hooks are the contract. If we upstream code, anything that fails a hook fails the PR.

Cortana MK3 implications

Two scenarios - one where these standards bind hard, one where they’re nice-to-have.

Scenario A: Cortana stays a downstream consumer (default)

Cortana lives in our own repo, imports nautilus_trader from PyPI wheels, writes a Strategy and Actor subclass. None of these coding standards bind us legally. We’re not contributing back; our internal code can use whatever style we want.

That said, there are good reasons to adopt the subset anyway:

  • Naming convention. _price_prec / price_precision is genuinely better hygiene for any project that exposes metrics to Prometheus or labels to Grafana. Adopt.
  • , got, was in error messages. Trivially better; reads as natural English. Adopt.
  • Caught-exception variable name e. We already do this. Keep.
  • Sentence-case comments without trailing periods on single-liners. Stylistic preference. Adopt for consistency when we read Nautilus source side-by-side.
  • Hanging-indent style for multi-line calls. We already mostly match this (Black does similar). No active work needed.
  • hardcoded (one word). Adopt.
  • No emoji in source. Already our convention. Keep.

The standards we don’t need for Scenario A:

  • Rust doc-comment indicative mood - we’re not writing Rust.
  • Rust pre-commit hooks - we don’t have a Rust crate to enforce them on.
  • 60-char commit subjects - our convention is closer to 72.
  • LGPL-3.0 file headers - we’re MIT/proprietary, not LGPL.

Scenario B: Upstream the UW adapter to Nautilus

Per nautilus-rust.md, the eventual right answer for the UW data adapter is a Rust crate at crates/adapters/unusual_whales/ plus Python wiring at nautilus_trader/adapters/unusual_whales/. If we ever upstream it (possible - UW adapter is broadly useful, not Cortana-specific), every standard above binds:

  • Hard: all pre-commit hooks pass, including check_tokio_usage.sh (get_runtime().spawn() only), check_anyhow_usage.sh, check_error_conventions.sh, check_copyright_year.sh.
  • Hard: LGPL-3.0 header on every Rust file.
  • Hard: Rust doc comments in indicative mood.
  • Hard: _price_prec style for internal struct fields, full names for the Python-facing config and PyO3-exposed properties.
  • Hard: PyO3 stub annotations (gen_stub_pyclass, etc.) on every Python-exposed type so generated .pyi stubs stay in sync.
  • Hard: 60-char commit subject + 100-char body width per .gitlint.
  • Hard: Trailing comma + closing-paren-on-own-line for multi-line Python calls.
  • Hard: , was not , got in error messages.
  • Soft (best-effort): sentence-case no-trailing-period comments.

Codex would author the Rust under Cody’s review; the standards above become the explicit checklist for the handoff prompt.

Comparison to PEP 8 / Black defaults

For Cortana’s Python code, the three most likely surprises vs. PEP 8 / Black are:

  1. , got is banned. Every PEP 8-conformant codebase I’ve seen uses got; Nautilus explicitly forbids it. Easy to slip if not internalized.
  2. Single-line comments do NOT end with a period. PEP 8 is silent on this; most Black-formatted codebases default to either style. Nautilus is opinionated.
  3. Naming split: abbreviated internal, spelled-out public. PEP 8 says “use clear names”; Nautilus operationalizes this by drawing the line at the leading underscore. Hot-path internal fields can abbreviate (_price_prec); anything exported, including metric labels, must not.

The rest (4-space indent, 100-char lines, sentence-case docstrings, trailing comma in multi-line calls) is broadly aligned with what black + ruff already produce. So if Cortana adopts black and ruff with line-length = 100, ~80% of the standard is automatic.

See Also


Timeline

  • 2026-05-07 | Cody - Filed during pre-spike concept mastery sweep batch 7 (developer guide).