Nautilus Trader - Releases, Versioning & Upgrade Discipline

Nautilus ships from a three-branch model - develop (dev wheels to Cloudflare R2), nightly (alpha wheels + CLI binaries), master (PyPI + Docker + docs rebuild) - with two independent version numbers: the Python package (pyproject.toml, currently 1.226.0 as of 2026-04-29) and the workspace Rust crates (Cargo.toml, currently 0.55.0-class). The Python version drives the release tag (v1.226.0) and the publish pipeline. Empirically, minor versions ship roughly monthly with breaking changes in nearly every release; the leading 1. digit is structurally pinned - the 1.22x.0 cadence is “continuous beta,” not semver-stable. Breaking changes are surfaced in a structured RELEASES.md with seven canonical sections (Enhancements, Breaking Changes, Security, Fixes, Internal Improvements, Documentation Updates, Deprecations); each “Removed” / “Renamed” / “Changed” entry includes brief migration guidance. Deprecations name the replacement and announce removal “in a future version.” Security items (memory safety, undefined behavior, data integrity, input validation, build hardening) are split out from plain-logic Fixes. The practical consequence for any 4-6 month build on top of Nautilus is that you will eat at least 4 minor bumps with breaking changes during the build window - pin hard at milestone boundaries; never auto-track latest in production.

Core claim

Nautilus’s release process is transparent (structured changelog, named breaking changes, deprecation guidance) but fast (roughly monthly minors, breaking changes nearly every release, no LTS lane). The transparency is load-bearing: you can read each release’s “Breaking Changes” block and decide to skip or migrate. The speed means you must pin and treat every upgrade as a deliberate engineering task, not a pip install -U afterthought. There is no “stable” channel that sits behind the bleeding edge - master is the release branch.

Branch model

Three branches, each with its own publish pipeline.

BranchWheels published toTriggerUse case
developCloudflare R2 (dev wheels)Every pushActive development; bleeding edge
nightlyCloudflare R2 (alpha wheels + CLI binaries)Pre-release testingPre-release validation, RC channel
masterPyPI + Docker Hub + docs siteMerge from nightlyStable releases; what pip install nautilus_trader pulls

Pushing to master automatically: tags the version from pyproject.toml, creates a GitHub release, publishes wheels and sdist to PyPI, builds Docker images, triggers docs rebuild. Cortana production must always pull from PyPI (master); never from the Cloudflare R2 dev/alpha wheels.

Versioning policy - two numbers, one tag

“These are bumped independently. The Python version drives the release tag (v1.223.0).”

  • Python package (pyproject.toml): 1.226.0 as of 2026-04-29. The leading 1. is structurally pinned - this is what they actually ship - and the middle digit is the only one that moves in normal releases. Despite the 1.x.y shape, this is not semver. Breaking changes ship in minor bumps as a matter of course.
  • Rust crates (Cargo.toml workspace): 0.55.0-class. End-user-facing but only relevant to contributors compiling from source. Pinning the Python wheel pins both, since the wheel statically links the matched Rust artifacts.

The two numbers are decoupled to let core Rust crate semver mature separately from the Python distribution’s release cadence. For Cortana, the Python version is the only one that matters for pinning.

Pinning syntax for Cortana (lock to a specific Python release):

# pyproject.toml under MK3
[project]
dependencies = [
    "nautilus_trader[ib]==1.226.0",  # exact pin, no range
]

Do not use ~=1.226.0 (PEP 440 compatible-release) or >=1.226,<2.0. Both let minor bumps slip in, and minor bumps ship breaking changes.

Release cadence - empirical

The releases doc page does not specify a cadence. Empirical pull from the GitHub releases page (sampled 2026-05-07):

VersionRelease dateGap from prior
1.226.02026-04-2923 days
1.225.02026-04-0634 days
1.224.02026-03-0310 days
1.223.02026-02-2151 days
1.222.02026-01-0167 days
1.221.02025-10-26-

Headline: ~6 minor releases over ~6 months → roughly one minor per month during steady-state; gaps of 1-2 months between releases when the team is in a deeper internal-improvement window (the Oct 2025 → Jan 2026 gap). Every release in this window contains a “Breaking Changes” section.

There is no observed major-version bump in this window - the leading 1. has been pinned for the whole sample. There is also no observed LTS branch or back-port lane: when a Fix lands, it lands on develop and ships in the next minor. If you’re on N-3 and a security fix lands on N, you have to upgrade to get it.

Changelog conventions - the seven sections

