Coding Standards
Code Style
Use the codebase as a guide to formatting conventions, supplemented by the rules below.
Universal formatting rules
The following applies to all source files (Rust, Python, shell, etc.):
- Use spaces only, never hard tab characters.
- Lines should generally stay below 100 characters. Wrap thoughtfully when necessary.
- Use American English spelling (
color,serialize,behavior), enforced by.typos.toml. Preserve external API spellings through exact identifier exceptions. Exclude verbatim data and generated files. Runprek run typos --all-filesto check the repository.
Shell scripts
Bash is the default shell for repository scripts. Use POSIX sh only when a caller cannot rely on
Bash being installed. See Shell for script selection, extensions, portability,
structure, testing, formatting, and linting requirements.
Comment conventions
- Generally leave one blank line above every comment block or docstring so it is visually separated from code.
- Use sentence case - capitalize the first letter, keep the rest lowercase unless proper nouns or acronyms.
- Do not use double spaces after periods.
- Single-line comments must not end with a period, except when they end with a URL or inline Markdown link. For those exceptions, keep the punctuation that the link needs.
- Multi-line comments should separate sentences with commas (not period-per-line). The final line should end with a period.
- Keep comments concise. Favor clarity and only explain the non-obvious - less is more.
- Avoid emoji symbols in text.
Doc comment mood
Write Rust doc comments in the indicative mood, for example, "Returns a cached client." This matches the prevailing Rust ecosystem style and makes generated documentation natural for end-users.
Terminology and phrasing
-
Error messages: Avoid using ", got" in error messages. Use more descriptive alternatives like ", was", ", received", or ", found" depending on context.
- Bad:
"Expected string, got {type(value)}" - Good:
"Expected string, was {type(value)}"
- Bad:
-
Spelling: Use "hardcoded" (single word) rather than "hard-coded" or "hard coded" - this is the more modern and accepted spelling.
-
Error variable naming: Use single-letter
efor caught errors/exceptions:- Rust:
Err(e)notErr(err)orErr(error), and|e|not|err|in closures - Python:
except SomeError as e:notas err:oras error:
- Rust:
Naming conventions
-
Internal fields: Abbreviations are acceptable for private/internal fields (e.g.,
_price_prec,_size_prec) to keep hot-path code concise. -
User-facing API: Use full, descriptive names for public properties, function parameters, return types, and metric names/labels (e.g.,
price_precision,size_precision). This prevents abbreviated terminology from leaking into dashboards or alerts. -
Error messages and logs: Use full words for clarity (e.g., "price precision" not "price prec"). The user should never see abbreviated terminology.
-
Execution terminology: Use
Executionin public, project-owned PascalCase type names, such asBinanceExecutionClientConfig. Internal implementation types may retain establishedExecnames. Also reserveExecfor theExecAlgorithmIdandExecTesterfamilies, establishedexec_*names, and venue or protocol terms such asOkxExecType. Name protocol-specific wire models after the venue concept, such asHyperliquidExchangeAction. Preserve established public names, historical release entries, and source names in migration tables. -
Runtime qualifiers: Use
Livewhen a type selects or configures real-time runtime semantics. Examples includeLiveNodeversusBacktestNode, andLiveClockversusVirtualClock. TheLiveDataEngineConfig,LiveRiskEngineConfig, andLiveExecutionEngineConfigfamily differs from reusable core engine configs in the same way.Omit
Livefrom the ordinary adapter client family because a connected client is the default. Qualify alternate implementations by their behavior, such asSandboxExecutionClientorDatabentoHistoricalClient. An explicit live/historical protocol pair may retainLiveto distinguish the two implementations. -
Adapter factory configs: Name the data and execution inputs
<Venue>DataClientConfigand<Venue>ExecutionClientConfig. Factories consume these client configs directly rather than a separate factory config wrapper.LiveNodeConfigownstrader_id. Venue-specificaccount_idvalues belong on execution client configs.
Data loading APIs
Use free functions for stateless data ingestion. Use a class only when instances retain reusable configuration, caches, workers, iteration state, or an open resource across calls. Do not use a zero-state class solely to group static or class methods.
Follow the semantic distinction in the
Polars I/O API, adapted to the
established Nautilus load_* vocabulary:
load_<source>_<data>eagerly reads, normalizes, and materializes a complete result.scan_<source>_<data>creates a lazy query or deferred execution plan.stream_<source>_<data>incrementally yields records or batches.write_<format>eagerly writes an in-memory result.sink_<format>writes through a lazy or streaming execution path.
For ingestion, order names from general to specific: verb, source, logical data, then an optional
representation. Include the representation only when real sibling formats exist. For example,
load_binance_order_book_deltas is preferable to a stateless
BinanceOrderBookDeltaDataLoader.load class or a generic load_binance_data function.
Adapter package facades
Each package under python/nautilus_trader/adapters/ is a thin facade over the private _libnautilus
extension. Each adapter __init__.py declares a deterministic __all__ as the single source of truth for its
public API. python/generate_stubs.py copies that list into the matching .pyi. Thus, runtime and stub
exports agree exactly.
A venue adapter exposes its canonical identity constants plus the supported public surface:
<VENUE>,<VENUE>_CLIENT_ID,<VENUE>_VENUE, registered from Rust viam.addin the adapter'spython/mod.rs- data types,
*Config,*Factory, user-facing enums (such as*Environmentand*ProductType) - stateless loaders (
load_*,stream_*,convert_*) and intentional utilities (decode_*,get_*_arrow_schema_map)
Keep the facade thin. Do not add internals to __all__ only for structural parity. This includes raw HTTP or
WebSocket clients, wire models, endpoint URL resolvers (get_*_url, *_HTTP_URL), and caches. These packages
omit venue constants because the constants would be meaningless for them:
- Data providers, such as
databentoandtardis. - The
blockchaindata client. - The
sandboxexecution client. - The multi-venue
interactive_brokersbroker.
Order __all__ entries so the RUF022 pre-commit gate owns sort order. Do not hand-order the list.
Formatting
-
For longer code lines and calls with more than a couple of arguments, the recommended layout uses the next logical indent. Start a new line at that indent, rather than a hanging 'vanity' alignment from an opening parenthesis. This practice saves space to the right and keeps important code more central in view. The layout also survives function or method name changes.
-
The closing parenthesis should be located on a new line, aligned at the logical indent.
-
Multiple hanging parameters or arguments should end with a trailing comma:
long_method_with_many_params(
some_arg1,
some_arg2,
some_arg3, # <-- trailing comma
)Commit messages
Commit messages use a capitalized, imperative subject naming the affected surface, optionally followed by a body explaining the change.
Subject line
- Open with a capitalized imperative verb, so the subject describes what the commit does when applied.
Add,Fix,Improve,Refine,Update,Remove,Refactor, andStandardizecover most of the history. - Name the affected surface (crate, adapter, subsystem, or type) so the log stays scannable.
- Keep the subject at 10 characters or more so it can name the affected surface clearly.
- Aim for 60 characters or fewer for clear GitHub rendering and concise text. The commit-message hook warns without failing when the subject exceeds this target. The project plans to enforce this limit in the future.
- Do not end the subject with a period.
- Do not put an issue or pull request number in the subject. GitHub appends the pull request number
on squash merge, and any other reference belongs in the body. The commit-message hook rejects a
subject containing
#<number>in any position.
Add Decimal constructors to Instrument trait
Fix non-atomic order event application
Refine cross-platform wheel validation
Remove stale security audit exceptionsAvoid these shapes:
feat(bybit): add due_post_only flag # Conventional Commits type and scope
fix: bug # lowercase, unspecific, too short
Fixed the Bybit post-only rejection flag. # past tense, trailing period
Update stuff # says nothing about the surface
Fix the post-only flag (#4544) # pull request number added by hand
Fix PR #4544 review feedback # issue or pull request number in the subjectConventional Commits
Do NOT use Conventional Commits syntax for commit messages or pull request titles. Many editors and AI assistants emit that format by default. But no commit in this repository's history uses it. The type and scope repeat information that the subject already gives. Pull request titles matter here too, because a squash merge turns the PR title into the commit subject.
Body
The body is optional, but anything beyond a trivial change should say why the change was made rather than restate the diff.
- Separate the body from the subject with a blank line.
- Keep body lines to 79 characters or fewer to align with PEP 8 and traditional Git tooling.
- Use prose paragraphs or bullet points, whichever suits the change. Bullets may keep the same imperative voice as the subject, and do not need terminating periods.
- Include informative hyperlinks where they help a future reader.
Issue references
- Reference issues from the body, typically on a final line:
Resolves #4534when the commit closes the issue, orRelated to #4547when it is partial work. - GitHub appends the pull request number to the subject on squash merge, producing subjects such as
Fix TWAP child-order sizing and interval validation (#4544). Do not add that suffix by hand. Do not reference a pull request or issue anywhere else in the subject. The subject has no room for detail the body carries better, and a hand-written number duplicates or contradicts the appended one. - Aim to keep the pull request title short enough for the appended suffix to leave the squash-merged subject at 60 characters or fewer.
Runtime Conformance Contract
Use this reference to locate implementation boundaries and representative checks for selected design principles.
Shell Scripts
This page defines when to create a shell script and how to name, write, invoke, and test it. Bash is the default. Use POSIX sh only when a supported caller...