Nautilus Rust
Nautilus has three implementation paths sharing a single Rust domain model: v1 legacy (Cython/Python in
nautilus_trader/, broadest feature coverage), v2 Rust (pure Rust undercrates/, no Python runtime), and v2 PyO3 (Python user-components running on the Rust core engine via PyO3 bindings). For Cortana MK3, the relevant path is v1 legacy - because the Interactive Brokers adapter exists ONLY there. v2 Rust / v2 PyO3 currently lack IBKR support per the official capability matrix. The Rust core undercrates/is a 16-crate workspace organized as Foundation (core,model,common,system,trading), Engines (data,execution,portfolio,risk), Infrastructure (serialization,network,cryptography,persistence), Runtime (live,backtest), and Bindings (pyo3). End-userspip install nautilus_trader- wheels are prebuilt; no Rust toolchain required at runtime. Rust shows up only when (1) building from source, (2) writing a new venue adapter, or (3) extending matching/parsing/codec hot paths. The PyO3 boundary is designed so strategy logic stays Python-fast-enough while data routing, book maintenance, and accounting math stay in Rust.
Why this page exists
The other Nautilus pages (nautilus-architecture.md,
nautilus-developer-guide.md, nautilus-getting-started.md) treat Rust as
backdrop. This page makes the Rust story explicit so a non-Rust developer
(Cody) plus an AI implementer (Codex) can answer one question with
confidence: do I ever have to write Rust to run Cortana on Nautilus?
Spike DX premise (per 2026-05-09-nautilus-spike.md): “Python-first; Rust
core fades into the background.” This page tests that premise against the
documented Rust surface.
Three implementation paths (the capability matrix)
Per the /concepts/rust/ page, Nautilus exposes three coexisting
implementations, all sharing the Rust domain model (Order, Position,
Account, Instrument, Price, Quantity, Money):
| Path | Where it lives | Runtime |
|---|---|---|
| v1 legacy | nautilus_trader/ (Cython + Python) | CPython interpreter loading Cython extension modules linked against Rust libs |
| v2 Rust | crates/ (pure Rust) | Standalone cargo build binary, no Python |
| v2 PyO3 | crates/ engine + Python user-components | Rust event loop calling Python actors/strategies via PyO3 |
Capability coverage (what works on which path)
The capability matrix on the page is not symmetric - it directly determines which path is usable for a project.
Available on all three paths: Strategy, Actor, DataEngine, ExecutionEngine, RiskEngine, BacktestEngine, BacktestNode, LiveNode, OrderEmulator, Matching engine, Portfolio, Accounts, Cache, MessageBus, Data catalog, Indicators.
v1 legacy ONLY: Controller, Tearsheets, Config serialization, the Interactive Brokers adapter, exec algorithms beyond TWAP.
Adapter coverage at 2026-05-07:
- v1 legacy: every shipped adapter (Architect, Betfair, Binance, BitMEX, Bybit, Databento, Deribit, dYdX, Hyperliquid, Interactive Brokers, Kraken, OKX, Polymarket, Sandbox, Tardis).
- v2 Rust + v2 PyO3: all of the above EXCEPT Interactive Brokers.
Choosing a path (per the docs)
“v1 legacy is the most complete today. Use it if you need the Controller, tearsheets, Interactive Brokers, or config serialization.”
“v2 Rust gives native performance without a Python runtime. All core trading functionality is available. Use it for latency-sensitive deployments or teams that prefer a compiled language.”
“v2 PyO3: Python user-components (actors, strategies) run on the Rust core engine with Rust performance for data processing and execution, while keeping the Python authoring experience.”
For Cortana, the IBKR-only-on-v1 fact is load-bearing: the spike’s target broker is IBKR, so v1 legacy is the path. v2 PyO3 becomes relevant later if/when IBKR ships there.
Rust crate layout (the workspace under crates/)
Per nautilus-architecture.md (canonical) and the Rust concept page:
| Category | Crates | Purpose |
|---|---|---|
| Foundation | core, model, common, system, trading | Primitives, domain model (Price, Quantity, Money, Order, Position), kernel, actor & strategy base |
| Engines | data, execution, portfolio, risk | Core trading engine components |
| Infrastructure | serialization, network, cryptography, persistence | Encoding, networking, signing, storage |
| Runtime | live, backtest | Environment-specific node implementations |
| External | adapters/* | Venue and data integrations |
| Bindings | pyo3 | Python bindings |
Project setup (Rust-side, contributor view)
If a contributor does depend on the Rust crates directly:
[dependencies]
nautilus-backtest = "0.55"
nautilus-common = "0.55"
nautilus-execution = "0.55"
nautilus-model = { version = "0.55", features = ["stubs"] }
nautilus-trading = { version = "0.55", features = ["examples"] }
anyhow = "1"
log = "0.4"For live: add nautilus-live = "0.55" plus the venue adapter
(nautilus-okx, etc.). The IBKR crate is not on this list - it lives
only in the Cython tree.
To track develop, point all crates at the same git source to avoid
type mismatches between crates.io and git versions.
MSRV: Rust 1.95.0.
Feature flags
| Flag | Crate | Effect |
|---|---|---|
high-precision | nautilus-model | 16-digit fixed precision (default 9). Required for crypto. |
stubs | nautilus-model | Test instrument stubs (audusd_sim, etc.). |
examples | nautilus-trading | Example strategies (EmaCross, GridMarketMaker). |
streaming | nautilus-backtest | Catalog-based data streaming via BacktestNode. |
defi | nautilus-model | DeFi data types. Implies high-precision. |
For Cortana (SPY options, USD-denominated), default 9-digit precision is enough.
The PyO3 boundary - where Python ends and Rust begins
The architecture page is explicit: Python bindings come from “statically linking the Rust libraries to the C extension modules generated by Cython at compile time (effectively extending the CPython API)” and “Both Rust and Cython are build dependencies. The binary wheels produced from a build do not require Rust or Cython to be installed at runtime.”
What this means in practice:
- End-user installs
pip install nautilus_trader(oruv pip install nautilus_trader[ib]). No Rust toolchain on the machine. Nocargo, norustc. The wheel includes prebuilt.sofiles that already statically link the Rust libs. - Strategy code is Python.
class CortanaStrategy(Strategy), withon_quote_tick,on_bar,on_order_filledoverrides, all Python methods. - Each call from Python into the framework crosses PyO3. When a
strategy calls
self.cache.quote_tick(instrument_id), the call descends into Rust through a PyO3-generated wrapper, executes against the Rust cache, and returns a Python-visible object. - Each callback from the framework into Python crosses PyO3. When
the DataEngine fires
on_quote_tick, the kernel (Rust) dispatches into the strategy (Python) through the PyO3 boundary.
What stays Rust no matter what (and is therefore fast no matter what)
The architecture page lists which paths the kernel runs on its single thread:
- MessageBus dispatch
- Cache reads and writes
- Strategy logic and order management (this is the Python-callback layer for v1; pure Rust for v2)
- Risk engine checks and execution coordination
The data-side hot paths are entirely Rust:
- Tick parsing in
DataClientadapters (Rust crates undercrates/adapters/<venue>/) - Bar building, order book maintenance, depth snapshots
- Quote/trade cache writes (
cache.add_quote,cache.add_trade) - MessageBus topic routing
The execution-side hot paths are entirely Rust:
- Order state machine
- Matching engine (in backtest)
- Reconciliation logic
- Fill/Filled/Canceled event dispatch
The accounting / portfolio / risk math is entirely Rust:
- Position aggregation, unrealized PnL, exposure calculation
- Pre-trade risk validation
- Greeks and option math (per
nautilus-options-greeksshipped inmodel)
What is Python (and is therefore “Python-fast”)
- The strategy
on_*callbacks themselves - your decision logic. - Configuration objects (
StrategyConfig,LiveDataClientConfig,BacktestEngineConfig). - The user’s
main()/ launcher that builds aLiveNode/BacktestNode. - ML inference (sklearn, PyTorch, NumPy) inside an actor - the model itself dominates, and the PyO3 boundary cost is dust.
The developer-guide page is explicit:
“Python is mandatory for strategy logic, configuration, orchestration, the user-facing API. Anything user-replaceable. … The PyO3 boundary swallows roughly all of the observable per-event cost relative to actual decision logic.”
When does Cortana have to drop to Rust?
This is the load-bearing question for the spike. The honest answer:
Path 1: Cortana on v1 legacy (the spike target)
Cody / Codex never has to write Rust. Period.
The full Cortana surface - composite scoring, meta-labeling classifier, position management, TP/SL fallback, IBKR routing - is expressible as:
- One or more
Actorsubclasses (Python) for scoring + signal publishing - One
Strategysubclass (Python) for entry/exit and order placement - Python configs for the
LiveNodeand the IBKRLiveDataClientConfig/LiveExecClientConfig
The IBKR adapter ships in v1 legacy. UW becomes either a custom Python
data ingestor publishing as CustomData to the bus, or - if Cortana ever
needs sub-millisecond UW parsing - a Rust adapter (see Path 3 below).
Path 2: Cortana scoring engine - does Python keep up?
For 0DTE scoring at one-second tick cadence on a single underlying (SPY), the answer is yes, Python keeps up easily. Decision logic runs at human timescales (signal cadence: seconds, not nanoseconds). The Rust engine handles all the per-tick plumbing; the Python callback is called only when there’s a quote/bar/score-update to react to.
Threshold for a Rust drop-down inside the scoring engine: if Cortana
ever needs to inspect every NBBO tick on a deep chain (50+ option
contracts, each ticking 5-50 Hz), the per-tick PyO3 crossing starts to
matter. At that point the right move is not “rewrite the strategy in
Rust” - it’s “publish a Rust-side aggregator that emits one
ScoreUpdate per N ticks, and the Python strategy subscribes to the
already-aggregated stream.” This is exactly the actor pattern the
developer guide recommends.
Path 3: Rust drop-down for a UW adapter
Per nautilus-developer-guide.md, first-class adapters are layered:
“The Rust layer owns HTTP/WS networking, request signing, rate limiting, and parsing. The Python layer owns the engine-facing interface (
LiveDataClient,LiveMarketDataClient,LiveExecutionClient,InstrumentProvider) and the configuration.”
A “fully Nautilus-native” UW adapter therefore has a Rust crate at
crates/adapters/unusual_whales/ plus Python wiring at
nautilus_trader/adapters/unusual_whales/. The Rust side owns the UW
WebSocket client, JSON parsing, retry/rate-limit logic, and the
type-safe Data payload publication.
Is this required for Cortana MK3? No, not at the spike stage. UW can
be ingested entirely in Python - a Python actor that polls UW REST and
subscribes to the UW WebSocket, normalizes events into a custom
@customdataclass, and publishes to the message bus. This is
explicitly second-class per the developer guide (“for new work, the
Rust + PyO3 stack is the supported path”), but it’s functional.
The honest tradeoff: Python UW ingest is faster to ship and lets Codex own the work end-to-end. Rust UW ingest is the eventual right answer for a SaaS with tens of tenants - but it is not blocking the spike or the early MK3 build.
Path 4: Rust drop-down for sub-millisecond hot paths
Per the developer guide, Rust is mandatory for:
- “Anything in the data engine hot path (bar building, book maintenance, tick routing).” - Already Rust. Cortana doesn’t reimplement this.
- “Parsers and codecs (JSON, FIX, WebSocket frames, Arrow IPC).” - Already Rust for shipped adapters. Cortana would only need this for a custom adapter (UW).
- “Matching logic, fill simulation, accounting math.” - Already Rust. Cortana doesn’t touch this.
- “Anywhere you’d otherwise hit
tokio::spawnfrom a hot loop.” - Already Rust. Cortana doesn’t writetokiocode.
So the candidate Cortana hot paths that might warrant Rust:
- UW parsing in a high-flow regime. If UW WebSocket emits 1000+ events/second during a flow burst and we need to parse-and-publish with sub-ms latency, Rust adapter pays off.
- Greeks computation across a 0DTE chain. Already Rust in
nautilus-model(Black-Scholes lives there). Cortana subscribes, doesn’t compute. - Custom indicator on tick data. If we ever want a sub-bar
microstructure indicator (e.g., volume imbalance per 100ms window),
the indicator would ideally live in the Rust
indicatorscrate. Defer until there’s a working strategy and we’ve measured the Python callback cost.
The single most likely Rust drop-down for Cortana MK3
The UW data adapter. Reason: it’s the one piece of plumbing where (a) latency directly affects “early and right” (per the project mandate), (b) we control the schema, and (c) the Nautilus pattern explicitly puts the parser in Rust. Everything else stays Python until profiling says otherwise.
Estimate (per developer guide phasing): a non-Rust developer with Codex pairing can land a baseline UW Rust adapter in 4-7 phased steps (Rust core → instruments → market data → exec [N/A for UW] → configuration → testing). Cody doesn’t author the Rust; Codex does. The review-and-iterate loop is what Cody owns.
The panic = abort philosophy
Per nautilus-architecture.md and the Rust concept page, the framework
runs panic = abort in release builds. Rationale:
“In production deployments, the system is typically configured with
panic = abortin release builds, ensuring that any panic results in a clean process termination that can be handled by process supervisors or orchestration systems.”
What panics in Rust:
- Programmer errors (logic bugs, incorrect API usage).
- Data that violates fundamental invariants (negative timestamps, NaN prices).
- Arithmetic that would silently produce incorrect results.
What returns Result/Option instead:
- Network errors, file I/O.
- Order constraints, risk limits.
- User input validation.
For Cortana, the practical implication: if the engine panics, the process dies. Launchd / systemd / supervisor restarts. This is the crash-only design. Cortana’s MK2 launchd sequence already restarts on exit, so the deploy shape is compatible.
What this means for our Python strategy code: panics from Rust
internals abort the interpreter. From the developer guide: don’t use
pytest.raises(BaseException) against PyO3 panic paths in
python/tests/ - debug builds may pass, release wheels abort the
process. Test panics in Rust unit tests, not Python tests.
FFI memory contract (relevant if anyone ever touches the boundary)
From the developer guide. Cortana likely never needs to touch this, but it’s the rule for any Rust↔Python boundary:
- Rust panics must never unwind across
extern "C". Wrap every exported symbol incrate::ffi::abort_on_panic(|| { ... }). CVecis arepr(C)thin wrapper aroundVec<T>passed by value. Receiving Cython code calls the type-specific drop helper exactly once.- New PyO3 capsules must use
PyCapsule::new_with_destructor- never the no-destructor variant.
This is the kind of detail Codex would handle if a Rust adapter ever needs an FFI boundary; not something the trader-author touches.
The contributor build path (only matters if you hand-build from source)
End-users pip install and never touch this. If we ever need to build
from source (custom branch, platform with no wheel, fork), the steps
are:
# Rust toolchain
curl https://sh.rustup.rs -sSf | sh
source $HOME/.cargo/env
# Linux build deps (macOS skips this)
sudo apt-get install clang lld
# uv (Python package/env manager)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Clone + sync
git clone --branch develop --depth 1 https://github.com/nautechsystems/nautilus_trader
cd nautilus_trader
uv sync --all-extrasmacOS Sequoia ARM64 is supported (per
nautilus-getting-started.md). MSRV is Rust 1.95.0. PyO3 environment
variables are required for the in-tree build.
For Cortana, build-from-source would only matter if we forked the IBKR adapter - and that hasn’t come up.
Running Rust components (three paths)
Per the Rust page, native Rust strategies/actors can run via:
- Pure Rust binary.
cargo build, no Python. Strategy andmainin Rust. Standalone executable. Not relevant for Cortana on v1. - Native config from Python. Pass a Rust strategy config from
Python via
node.add_native_strategy(config). The Rust side constructs the strategy; Python provides config; execution is all Rust. Built-in configs ship forEmaCross,GridMarketMaker,DeltaNeutralVol. Available on v2 PyO3 only - not v1 legacy. - Plugin loading (planned, not yet shipped). A future plugin
system will load compiled
cdylibstrategies at runtime without recompilation.
Since Cortana is on v1 legacy, none of these apply. Cortana strategies are pure Python on the Cython framework, calling into Rust libs through the static link.
v1 legacy vs v2 PyO3 - the migration question
Eventually v1 Cython retires (the page hints: “Cython is no longer used for new work” - paraphrased; the v2 paths are explicitly the future). When v2 PyO3 ships IBKR support, the right move is to migrate.
For the spike: start on v1 legacy (it’s the only path that runs
IBKR), but write strategies in a way that survives the v1→v2-PyO3
migration. The good news: per the docs, “the domain model is shared
across all paths.” Order, Position, Quantity, Price mean the
same thing in v1 and v2 PyO3. Strategy callback signatures are
identical. The migration cost is config plumbing, not strategy
rewrites.
Cortana MK3 implications
Honest yes/no on whether Cortana MK3 ever requires Cody / Codex to write Rust: No, not required. The full Cortana surface ships in Python on top of the Rust core. The IBKR adapter is already Rust. The MessageBus, Cache, RiskEngine, ExecutionEngine, and option Greeks math are already Rust. Cortana adds Python actors and one Python strategy.
Is there a Cortana hot path most likely to motivate a Rust
drop-down? Yes - the UW data adapter. If/when UW ingest needs to
parse and publish faster than Python can keep up under flow bursts, the
right move is a Rust crate at crates/adapters/unusual_whales/ plus
Python factory wiring. Codex authors the Rust under Cody’s review. This
is deferred until the Python UW ingestor is in place and we’ve
measured actual ingestion latency under load.
Everything else stays Python forever - strategy logic, scoring, meta-labeling, position management, ML inference, dashboard, alerting, brain integration. The PyO3 boundary cost is invisible compared to decision-logic latency, and the framework explicitly designs for Python authoring.
Spike DX premise verdict: Confirmed. “Python-first; Rust core fades into the background” is exactly what the framework is engineered for. Cody + Codex drive the spike entirely in Python.
See Also
- Nautilus Architecture
- Nautilus Developer Guide
- Nautilus Getting Started
- 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 3.