RELEASES.md (and the corresponding GitHub Release body) is structured into seven sections, in this exact order:

  1. Enhancements - new features, improvements that don’t break callers.
  2. Breaking Changes - Removed, Renamed, Changed entries, each with brief migration guidance.
  3. Security - memory safety issues, undefined behavior, data integrity fixes, input validation, build hardening. Plain logic panics belong in Fixes “unless they threaten system stability or data corruption.”
  4. Fixes - regressions, bug fixes, edge cases.
  5. Internal Improvements - refactors, performance work that doesn’t change the API surface.
  6. Documentation Updates.
  7. Deprecations - Deprecated <feature>; <action>. Will be removed in future version.

Sections with no items are omitted.

Style is enforced: sentence case (capitalize first word only), backticks for code elements, focus on what changed (not implementation detail), include issue/PR numbers, credit contributors with thanks @username.

Breaking-change signals

Every breaking change is marked with one of three explicit verbs:

  • Removed - the feature is gone. Migration path required.
  • Renamed - the symbol moved. Migration is mechanical (find/replace) but you have to do it.
  • Changed - semantics shifted. Read carefully; this is the most dangerous category because code may keep compiling but produce different behavior.

Each entry includes “explanation of migration path briefly.” There is no machine-readable schema for breaking changes - you read the text.

Deprecation policy

Deprecations are announced with this shape:

Deprecated <feature>; <action>. Will be removed in future version.

The deprecation entry includes:

  • The replacement API.
  • A brief explanation of why.
  • A “will be removed in future version” notice - but no specific version number. The lead time is at the maintainers’ discretion.

Practical implication: if you see a feature you depend on appear under “Deprecations”, you are on the clock - but the clock has no documented length. Plan to migrate within the next 1-2 minor bumps to be safe; do not let deprecated calls accumulate.

Security release process

Security items live in their own changelog section, separate from Fixes. The boundary, verbatim:

“Plain logic panics belong in Fixes unless they threaten system stability or data corruption.”

So if a panic could corrupt cache state, screw an account ledger, or take down the kernel, it goes in Security. If it just bombs the strategy callback, it goes in Fixes. There is no separate security advisory mailing list documented; security fixes ship in the normal minor releases on the regular cadence. There is no security back-port lane - if you need the fix, you take the whole minor.

For Cortana, this means: monitor the RELEASES.md Security section on every release, and don’t sit on a known security fix waiting for a “convenient” upgrade window.

Upgrade-path documentation

Per release, the upgrade path is the union of:

  • The Breaking Changes section’s per-entry migration notes.
  • The Deprecations section’s named replacements.
  • The Documentation Updates section’s pointer to any reworked guides.

There is no separate MIGRATING.md or per-version migration guide. You walk the changelog from your pinned version forward, applying changes release by release.

For a 4-week-old pin upgrading to current, that’s roughly 1 release worth of migration. For a 4-month-old pin, that’s 4 releases - significantly more painful, and the “Changed” entries (silent semantic shifts) compound.

Cortana MK3 implications

This is where this page earns its keep. MK3 is a 4-6 month build (per projects/cortana-mk3-roadmap.md - six milestones, M1 → M6 over Weeks 1-20+). Across that window, Nautilus will ship roughly 4-6 minor releases, each with breaking changes. We need an explicit pin policy.

PhasePinUpgrade cadenceJustification
Spike (2026-05-09)nautilus_trader[ib]==<latest stable at spike date>None (1-day spike)Latest features, latest fixes; no upgrade pain in a 1-day window.
M1 Foundation (Weeks 1-3)Exact pin to spike versionNone during M1First architecture pass needs a stable target. Don’t chase a moving API while we’re still learning Nautilus’s primitives.
M2 Strategy parity (Weeks 4-7)Same exact pin as M1One controlled upgrade allowed at start of M2 if a needed feature landedStrategy port is the bulk of work - minimize churn. Re-pin once at M2 start if there’s a compelling reason.
M3 Single-tenant prod (Weeks 8-9)Re-pin to current stableOnce at start of M3Production cutover is the right moment to absorb 2-3 minor versions of debt and validate against MK2 in parallel.
M4 Multi-tenant scaffolding (Weeks 10-13)Same M3 pinNoneMulti-tenant work is enough complexity on its own.
M5 Onboarding flow (Weeks 14-19)Re-pin once at M5 startOnce at start of M5Customer-facing surface - pull in any security fixes before exposing to even one stranger.
M6 First external customer (Week 20+)Stay on M5 pin until first quiet weekendConservative - only upgrade when the new release contains a fix or feature MK3 actually needsBeta tester is live. Stability over freshness.

Default cadence: re-pin at each milestone gate, not within a milestone. That gives ~4 controlled upgrades across the 4-6 month build, each at a moment when we’re already validating end-to-end and can tolerate breakage.

