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 ishardcoded(one word). Caught exceptions are bound toein both Rust and Python (nevererr/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:
- If we ever upstream the UW adapter to Nautilus’s
crates/adapters/tree (the eventual right move pernautilus-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. - 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_precis internal,price_precisionis 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.
.editorconfigenforces. - ~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. Notcolour,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:- neveras err:oras error:. - Rust:
Err(e)and|e|in closures - neverErr(err)orErr(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).
#referencesto 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:
- Shebang is always
#!/usr/bin/env bash. Not/bin/bash, not/bin/sh. The repo uses bash, not POSIX sh. - 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. Usewhile readloops. - No
${var,,}/${var^^}. Usetr '[:upper:]' '[:lower:]'.
- No
- 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:
| Need | Linux (GNU) | macOS (BSD) | Portable answer |
|---|---|---|---|
| In-place sed | sed -i 's/…' | sed -i '' 's/…' | Use sed -i.bak 's/…' (works on both) |
| File size | stat -c%s file | stat -f%z file | Detect with stat --version |
| SHA-256 | sha256sum file | n/a | Use shasum -a 256 or detect |
| Resolve symlinks | readlink -f | n/a | Use realpath instead |
| PCRE grep | grep -P | n/a | Use grep -E (extended regex) |
| Nanosecond date | date +%N | n/a | Use $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 bareinfo!.check_error_conventions.sh- lowercase error strings, no, got.check_tokio_usage.sh- block plaintokio::spawn; requirenautilus_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_precisionis genuinely better hygiene for any project that exposes metrics to Prometheus or labels to Grafana. Adopt. , got→, wasin 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_precstyle 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.pyistubs 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:
, wasnot, gotin 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:
, gotis banned. Every PEP 8-conformant codebase I’ve seen usesgot; Nautilus explicitly forbids it. Easy to slip if not internalized.- 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.
- 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
- Nautilus Developer Guide
- Nautilus Rust
- 2026-05-09 Nautilus Spike Plan:
~/conductor/workspaces/cortanaroi-mk2/belo-horizonte/plans/2026-05-09-nautilus-spike.md
Timeline
- 2026-05-07 | Cody - Filed during pre-spike concept mastery sweep batch 7 (developer guide).