NautilusTrader
Concepts

Nightly docs

These docs track unreleased changes and may change without notice. Their code examples can use APIs that the latest release lacks, so run them with a development wheel or switch to the latest release docs.

Behavioral Models

This page describes the pluggable models that change or extend NautilusTrader behavior.

A behavioral model supplies the rules for a specific calculation or decision made by the system. For example, a fill model determines simulated fill eligibility and liquidity, while a fee model calculates the commission on a fill. The engine calls the configured model through a defined interface, so users can change these rules without modifying the engine or their strategy.

Models are supplied as objects in configuration or through runtime APIs. Users can select a built-in implementation, configure its parameters, or supply a custom implementation where the model family and API support it. Here, pluggable means that users replace behavior through these interfaces. runtime loading of native libraries is a separate capability.

Model families

FamilyControlsBuilt-in examples
FillSimulated limit-fill eligibility, slippage, and optional synthetic liquidity.DefaultFillModel, BestPriceFillModel, TwoTierFillModel.
FeeCommission calculated from an order, fill, instrument, and optional pricing context.MakerTakerFeeModel, FixedFeeModel, PerContractFeeModel.
MarginInitial order margin and maintenance position margin.StandardMarginModel, LeveragedMarginModel.
LatencySimulated delays for order submission, modification, and cancellation.StaticLatencyModel.

Fill models range from using the recorded book to supplying synthetic liquidity with tiered, partial-fill, size-aware, competition-aware, volume-sensitive, or market-hours behavior. See fill models for the complete built-in list and the effect of book type on fill simulation.

Fee models also include ProbabilityPriceFeeModel, CappedOptionFeeModel, and TieredNotionalOptionFeeModel for probability-priced instruments and option fee schedules.

The margin models select whether instrument margin requirements are reduced by account leverage. StaticLatencyModel adds a base delay to separately configured insert, update, and cancel delays.

Fill and latency models control simulated execution. They do not determine whether or when a live venue fills an order. Model behavior applies at the engine or account boundary that consumes it. Changing a model does not replace order-state validation, execution routing, or the rest of the runtime.

Simulation modules

Simulation modules provide a related extension point for behavior across the simulated exchange lifecycle. Instead of answering a fill, fee, margin, or latency calculation, a module processes exchange state and returns account adjustments. Built-in FX rollover and CFD swap modules use this interface. Custom modules can extend simulation behavior through the same lifecycle and acknowledgement contract.

Supplying implementations

Rust callers can implement the model family's trait and pass the implementation through its runtime handle. Python callers can supply built-in or custom models through these APIs. Model parameters follow the order fill_model, fee_model, margin_model, and latency_model. When available, modules follows the model group. Use keyword arguments to keep calls independent of parameter order.

FamilyBacktestVenueConfigBacktestEngine.add_venue()SandboxExecutionClientConfig
FillBuilt-in models or custom Python models.Built-in models or custom Python models.Built-in models or custom Python models.
FeeBuilt-in models or custom Python models.Built-in models or custom Python models.Built-in models or custom Python models.
MarginBuilt-in models or custom Python models.Built-in models or custom Python models.Built-in models or custom Python models.
LatencyBuilt-in models or custom Python models.Built-in models or custom Python models.Built-in models or custom Python models.

Configuration copies share the supplied custom Python model instance. Its random generator, replay state, and other state persist across runs and engine.reset(). For an independent run, reset that state yourself or construct a new engine with a fresh model.

Custom Python fill models

Pass a FillModel subclass or a compatible Python object as fill_model to BacktestVenueConfig, BacktestEngine.add_venue(), BacktestEngine.change_fill_model(venue, fill_model), or SandboxExecutionClientConfig. See custom fill models for the required methods, optional spread-fill behavior, and liquidity hook.

Custom Python fee models

Implement get_commission(order, fill_quantity, fill_px, instrument) and return a Money commission for each fill. Subclass FeeModel or supply a Python object with this method, then pass the instance as fee_model to BacktestVenueConfig, BacktestEngine.add_venue(), or SandboxExecutionClientConfig.

When the calculation needs the underlying price, implement get_commission_with_context. It receives the same arguments and an additional underlying_px. The engine supplies an underlying price only for CryptoOption and OptionContract instruments when one is available; otherwise, it passes None. Without an override, the engine calls get_commission.

Custom Python margin models

Pass a subclass of nautilus_trader.model.MarginModel or a compatible Python object as margin_model to these APIs. See Python model selection for the callback contract.

