Skip to content
python.financial
Platform
Backtesting and live-trading engine
License
LGPL-3.0-or-later
Pricing
Free and open source
Live trading
Supported through venue and broker adapters
Best for
Granular event and order simulation using strategy code that can also run live

NautilusTrader is useful when a strategy depends on event ordering, order state, market-data resolution, or several trading venues. It reuses the message bus, cache, portfolio, actors, strategies, and execution algorithms across backtests and live trading. This reduces differences between the two modes, but simulated fills still cannot guarantee live results. The backtest remains only as good as its historical data and its assumptions about fills, fees, latency, and liquidity.

Which NautilusTrader version should you use?

The project is moving from its established Python-facing design toward a more Rust-native design. Check the official installation and migration docs before choosing between the stable and preview lines.

  • Stable v1: Use the stable line for production work unless you have validated the v2 migration. It has a legacy Cython-based core alongside Rust components.
  • v2 release candidates: The v2 line moves the platform core to Rust and exposes it to Python through PyO3. The project explicitly says release candidates are not recommended for production trading with live capital.

The official documentation's latest branch describes v2, including installation with a pre-release flag. Check the stable documentation when working with stable v1 so that examples and configuration types match the installed package.

How do you run a minimal NautilusTrader backtest?

This complete stable-v1 example needs no credentials or data download. It creates three synthetic top-of-book quotes, subscribes a strategy to them, submits one market order after the first quote has established a market, and prints the resulting fill. The TestInstrumentProvider is a bundled fixture that keeps the example short. Use validated point-in-time instrument definitions in research and production.

from nautilus_trader.backtest.engine import BacktestEngine
from nautilus_trader.config import BacktestEngineConfig, LoggingConfig
from nautilus_trader.model.data import QuoteTick
from nautilus_trader.model.enums import AccountType, OmsType, OrderSide
from nautilus_trader.model.identifiers import Venue
from nautilus_trader.model.objects import Money
from nautilus_trader.test_kit.providers import TestInstrumentProvider
from nautilus_trader.trading.strategy import Strategy


class BuyOnce(Strategy):
    def __init__(self, instrument_id):
        super().__init__()
        self.instrument_id = instrument_id
        self.quotes_seen = 0

    def on_start(self):
        self.subscribe_quote_ticks(self.instrument_id)

    def on_quote_tick(self, tick):
        self.quotes_seen += 1
        if self.quotes_seen == 2:
            instrument = self.cache.instrument(self.instrument_id)
            order = self.order_factory.market(
                instrument_id=self.instrument_id,
                order_side=OrderSide.BUY,
                quantity=instrument.make_qty(100_000),
            )
            self.submit_order(order)


instrument = TestInstrumentProvider.default_fx_ccy("AUD/USD")
engine = BacktestEngine(
    config=BacktestEngineConfig(
        logging=LoggingConfig(bypass_logging=True),
        run_analysis=False,
    ),
)
engine.add_venue(
    venue=Venue("SIM"),
    oms_type=OmsType.HEDGING,
    account_type=AccountType.MARGIN,
    base_currency=None,
    starting_balances=[Money.from_str("1000000 USD")],
)
engine.add_instrument(instrument)

ticks = [
    QuoteTick(
        instrument_id=instrument.id,
        bid_price=instrument.make_price(bid),
        ask_price=instrument.make_price(ask),
        bid_size=instrument.make_qty(1_000_000),
        ask_size=instrument.make_qty(1_000_000),
        ts_event=ts,
        ts_init=ts,
    )
    for bid, ask, ts in [
        (1.0000, 1.0002, 1_000_000_000),
        (1.0001, 1.0003, 2_000_000_000),
        (1.0002, 1.0004, 3_000_000_000),
    ]
]
engine.add_data(ticks)
engine.add_strategy(BuyOnce(instrument.id))
engine.run()

fills = engine.trader.generate_order_fills_report()
columns = ["instrument_id", "side", "filled_qty", "avg_px", "commissions", "status"]
print(fills.loc[:, columns].to_string(index=False))
engine.dispose()

Executed with NautilusTrader 1.221.0, the example prints:

instrument_id side filled_qty  avg_px commissions status
  AUD/USD.SIM  BUY     100000  1.0003  [2.00 USD] FILLED

