Nightly docs
Releases
This guide covers the release process and the standards for writing release notes.
Overview
NautilusTrader uses a three-branch model:
develop: active development; publishes dev wheels to Cloudflare R2 on every push.nightly: pre-release testing; publishes all supported pre-release wheels and CLI binaries.master: stable releases; triggers the full release pipeline.
Merging a release commit to master automatically tags the version from python/pyproject.toml,
creates a draft GitHub release, uploads release assets, publishes Cargo crates to crates.io,
publishes wheels and sdist to PyPI, publishes the GitHub release, builds Docker images, and triggers
a docs rebuild.
Stable release workflow
The build workflow treats the GitHub release as the anchor for stable releases. It creates the
release as a draft first, uploads the wheel and sdist assets to that draft release, and only then
publishes those packages to package indexes. The workflow publishes the GitHub release only after
the registry verification and final integrity assets are complete.
Keep these sequencing rules intact when editing .github/workflows/build.yml:
- The draft GitHub release must exist before any release asset upload or package registry publish.
tag-releasemust depend onsecurity-auditso stable release tagging cannot proceed after an audit failure.- Wheel and sdist assets must be attached to the GitHub release before package index publishing
starts (
packages.nautechsystems.io, PyPI, crates.io). - PyPI and crates.io Trusted Publishing jobs must keep
environment: releaseandid-token: write; those registrations depend on thereleaseenvironment. - Non-OIDC integrity and asset-upload jobs should avoid
environment: releaseunless they need release environment secrets or approvals. publish-release-integritymust run after PyPI and crates.io publishing. It generates the release manifest first, verifies registries against that manifest, then attaches final integrity assets only after verification passes.publish-github-releasemust be the final stable release job. GitHub recommends creating a draft release, attaching all assets, then publishing the draft before enabling release immutability. Once GitHub release immutability is enabled for the repo, published release assets and the release tag cannot be changed; only the title and release notes remain editable. The job verifies the final draft asset set before publishing and verifies GitHub's release attestation after publishing the draft.
Recovering a missing release tag
The Create git tag step creates v<version> only when the version at the run's commit differs
from the version at the previous commit (HEAD~1), and never when the tag already exists. A run
whose commit carries the same version as its parent therefore never creates the tag, and when the
tag is missing Verify release tag fails no matter how often the failed jobs are re-run. This is
the normal shape of a retried release: one or more commits land on master after the version
bump, so the retry run carries the same version as its parent. Because tag-release anchors the
draft release and every downstream publish job, the run cannot proceed while the tag is missing.
First check whether the tag exists and whether the run's commit bumps the version:
- If the tag is missing and the run's commit bumps the version, re-running the failed jobs is
enough: once the failed jobs pass,
tag-releasecreates the tag. - If the tag is missing and the run's commit carries the same version as its parent, no re-run creates the tag. Recover with the manual tag procedure below.
Manual tag recovery works because of the remaining tag-release mechanics:
- On a re-run after the manual push,
Create git tagskips creation under the same rule. Verify release tagrequiresv<version>to resolve to the run's commit (GITHUB_SHA), so a manually pushed tag at that exact commit passes the check.- Draft release creation is idempotent, so a re-run updates the existing draft if one was already created.
To recover, a maintainer creates the tag manually and re-runs the failed jobs:
-
From the failed run page, note the run's commit SHA. Check out that commit locally and confirm the version it carries:
./scripts/package-version.sh -
Create a signed annotated tag at the run's exact commit, using the existing tag message convention, and push it:
git tag -s v<version> -m "Released version <version>" <run-commit-sha> git push origin v<version> -
Verify the tag locally. The signature must be good and
git rev-parse 'v<version>^{commit}'must equal the run's commit SHA:git tag -v v<version> git rev-parse 'v<version>^{commit}' -
On the failed run, select "Re-run failed jobs".
Verify release tagpasses with the pushed tag, and draft release creation and the downstream publishing jobs continue.
Only do this when the release commit and version are final. The tag is the permanent release
anchor, and once release immutability applies the published release tag cannot be changed. If
v<version> already exists but points at a different commit, do not move or re-push the tag;
investigate the mismatch instead, because Verify release tag fails in that case by design.
Versioning
The project maintains two version numbers:
| File | Scope |
|---|---|
python/pyproject.toml | Python package |
Cargo.toml (workspace) | Rust crates |
These are bumped independently. The Python version drives the v<python-version> release tag.
Versions ending in aN, bN, or rcN create a GitHub pre-release; final versions create a normal
release.
Crates.io publishing
The build workflow publishes Cargo crates from the publish-cargo-crates job. The job uses
crates.io Trusted Publishing through GitHub Actions OIDC, so it does not use a persistent cargo
token. Configure each crate on crates.io with:
| Field | Value |
|---|---|
| Owner | nautechsystems |
| Repository | nautilus_trader |
| Workflow | build.yml |
| Environment | release |
Enable Trusted Publishing Only for crates after their trusted publisher is configured. Crates that have never been published still need an initial manual publish before crates.io allows the trusted publisher configuration.
Do not use cargo publish --workspace for CI releases. The release job runs
scripts/ci/publish-cargo-crates.sh, which publishes crates one at a time in dependency order,
skips versions already present on crates.io, and waits for each new version to appear in the
crates.io API and sparse index before publishing dependents. The script fails before uploading if a
publishable crate depends on a local publish = false crate that is absent from crates.io.
Optional local dependencies count as blockers because publishing a public feature that resolves to
an absent crate would leave that feature unusable.
Post-publish verification treats an existing crate version as previously_published only when
crates.io shows it was trusted-published by this repository. It still fails for user-published
crate versions unless CRATES_IO_MANUAL_PUBLISH_EXCEPTIONS names each recovered crate@version
entry for emergency token-publish recovery. Accepted manual entries are recorded in
crates-manifest.json with release_status: "manual_token_publish", and malformed or unused
exception entries fail the job. Wrong trusted-publishing repositories and checksum or sparse-index
mismatches also fail.
Release checklist
Pre-release (on develop)
- Finalize
RELEASES.md: review all items, remove empty sections - Ensure versions are set in
python/pyproject.tomland theCargo.tomlworkspace - Ensure crates.io Trusted Publishing is configured for every crate that CI publishes:
bash scripts/ci/check-crates-io-trusted-publishing.sh - Ensure all CI checks pass on
develop
Release
- Merge
developintonightly, verify nightly CI passes - Merge
nightlyintomaster - Verify the
buildworkflow completes:- Wheels built for Linux x86/ARM, macOS, Windows
cargo-denyandcargo-vetpasssecurity-auditpasses its Zizmor and supply-chain checks- Release docs/features and Cargo publish preflights pass before tagging
- Tag and draft GitHub release created
- Wheels and sdist attached to the GitHub release before package registry publishing
- Cargo crates published to crates.io or skipped because the version already exists
- Wheels and sdist published to PyPI
- Registry verification passes before release checksums, crates manifest, and attestation siblings are attached
- GitHub release published after all release assets and integrity assets are attached
- If the run fails with the release tag missing, follow Recovering a missing release tag and re-run the failed jobs
- Verify the
dockerworkflow completes (images built and pushed) - Verify the
build-docsworkflow completes (docs rebuild triggered)
Post-release (on develop)
- Update the release date in
RELEASES.mdfor the published version - Add horizontal separator
---below the completed release - Add the next version template at the top of
RELEASES.md(see below) - Bump
python/pyproject.tomlversion to the next release number - Bump crate versions in tutorial and how-to
Cargo.tomlsnippets (docs/concepts/rust.md,docs/how_to/run_rust_backtest.md,docs/how_to/run_rust_live_trading.md)
Release notes
This section documents the standards for writing release notes in RELEASES.md.
Sections
Use the following sections in this order:
- Enhancements
- Breaking Changes
- Security
- Fixes
- Internal Improvements
- Documentation Updates
- Deprecations
Omit sections that have no items for a given release.
Enhancements
New features and user-visible improvements.
Format:
- Added `subscribe_order_fills(...)` and `unsubscribe_order_fills(...)` for `Actor`
- Added Bybit conditional orders support
- Added support for `OrderBookDepth10` requests (#2955), thanks @faysouGuidelines:
- Start with "Added".
- Use backticks for code elements.
- Be specific about what was added, not how.
Breaking Changes
Changes that may break existing code.
Format:
- Removed `nautilus_trader.analysis.statistics` subpackage - must import from `nautilus_trader.analysis`
- Renamed `BinanceAccountType.USDT_FUTURE` to `USDT_FUTURES`
- Changed `start` parameter to required for `Actor` data request methodsGuidelines:
- Start with "Removed", "Renamed", or "Changed".
- Explain migration path briefly.
Security
Security hardening and fixes that prevent crashes, undefined behavior, or data corruption. Includes significant hardening improvements elevated from Internal Improvements.
Format:
- Fixed divide-by-zero and overflow bugs in model crate that could cause crashes
- Fixed core arithmetic operations to reject NaN/Infinity values and improve overflow handlingGuidelines:
- Include overflow/underflow fixes, memory safety improvements, FFI guards, data integrity fixes.
- Focus on user impact: what could have happened.
- Exclude routine dependency updates, minor hardening, or test-only fixes.
- Omit this section entirely if there are no security items for the release.
Fixes
Bug fixes that improve correctness but don't qualify as security issues.
Format:
- Fixed reduce-only order panic when quantity exceeds position
- Fixed Binance order status parsing for external orders (#3006), thanks for reporting @bmlquantGuidelines:
- Start with "Fixed".
Internal Improvements
Implementation details and infrastructure changes.
Format:
- Added ARM64 support to Docker builds
- Ported `PortfolioAnalyzer` to Rust
- Improved clock and timer thread safety
- Upgraded Rust (MSRV) to 1.90.0
- Upgraded `pyo3` crates to v0.26.0Guidelines:
- Use "Added", "Implemented", "Improved", "Optimized", "Upgraded", "Refined", "Standardized".
- Include version numbers for dependency upgrades.
Documentation Updates
Changes to guides and examples.
Format:
- Added rate limit tables with links to official docs
- Improved dark and light themes for readability
- Fixed broken linksDeprecations
Features marked for removal.
Format:
- Deprecated `some_config_option`; disable (`False`) to maintain consistent behavior. Will be removed in future versionGuidelines:
- Explain migration path and provide alternatives.
Attribution
- Credit external contributors:
thanks @usernameorthanks for reporting @username. - Include issue/PR numbers for community contributions and complex features:
(#1234).
Style
- Use sentence case (capitalize first word only).
- Do not end with periods.
- Use backticks for code elements.
- Focus on what changed, not how.
Be specific:
Bad: Improved Binance adapter
Good: Improved Binance fill handling when instrument not cachedSecurity classification
Include in Security if the change addresses:
- Memory safety (overflow, underflow, divide-by-zero that threatens stability).
- Undefined behavior or crashes that could corrupt state.
- Data integrity (NaN/Infinity propagation, race conditions leading to corruption).
- Input validation preventing injection or exploitation (SQL injection, command injection, path traversal).
- Build hardening (non-exec stack, FFI guards).
- Significant hardening that users should know about.
Otherwise use Fixes (for logic bugs and panics) or Internal Improvements (for minor hardening).
Note: Plain logic panics belong in Fixes unless they threaten system stability or data corruption.
Examples
Security (could cause crashes/corruption):
- Fixed divide-by-zero in margin calculations that could crash the engine
- Fixed integer overflow in model arithmetic that could crash the processFixes (incorrect but safe):
- Fixed Binance order status parsing for external orders
- Fixed position purge logic to prevent purging re-opened positionEnhancements (user-facing):
- Added Bybit conditional orders supportInternal (implementation):
- Implemented Bybit ping/pong handlingRelease notes template
# NautilusTrader <VERSION> Beta
Released on TBD (UTC).
### Enhancements
### Breaking Changes
### Security
### Fixes
### Internal Improvements
### Documentation Updates
### Deprecations
---