Custom Python latency models

Subclass LatencyModel and override all three methods. Each method returns an int delay in [0, 2**64 - 1] nanoseconds, including any base latency; the engine adds none. Boolean values are invalid.

from random import Random

from nautilus_trader.execution import LatencyModel


class SampledLatency(LatencyModel):
    def __init__(self, seed=42):
        self._rng = Random(seed)

    def get_insert_latency(self, command, ts_now):
        return self._rng.choice((1_000_000, 2_000_000, 5_000_000))

    def get_update_latency(self, command, ts_now):
        return self._rng.choice((500_000, 1_000_000))

    def get_delete_latency(self, command, ts_now):
        return self._rng.choice((250_000, 500_000))

Pass an instance as latency_model to BacktestVenueConfig, BacktestEngine.add_venue(), or SandboxExecutionClientConfig. Custom objects that do not subclass LatencyModel raise TypeError. An unimplemented callback raises NotImplementedError when called and triggers the failure handling below.

In backtests, the engine calls the model only for queued commands. Venues created from BacktestVenueConfig always queue commands. BacktestEngine.add_venue() queues commands by default; with use_message_queue=False, it processes them immediately without calling the latency model.

Each callback receives the concrete command and ts_now, the current clock time in Unix nanoseconds: simulation time in backtests and the sandbox client's clock time in sandbox.

MethodCommand types
get_insert_latencySubmitOrder, SubmitOrderList.
get_update_latencyModifyOrder, BatchModifyOrders.
get_delete_latencyCancelOrder, BatchCancelOrders, CancelAllOrders.

Import command classes from nautilus_trader.live. For single-order commands, use command.client_order_id to look up measured delays. The same methods receive whole batch commands and return one delay for all their members. The engine calls the model once per command.

Backtest arrival time is command.ts_init + delay; the engine processes the command at the first eligible settlement point at or after that time. Sandbox arrival time is ts_now + delay, where ts_now is the receipt time.

Callback errors, invalid delays, and arrival-time overflow have these effects:

  • BacktestEngine: run() or end() raises RuntimeError and aborts the run. Call engine.reset() before the next run.
  • BacktestNode: the node logs the error and omits that run's result by default. With raise_exception=True in BacktestRunConfig, it raises the error instead.
  • Sandbox: the client rejects failed submits and emits modify or cancel rejections for targets that remain open. It logs and discards a failed CancelAllOrders.

Model family structure

Behavioral model families use a common representation across simulation and execution:

  • A Rust <Family>Model trait defines the behavioral contract.
  • Concrete Rust types implement the built-in models.
  • A <Family>ModelAny enum lists the core built-ins and any language bridges that require enum storage, then implements the trait through explicit enum dispatch.
  • A <Family>ModelHandle stores a shared trait object where runtime components accept linked Rust implementations beyond the enum variants.

Supported concrete built-ins are exposed as PyO3 classes. Their type stub annotations feed the generated Python artifacts. Backtest configuration accepts these concrete model objects directly rather than using separate model configuration and factory wrappers.

Adapter-specific models live in their adapter crate when a core enum variant would create a reverse dependency. Low-level Rust code passes these models through the corresponding handle.

Python exposure uses an explicit bridge for the model family. Depending on the storage boundary, this is an enum variant or a trait implementation passed through the handle.

Dispatch boundary

FormAccepted implementationsDispatchRole
Concrete type or genericOne concrete implementationStaticModel internals and specialized callers.
<Family>ModelAnyDeclared built-ins and bridge variantsEnum matchBuilt-in and bridge configuration or storage.
<Family>ModelHandleAny accepted Rust trait implementationTrait objectShared runtime storage and custom types.

<Family>ModelAny uses enum dispatch. <Family>ModelHandle uses dynamic dispatch through a trait object, so a built-in converted from the enum into a handle crosses a vtable before its enum match. Built-in-only storage stays typed as <Family>ModelAny where avoiding trait-object dispatch matters.

The handle has no separate built-in fast path. The handle uses the simpler single representation because no measured performance case justifies the additional variant and dispatch complexity.

Native extension boundary

The open-source plug-in crate defines an artifact ABI, but model registration and the loading host are not part of this repository. The open-source distribution does not provide runtime native model plugins. Native models are composed at compile time and passed through the corresponding enum or handle.

Simulation modules use these representations at backtest configuration and runtime boundaries.

On this page