How to monitor for breaking changes

  1. GitHub releases RSS feed: subscribe to https://github.com/nautechsystems/nautilus_trader/releases.atom. Wire into the brain’s update pipeline OR a personal feed reader. New release triggers a 5-minute “read the Breaking Changes section” task.
  2. GitHub watch (custom): in the repo, Watch → Custom → Releases. Email on every release; treat each one as a triage event.
  3. Pre-upgrade ritual: before any pin bump, read every intervening release’s RELEASES.md end-to-end. Note every “Removed”, “Renamed”, “Changed” that touches code Cortana actually uses. Estimate migration time before committing.
  4. Post-upgrade ritual: after pin bump, run the full backtest replay suite against decisions.db golden output. Numerical drift on golden trades is the canary for “Changed” entries we missed.

The “Nautilus 2.0 ships during M3” scenario

Empirically, the leading 1. has been pinned across the entire 6-release sample (Oct 2025 → Apr 2026). There is no public roadmap for a 2.0 visible in the releases doc. But the project explicitly carries a “beta” designation in its release notes, and the changelog conventions allow breaking changes in minor bumps without requiring a major bump - meaning the team gets most of the benefit of a 2.0 without ever cutting one. So a 2.0 is unlikely on the M1-M5 timeline.

If a 2.0 does ship mid-build:

  • Don’t upgrade during the milestone in progress. Finish the milestone on the pinned 1.x. The 2.0 changelog is going to be enormous.
  • Audit the 2.0 release notes. If it’s a packaging/repo split with no code-level breakage, upgrade at the next milestone gate. If it’s a fundamental API rework (Strategy/Actor signatures changed, MessageBus topics restructured), treat it as a separate migration project - don’t fold it into a regular milestone gate.
  • Hold the 1.x pin indefinitely if 2.0 deletes features Cortana depends on. This is the worst case; mitigation is to read every Deprecations section as it lands and migrate eagerly so that 2.0 doesn’t catch us using something already deprecated.

Risk accounting

The risk that Nautilus’s release cadence breaks Cortana mid-build is real but bounded. Empirical cadence + structured changelog + named breaking changes + the ability to pin precisely on PyPI = we control the exposure. The irreducible risk is silent “Changed” entries we miss in the release notes; the mitigation is the post-upgrade golden-replay test.

This risk is materially smaller than the risk we already carry on MK2 - which has zero release process, zero versioning, and our own hand-rolled runtime is the moving target. Adopting Nautilus’s cadence is adding discipline, not adopting chaos.

Pre-commit and contribution discipline

Several pre-commit hooks enforce release-relevant conventions:

  • check_anyhow_usage.sh - anyhow usage rules.
  • check_logging_macro_usage.sh - fully-qualified log::info! etc.
  • check_error_conventions.sh - error context strings lowercase.
  • check_tokio_usage.sh - no plain tokio::spawn (use nautilus_common::live::get_runtime().spawn(...)).
  • check_copyright_year.sh - current year LGPL-3.0 header on every Rust file.

PRs that bypass these fail CI. For Cortana contributors filing PRs upstream, these are the conventions to internalize.

What to actually file in our own changelog

When we eventually ship MK3 to customers (M5+), our own CHANGELOG.md should mirror Nautilus’s seven-section structure for our own release notes - specifically calling out the upstream Nautilus version we’re pinned to, so customers can read both changelogs to understand what changed in their trading platform.

## [MK3 0.4.0] - 2026-09-15
 
**Upstream:** nautilus_trader 1.230.0
 
### Breaking Changes
- Removed legacy `cooldown_state.py` shim; migration: ...
 
### Security
- ...
 
### Fixes
- ...

This is a habit to start in M1 and keep the entire project life.

Open threads / Saturday spike check

These should be confirmed during the 2026-05-09 spike:

  1. Confirm pip install "nautilus_trader[ib]==<spike-pin>" resolves cleanly and produces a reproducible wheel (no transitive >= ranges leaking bleeding-edge sub-deps).
  2. Confirm the [ib] extra pins IBKR adapter dependencies tightly - the adapter is the most likely surface for breakage.
  3. Read the most recent 3 release notes end-to-end before pinning. If any contain a Breaking Change in LiveNode, Strategy, RiskEngine, or MessageBus, lean toward the version before that change for M1 foundation work, then absorb at M2 start.
  4. Find the deprecation half-life empirically: pick two recent “Deprecated” entries and count how many releases until they were “Removed”. Sets a rough lead-time expectation for our own migration planning.

See Also


Timeline

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