nautilus-plugin 0.58.0

Plug-in system for the Nautilus trading engine
Documentation

nautilus-plugin

build Documentation license Discord

Plug-in system for NautilusTrader.

[!WARNING] Early alpha. The ABI and public API are not stable and may change between versions while the host-side adapters, HostVTable surface, and LiveNodeConfig wiring land. Plug-in builds must be pinned to the matching Nautilus version.

The nautilus-plugin crate defines the C-ABI boundary between a Nautilus host (the live node) and independently compiled Rust plug-in cdylibs. The host dlopens a plug-in, calls a single nautilus_plugin_init entry symbol, and registers every plug point the returned manifest enumerates after validating that the manifest is structurally well-formed. The boundary is C ABI because Rust's #[repr(Rust)] layout is unstable across compilations, so cross-cdylib Box<dyn Trait> and async fn are unsound. C ABI is the layer of contract both halves can compile to without sharing a build.

Authors write normal Rust. The nautilus_plugin! macro emits the extern "C" symbol, the #[repr(C)] manifest, and the per-type vtables; authors never type extern "C", #[repr(C)], or unsafe themselves.

Vtable slots are nullable in the ABI structs so the host can report malformed manifests with missing callbacks. Macro-generated vtables fill every required slot, and the loader rejects null slots before registration or instantiation.

The plug-in system supports the following sync trait surfaces. Each lives in its own module under src/ and follows the same pattern: a #[repr(C)] vtable, a matching author-facing trait, extern "C" thunks wired through panic::guard, and a Slice<'static, Registration> field on PluginManifest. Adding a plug point means adding one module and one Slice field.

Plug point Status Module
Custom data type Shipped surfaces::custom_data
Actor / DataActor Shipped surfaces::actor
Strategy Shipped surfaces::strategy
Execution algorithm Not yet surfaces::exec_algorithm
Indicator Not yet surfaces::indicator
Fill model Not yet surfaces::fill_model
Pricing / greeks Not yet surfaces::pricing

Out of scope: async client adapters (data and execution), catalog and cache backends, pre-trade risk gating, event store backends, mutable host order book state, native or Python custom-data callbacks that are not backed by a plug-in vtable, and hot reload of any plug-in while the live node is running. Plug-ins load at process startup and live for the process lifetime.

OrderBook crosses the boundary via OrderBookHandle, a #[repr(C)] wrapper owned by the host that boxes a cloned book snapshot for the duration of the callback. The plug-in's thunk dereferences the handle once and hands a borrowed &OrderBook to the trait method; the plug-in never receives mutable host book state.

OrderBookDeltas cross the boundary via OrderBookDeltasHandle, a #[repr(C)] wrapper owned by the host that boxes the deltas for the duration of the callback. The plug-in's thunk dereferences the handle once and hands a borrowed &OrderBookDeltas to the trait method; matches the ownership contract OrderBookDeltas_API uses on the Cython side.

InstrumentAny crosses the boundary via InstrumentAnyHandle, a #[repr(C)] wrapper that owns a boxed InstrumentAny for the duration of the callback. InstrumentAny is a Rust enum whose variant payloads own heap-allocated fields, so the same boundary-owned shape applies: the plug-in's thunk dereferences the handle once and hands a borrowed &InstrumentAny to the trait method.

OptionChainSlice crosses the boundary via OptionChainSliceHandle for the same reason: the snapshot owns BTreeMap<Price, OptionStrikeData> call and put maps and cannot be #[repr(C)]. The host boxes the slice in the handle for the duration of the callback.

Custom data registered through PluginCustomData can flow into actor and strategy on_data callbacks. The host wraps each decoded value in a plug-in-owned handle plus vtable, then passes a borrowed PluginCustomDataRef to the cdylib. Plug-in code can inspect the type name and downcast to its concrete type locally. The host rejects custom data that did not come from a plug-in registration, because those values do not carry the vtable and handle needed for that downcast.

Historical plug-in custom-data responses use the same boundary only when the value came from a PluginCustomData registration. The host inspects &dyn Any only inside the adapter, extracts registered plug-in CustomData, and calls the existing on_data slot with PluginCustomDataRef. No &dyn Any value crosses the cdylib boundary.

Identifier interning

Nautilus identifier types such as ClientOrderId, InstrumentId, ClientId, AccountId, PositionId, StrategyId, and TraderId wrap Ustr. A Rust cdylib has its own ustr global string cache, so equal text can have different Ustr pointers on the host and plug-in sides. The plug-in boundary treats Ustr values as receiver-local:

  • Host command dispatch re-interns every identifier in boundary-owned command handles before calling Strategy::*.
  • Plug-in event thunks re-intern identifiers in inbound event payloads before calling PluginActor or PluginStrategy trait methods.
  • Plug-in authors can compare and store identifiers received through trait callbacks normally. Code that bypasses the macro-generated thunks must re-intern copied identifiers with Ustr::from(value.as_str()).

The policy also covers nested identifiers such as Symbol, Venue, OrderListId, ExecAlgorithmId, VenueOrderId, OptionSeriesId, raw Ustr tags and names, and currency codes carried inside command or event payloads.

NautilusTrader

NautilusTrader is an open-source, production-grade, Rust-native engine for multi-asset, multi-venue trading systems.

The system spans research, deterministic simulation, and live execution within a single event-driven architecture, providing research-to-live semantic parity.

Feature flags

This crate provides feature flags to control source code inclusion during compilation:

  • host: Enables host-side plug-in loading via libloading. The live node enables this feature; plug-in cdylibs leave it off so they do not link libloading themselves.

Platform

Plug-in cdylibs use the platform-native shared-library format:

  • Linux: lib<name>.so
  • macOS: lib<name>.dylib
  • Windows: <name>.dll

libloading handles the platform differences on the host side; the example test under tests/load_example_cdylib.rs builds and loads a cdylib on all three.

Rust's ABI is unstable across compilations on every platform, so the plug-in build must be pinned to the host's Rust toolchain version and Nautilus version. Each manifest carries a versioned PluginBuildId with the nautilus-plugin crate version, Rust compiler version when the build script can read it, target triple, build profile, and fixed-point precision mode. The loader includes that build identifier in ABI mismatch diagnostics so operators can spot stale or cross-built cdylibs.

The loader validates the build-id schema version and fixed-point precision mode because precision changes model layout at the plug-in boundary. The specific crate version, compiler version, target triple, and build profile values remain diagnostic.

NAUTILUS_PLUGIN_ABI_VERSION and PLUGIN_BUILD_ID_VERSION remain 1 while this early-alpha surface is unreleased and unstable. During this phase, those values do not promise compatibility between Nautilus versions. Rebuild plug-in cdylibs to match the exact host version. The host refuses to load a plug-in whose PluginManifest::abi_version does not match, and manifest validation rejects fixed-point precision mode mismatches.

Documentation

See the docs for more detailed usage.

License

The source code for NautilusTrader is available on GitHub under the GNU Lesser General Public License v3.0.


NautilusTrader™ is developed and maintained by Nautech Systems, a technology company specializing in the development of high-performance trading systems. For more information, visit https://nautilustrader.io.

Use of this software is subject to the Disclaimer.

© 2015-2026 Nautech Systems Pty Ltd. All rights reserved.