The order fills at the second synthetic quote's ask. The fixture supplies the USD commission. This run demonstrates strategy subscription, event handling, order submission, and reporting. It does not test a trading signal or prove realistic execution. For a research backtest, replace the fixture and synthetic quotes with catalog data, then configure spreads, fees, latency, liquidity consumption, and fill behavior for the venue being modeled.

How the architecture helps

NautilusTrader uses an event-driven, ports-and-adapters design. Historical simulators and live venue clients feed the same kinds of domain events into a shared trading runtime. Strategies can therefore retain much of their structure across research and deployment instead of being rewritten for an unrelated live engine. The backtesting architecture overview documents the components reused in both environments.

The v2 Python API controls a Rust core. Python strategies still execute Python code, so the Python global interpreter lock can still matter for strategy work. NautilusTrader also provides a Rust development path for workloads that need native traits or no Python runtime, although the Rust capability matrix shows that Python and Rust do not cover exactly the same features.

For data-intensive replay, the framework provides a Parquet-backed catalog and can stream catalog data in chunks. Event simulation still runs each scenario as its own stateful process. Broad parameter searches will usually require more orchestration than array-oriented research in community VectorBT.

What market data can the simulator know?

More detailed input data exposes more of the historical market, but it never reveals how a simulated order would have changed that history. The backtest data documentation distinguishes these useful boundaries:

Input data What the engine can model What remains unknown
Bars OHLCV movement at the chosen interval Intrabar path, spread, depth, and queue position
L1 quotes and trades Best bid and ask, trades, and top-of-book changes present in the feed Deeper liquidity and a reliable full queue
L2 market-by-price Displayed size at aggregated price levels Individual order priority within a price level
L3 market-by-order Recorded individual book orders and their changes The market's response to your unrecorded simulated order

Configure the simulated book type to match the source data. Feeding sparse data into an L2 or L3 venue does not create missing depth. Conversely, lower-granularity data cannot reconstruct a more detailed historical book.

Fill, liquidity, and latency assumptions

NautilusTrader exposes more execution controls than a bar-only backtester, but its defaults are not a claim about likely live execution.

  • The default fill model makes a touched limit order eligible to fill and applies no probabilistic one-tick slippage to L1 simulation. Those defaults can be optimistic for a real queue.
  • Liquidity consumption tracking is off by default. Without it, more than one simulated order may use the same displayed size during an iteration. Enable it when the test must account for consumption until fresh market data arrives.
  • Queue-position tracking can use trades and L2 or L3 data to reduce displayed quantity ahead of an order. It works only when the required data and venue options are present, and it still cannot observe the counterfactual effect of the simulated order.
  • The static latency model adds configured delays to order insert, update, and cancel paths. Calibrate those values from measurements. A configured delay is an assumption, not a prediction of future network or venue latency.

Fees, spread, market impact, funding, borrow availability, and borrow cost also need explicit treatment when they affect the strategy. Detailed order-book replay does not remove those research obligations.

Live trading scope and operational work

Official integrations cover crypto venues, Interactive Brokers, prediction markets, and specialist data services. Capabilities differ by adapter, so verify the relevant integration guide for supported market data, instruments, order types, reconciliation, and test environments before selecting a venue.

Live deployment is closer to operating a service than running a notebook. The live-trading configuration guide recommends one live node per process and does not recommend Jupyter notebooks for live operation. Startup reconciliation is enabled by default, but venue history windows can be incomplete. The reconciliation documentation explains why persisted order and position state matters for recovery.

The open-source project focuses on single-node systems for individuals and small teams. Distributed orchestration, user interfaces, and adjacent platform services are outside that stated scope. Plan separately for deployment, secrets, monitoring, alerting, restart policy, backups, and kill procedures.

When NautilusTrader is the wrong fit

Choose a simpler or vectorized research engine when the main job is rapid hypothesis screening over many signals and parameters, especially when bar-level assumptions are sufficient. NautilusTrader's explicit instruments, venues, catalogs, actors, and node configuration impose a larger learning and operational cost.

NautilusTrader becomes more useful when the research question genuinely needs stateful event handling or granular order behavior and you possess data detailed enough to support the model. The framework provides the machinery for a careful simulation. It does not turn coarse data or uncalibrated assumptions into execution realism.

NautilusTrader is distributed under LGPL-3.0-or-later. Review the license obligations for your distribution and deployment model.

Choose which optional services may run. You can change these settings at any time.