Environment Setup
Use an editor with current Rust and Python language support, such as PyCharm or Visual Studio Code.
uv is the preferred tool for handling all Python virtual environments and dependencies.
prek is used to automatically run various pre-commit checks, auto-formatters, and linting tools at commit.
Source builds and the Rust crates require Rust (installation guide).
Cap'n Proto is required for serialization schema compilation. The required
version is specified in .nautilus-engineering/tools.toml. Ubuntu's default package is typically
too old, so you may need to install from source (see below).
NautilusTrader must compile and run on Linux, macOS, and Windows. Please keep portability in
mind: use std::path::Path in code and follow the
shell portability policy for scripts.
Setup
The following steps are for UNIX-like systems, and only need to be completed once.
Quick setup
Use this as a compact setup path for a new Linux or macOS development machine. The detailed sections below explain each step and cover alternatives.
Install platform tools first:
sudo apt-get update
sudo apt-get install -y build-essential clang lld curl git make pkg-configThen clone the repository and install the pinned project tools:
git clone --branch develop https://github.com/nautechsystems/nautilus_trader
cd nautilus_trader
curl https://sh.rustup.rs -sSf | sh
source "$HOME/.cargo/env"
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
cargo install cargo-binstall --locked
make install-tools
./scripts/install-capnp.sh
make sync
source python/.venv/bin/activate
export PYO3_PYTHON="$PWD/python/.venv/bin/python"
if [ "$(uname -s)" = "Linux" ]; then
PYTHON_LIB_DIR="$("$PYO3_PYTHON" -c 'import sysconfig; print(sysconfig.get_config_var("LIBDIR"))')"
export LD_LIBRARY_PATH="$PYTHON_LIB_DIR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
fi
export PYTHONHOME="$("$PYO3_PYTHON" -c 'import sys; print(sys.base_prefix)')"
prek install
make build-debugWindows users should follow the source installation steps in the installation guide, then use the relevant commands from this guide.
Install dependencies
Follow the installation guide, then sync the development and test dependencies from the repository root:
make syncFor frequent development, install a debug build of the package into python/.venv:
make install-debugInstall development tools
NautilusTrader pins every development tool so that all contributors and CI run identical versions. A single Makefile target installs the full set:
make install-toolsThis installs:
- Shared Cargo CLIs pinned in
.nautilus-engineering/tools.toml:cargo-audit,cargo-deny,cargo-edit,cargo-llvm-cov,cargo-nextest, andcargo-vet. - NautilusTrader Cargo CLIs pinned in
Cargo.tomlunder[workspace.metadata.tools]:cargo-codspeed,cargo-fuzz,cargo-hawk,cargo-machete,cbindgen,flamegraph, andlychee. - Prebuilt binaries pinned in
.nautilus-engineering/tools.toml:prek(pre-commit runner) andosv-scanner(vulnerability scanner). - uv, installed at the shared pinned version. The supported local uv minor series is defined in
python/pyproject.toml.
Cap'n Proto is also pinned in .nautilus-engineering/tools.toml but installs separately; see the
Cap'n Proto section below.
Fuzz targets also require a Rust nightly toolchain at runtime because cargo-fuzz uses
libfuzzer-sys and unstable compiler flags:
rustup toolchain install nightlyThe docs.rs compatibility check uses the dated nightly pinned in tools.toml:
rustup toolchain install "$(bash scripts/tool-version.sh nightly)" --profile minimalOne-off prerequisite: cargo-binstall
make install-tools uses cargo-binstall to fetch
prek as a prebuilt binary instead of compiling it from source. Install cargo-binstall once per
machine:
cargo install cargo-binstall --lockedThis is a one-time step. Subsequent runs of make install-tools reuse the installed cargo-binstall.
Single source of truth for versions
The repository manifests are the canonical source for dependency and tool versions. Do not copy current version numbers into docs, runner images, or scripts unless there is no manifest-backed way to read them.
| Source file or section | Defines |
|---|---|
rust-toolchain.toml | Rust toolchain. |
Cargo.toml and Cargo.lock | Rust workspace dependencies and exact resolution. |
Cargo.toml [workspace.metadata.tools] | NautilusTrader-specific Cargo tools. |
python/pyproject.toml | Python dependencies and supported Python and uv ranges. |
python/uv.lock | Exact Python dependency resolution. |
.nautilus-engineering/tools.toml | Shared engineering tools. |
tools.toml | NautilusTrader-specific tools without a native manifest. |
The shared catalog includes uv, prek, pip-audit, osv-scanner, Cap'n Proto, and common Cargo
tools. The local catalog retains the docs.rs nightly, Miri toolchain, and pypi-attestations pins.
The Makefile reads these via scripts/cargo-tool-version.sh, scripts/tool-version.sh, and
scripts/uv-version.sh, so bumping a version in the source file is the only required version
change. To check the pinned cargo tool versions against crates.io, run:
make outdatedSet up Git hooks
Set up the file and commit-message hooks, which run automatically when committing:
prek installRerun prek install after pulling a change to the configured hook types.
Before opening a pull-request run the formatting and lint suite locally so that CI passes on the first attempt:
make format
make pre-commitMake sure the Rust compiler reports zero errors -- broken builds slow everyone down.
Configure environment variables
NautilusTrader keeps its uv-managed environment at python/.venv, beside
python/pyproject.toml. This follows
uv's default project environment layout,
which keeps the environment where uv and Python editors expect to discover it. Run direct uv project
commands from python/ or pass --project python from the repository root. Make targets and CI
select the project themselves.
If this checkout previously used the root .venv, remove any UV_PROJECT_ENVIRONMENT export from
your shell startup files and the current shell before running Make or uv. This override takes
precedence over uv's project discovery.
Also replace any PYO3_PYTHON export that points to the root .venv/bin/python with this
checkout's python/.venv/bin/python. Editing a startup file does not update existing shells or
running applications: repeat the exports in each shell and restart applications that inherited
the old environment.
Required for Rust/PyO3 (Linux and macOS): When using Python installed via uv on Linux or
macOS, set the following environment variables from the repository root after make sync:
Use the commands for your shell. Bash and Zsh use export and activate; Fish uses set -gx
and activate.fish. Source Fish scripts only from Fish.
# Set the Python executable path for PyO3
export PYO3_PYTHON="$PWD/python/.venv/bin/python"
# Linux only: Set the library path for the uv-managed Python runtime
PYTHON_LIB_DIR="$("$PYO3_PYTHON" -c 'import sysconfig; print(sysconfig.get_config_var("LIBDIR"))')"
export LD_LIBRARY_PATH="$PYTHON_LIB_DIR${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
# Set the Python home path (required for Rust tests)
export PYTHONHOME="$("$PYO3_PYTHON" -c 'import sys; print(sys.base_prefix)')"The LD_LIBRARY_PATH export is Linux-specific and not needed on macOS or Windows.
PYO3_PYTHONtells PyO3 which Python interpreter to use, reducing unnecessary recompilation.PYTHONHOMEis required when runningmake cargo-testwith auv-installed Python. Without it, tests that depend on PyO3 may fail to locate the Python runtime.
To verify your environment is configured correctly:
python -c "import sys; print('Python:', sys.executable, sys.version)"
echo "PYO3_PYTHON: $PYO3_PYTHON"
echo "PYTHONHOME: $PYTHONHOME"Dependency management
Python dependencies are managed by uv. The [tool.uv] section in
python/pyproject.toml enforces three supply chain safety settings:
required-version: local uv commands accept any patch release in the supported minor series. If your local uv is outside that range,uv lockanduv syncfail with a version mismatch..nautilus-engineering/tools.tomlseparately pins the exact version used by CI, Docker, pre-commit, andmake update-uv. The stub targets run throughmake sync, so they enforce the same supported range; see Generated Python artifacts.exclude-newer = "7 days":uv lockignores package versions published within the last 7 days. This gives the community time to detect and quarantine compromised releases before they enter the lockfile. The value accepts an RFC 3339 timestamp ("2026-03-30T00:00:00Z"), a friendly duration ("7 days","1 week","24 hours"), or an ISO 8601 duration ("P7D","P1W","PT24H"). uv 0.11.8+ stores the friendly/ISO form asexclude-newer-spaninsidepython/uv.lockand emits a sentinelexclude-newertimestamp alongside it for backwards compatibility.python/uv.lockuses that format.no-build-package: explicit list of every third-party package locked inpython/uv.lock.uvrefuses to build any of them from source. In normal operation uv prefers wheels, so the setting is a no-op; it triggers only if a listed package stops publishing wheels for the target platform, in which caseuv lockfails rather than silently building from an sdist. The local workspace package is intentionally not in the list because it must be built by the workspace's own build backend. The list is kept in sync withpython/uv.lockbyscripts/check-no-build-packages.sh, which also runs as a pre-commit hook on changes to the lockfile or manifest.
Bypassing the cooldown
When a security patch or critical bug fix must be pulled in immediately, review the release and
override exclude-newer for that lock operation. Prefer a package-scoped override so unrelated
packages remain subject to the 7-day default. Do not add persistent package overrides to
python/pyproject.toml. All forms accept a timestamp, friendly duration, or ISO duration; package
overrides additionally accept false to exempt a package from the cooldown entirely.
# Shorten the cooldown for a single package (friendly duration)
uv lock --project python --exclude-newer-package "somepackage=1 day"
# Pin a single package to an absolute cutoff
uv lock --project python --exclude-newer-package "somepackage=2026-03-30T00:00:00Z"
# Exempt a single package from the cooldown entirely
uv lock --project python --exclude-newer-package "somepackage=false"
# Disable the cooldown for the whole resolution after reviewing every newly eligible package
uv lock --project python --exclude-newer "0 seconds"The CLI flag overrides the python/pyproject.toml value for that invocation only. The config
remains unchanged for subsequent runs.
Updating uv
To support a new uv minor series, change required-version in python/pyproject.toml. To update the
exact project version within that range, update Nautilus Engineering's [uv].version, sync the
shared catalog, then update the rev in .pre-commit-config.yaml and each digest-pinned uv Docker
image. Run make update-uv to install the project version locally.
Rust dependency cooldown before compilation
Repository builds must check every resolved registry dependency before Cargo can execute dependency
build scripts or procedural macros. make check-cargo-cooldown checks all tracked Cargo.lock
files against [workspace.metadata.cooldown] in Cargo.toml, including versions already committed
or pulled from another branch. It does not need a Git comparison base or full checkout history.
The Rust build, stub, check, Clippy, test, coverage, documentation, benchmark, and local CLI install
targets require this check. Stub generation counts as compilation because it runs the Rust
python-stub-gen binary through Cargo. Each compilation target waits for the gate, including under
parallel Make. Compilation uses the checked lockfile without resolving replacements. Pre-flight
also checks early, and CI common setup checks before repository compilation begins.
A version inside the cooldown window requires both an exact entry in
[workspace.metadata.cooldown.allow] and a matching cargo-vet audit. Unavailable release metadata
and unsupported registries fail the check. The diff-based pre-commit and dependency-update checks
remain separate: a clean Git diff does not establish that resolved dependencies are old enough.
Successful full checks are cached as .cargo-cooldown.json in CARGO_TARGET_DIR, or the Make
TARGET_DIR when no Cargo target directory is set. CI uses its configured Cargo target directory
so persistent runners retain the cache between jobs. Changes to any checked lockfile,
the policy, audits, or the check script invalidate the cache. Failed checks are not cached. Treat
this file as local verification state; do not restore it from an untrusted source.
This gate reduces exposure to newly published malicious registry releases. It does not establish
that older releases are safe, sandbox build scripts, or vet Git and local path dependencies.
Development-tool bootstrap commands such as make install-tools install external packages with
separate dependency resolutions and are outside this repository-lockfile gate. Direct Cargo and
Maturin invocations also bypass Make: run the full check first and pass --locked when building
repository code. Keep manifests and lockfiles unchanged between the check and compilation.
Builds
The Python package and the standalone Nautilus CLI are separate build artifacts. make build-debug
and make build install the Python package into python/.venv; neither command updates the
nautilus binary in Cargo's binary directory. See the
Nautilus CLI developer guide when changing or using the CLI.
After changing Rust bindings or Python package code, use a debug build for normal development. It skips release optimization and LTO, which reduces build time and peak memory use:
make build-debugUse make build when you need an optimized build. The release profile uses fat LTO and one code
generation unit, which increases peak memory use. Fat LTO can complete on a 16 GB machine when the
build has access to the full memory allocation and sufficient swap. Check VM or container memory
limits when applicable.
If the linker runs out of memory, use ThinLTO for an optimized local build:
CARGO_PROFILE_RELEASE_LTO=thin make buildThis override applies only to that command. Use the default fat LTO profile for performance measurements.
Refresh after pulling changes
Use the command that updates the affected artifact. The build targets call their prerequisites, so
make build-debug also syncs Python dependencies and regenerates Python type stubs.
| Changed input | Command | Updated artifact |
|---|---|---|
python/pyproject.toml or python/uv.lock | make sync | Dependencies in python/.venv. |
| Rust bindings, Python package code, or stub sources | make build-debug | Debug Python package and generated type stubs. |
CLI code, SQL initialization code, or schema/sql | make install-cli | Standalone nautilus binary in Cargo's bin path. |
Cargo, uv, prek, or OSV Scanner tool pins | make install-tools | Pinned development tools. |
| Cap'n Proto version in the shared catalog | ./scripts/install-capnp.sh | Cap'n Proto compiler. |
The environment variables in Configure environment variables
contain checkout-specific paths. After switching checkouts, changing the selected Python version,
or recreating python/.venv, activate that checkout's environment and export the variables again.
Verify that the shell resolves Python from the expected checkout:
source python/.venv/bin/activate
command -v python
python --versionIn Fish, use source python/.venv/bin/activate.fish for activation. Activation alone does not
refresh PYO3_PYTHON; repeat the environment variable commands above for the selected checkout.
Cap'n Proto
Cap'n Proto is required for serialization schema compilation.
The required version is defined in .nautilus-engineering/tools.toml.
Install the correct version for your platform:
./scripts/install-capnp.shVerify the installed version matches the shared catalog:
capnp --versionThe install script ensures the pinned version is installed. If Homebrew or Chocolatey provides an older version, install from source or see the Cap'n Proto installation guide.
Faster builds
The Cranelift code generation backend can reduce local build time for development, tests, and IDE
checks. It requires the nightly Rust toolchain and local changes to Cargo.toml:
rustup toolchain install nightly --component rust-analyzerSave the patch below, then apply it with git apply <patch>. Remove it with
git apply -R <patch> before pushing changes.
Do not commit these changes. The cranelift patch is for local development only and will break CI if pushed.
diff --git a/Cargo.toml b/Cargo.toml
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,3 +1,5 @@
+cargo-features = ["codegen-backend"]
+
[workspace]
resolver = "2"
members = [
@@ -424,6 +426,7 @@
lto = false
panic = "unwind"
incremental = true
+codegen-backend = "cranelift"
# Compile third-party deps at opt-level=1 in dev/test profiles. Workspace
# members keep opt-level=0 (fast iteration); deps recompile rarely so the
@@ -444,6 +447,7 @@
strip = false
lto = false
incremental = true
+codegen-backend = "cranelift"
[profile.test.package."*"]
opt-level = 1
@@ -452,6 +456,7 @@
inherits = "test"
debug = false # Improves compile times
strip = "debuginfo" # Improves compile times
+codegen-backend = "cranelift"
[profile.ci-pr]
inherits = "test"Run local build commands with RUSTUP_TOOLCHAIN=nightly, for example:
RUSTUP_TOOLCHAIN=nightly make build-debugSet the same toolchain in your rust-analyzer settings when using this local patch.
Services
Initialize PostgreSQL, Redis, and pgAdmin from the repository root:
make init-servicesThis starts the containers and initializes the NautilusTrader database schema. To start the
containers without reinitializing the schema, run make start-services. To start one service, use
the Compose file directly:
docker compose -f .docker/docker-compose.yml up -d postgresThe development services are:
postgres: PostgreSQL withPOSTGRES_USER=nautilus,POSTGRES_PASSWORD=pass, andPOSTGRES_DB=nautilusby default.redis: Redis server.pgadmin: pgAdmin 4 for database management and administration.
Please use this as development environment only. For production, use a proper and more secure setup.
Use make stop-services to stop the containers without removing their data. Use
make purge-services only when you intend to delete the development volumes.
PostgreSQL-backed tests can each maintain several connections. On a high-core workstation, the local nextest concurrency can exceed the development container's connection limit. Use the CI profile to match CI's lower concurrency:
NEXTEST_PROFILE=ci make cargo-test-extrasTo retain more local parallelism, set an explicit bounded worker count, for example:
NEXTEST_TEST_THREADS=8 make cargo-test-extrasNautilus CLI developer guide
The Nautilus CLI is a standalone Rust binary for PostgreSQL administration and other repository
operations. It is independent from the Python package installed by make build-debug or
make build.
Build and select the CLI
Install the CLI from the current checkout with:
make install-cliThis target runs cargo install --locked --force and places nautilus in Cargo's binary directory,
normally ~/.cargo/bin. Reinstall it after pulling changes to crates/cli, SQL initialization code,
or schema/sql. An installed CLI can otherwise remain older than the checkout while reading newer
schema files from it.
Before running repository-dependent commands, check which binary the shell resolves and its version:
command -v nautilus
nautilus --versionTo build and run the CLI directly from the checkout without replacing the installed binary, use:
cargo run --locked --package nautilus-cli --bin nautilus -- --helpOn Linux systems with GNOME, /usr/bin/nautilus is normally the GNOME file manager. Select the
NautilusTrader CLI with one of these methods:
- Put
~/.cargo/binbefore/usr/bininPATH. - Run
~/.cargo/bin/nautilusexplicitly. - Add
alias nautilus="$HOME/.cargo/bin/nautilus"to the shell configuration.
Windows source installs require GNU Make through MSYS2 or WSL. The nightly workflow also publishes a Windows x86-64 CLI archive.
Run nautilus --help to view the available command groups.
Database commands
The database commands accept connection settings as command-line arguments or through a .env file
in the current working directory or one of its parents. The CLI also accepts the corresponding
environment variables.
| Flag | Environment variable | Purpose |
|---|---|---|
--host | POSTGRES_HOST | Database host. |
--port | POSTGRES_PORT | Database port. |
--username | POSTGRES_USERNAME | Connecting administrator, normally the postgres role. |
--password | POSTGRES_PASSWORD | Administrator password and password for the application role. |
--database | POSTGRES_DATABASE | Database name and application role created during init. |
--schema | SCHEMA_DIR | Directory containing the SQL schema files. |
For example:
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USERNAME=postgres
POSTGRES_PASSWORD=pass
POSTGRES_DATABASE=nautilusnautilus database init creates or updates the roles and schema from the SQL files. Pass the schema
directory explicitly so renamed clones and worktrees do not depend on checkout path detection:
nautilus database init --schema "$PWD/schema/sql"Use a CLI built from the same checkout as these schema files. The initialization is designed to be re-run, including after an earlier run stopped partway through.
nautilus database drop removes the target schema, privileges, role, and stored data. Use it only
for a disposable database or after confirming that the data can be deleted.
Run nautilus database --help for the complete command syntax.
Rust analyzer settings
Rust analyzer is a popular language server for Rust and integrates with many IDEs. Configure its
VIRTUAL_ENV to use python/.venv. If PyO3 analysis cannot locate Python, also provide the
PYO3_PYTHON and PYTHONHOME values from Configure environment variables.
The examples below cover VS Code and AstroNvim. For other settings, see the
rust-analyzer configuration.
{
"rust-analyzer.restartServerOnConfigChange": true,
"rust-analyzer.linkedProjects": [
"Cargo.toml"
],
"rust-analyzer.cargo.features": "all",
"rust-analyzer.check.workspace": false,
"rust-analyzer.check.extraEnv": {
"VIRTUAL_ENV": "<path-to-nautilus-trader>/python/.venv",
"CC": "clang",
"CXX": "clang++"
},
"rust-analyzer.cargo.extraEnv": {
"VIRTUAL_ENV": "<path-to-nautilus-trader>/python/.venv",
"CC": "clang",
"CXX": "clang++"
},
"rust-analyzer.runnables.extraEnv": {
"VIRTUAL_ENV": "<path-to-nautilus-trader>/python/.venv",
"CC": "clang",
"CXX": "clang++"
},
"rust-analyzer.check.features": "all",
"rust-analyzer.testExplorer": true
}