How to Configure Fill Models and Fee Models for Backtesting in Nautilus Trader

You configure fill models and fee models in Nautilus Trader by passing fill_model and fee_model parameters to BacktestEngine.add_venue(), or by calling change_fill_model() and change_fee_model() at runtime to modify simulation behavior dynamically.

Nautilus Trader is a high-performance algorithmic trading platform that provides sophisticated backtesting capabilities through its simulated exchange engine. When you configure fill models and fee models for backtesting, you control exactly how orders execute and how trading costs accrue, allowing you to stress-test strategies against realistic market conditions or specific exchange fee schedules.

Understanding Fill Models and Fee Models in Nautilus Trader

The backtest engine runs a simulated exchange for each venue you add. Two pluggable components control how trades are executed and how costs are charged:

  • FillModel: Determines the liquidity that the matching engine sees when trying to fill an order. It can be probabilistic, deterministic, size-aware, or inject slippage. The default is a simple probabilistic model located in nautilus_trader/backtest/models/fill.pyx.

  • FeeModel: Calculates the commission for each fill based on the instrument's maker/taker schedule, a fixed per-trade amount, or a per-contract charge. The default MakerTakerFeeModel uses the instrument's maker_fee and taker_fee fields, defined in nautilus_trader/backtest/models/fee.pyx.

Configuring Fill Models for Realistic Execution Simulation

Available Fill Model Implementations

The nautilus_trader/backtest/models/fill.pyx file contains several concrete implementations you can use:

  • FillModel: The base probabilistic model.
  • BestPriceFillModel: Always fills at the best available price with no slippage.
  • OneTickSlippageFillModel: Injects one tick of slippage on fills.
  • SizeAwareFillModel: Considers order size relative to available liquidity.
  • TwoTierFillModel: Fills a portion at best price, remainder at worse levels.

Setting a Custom Fill Model in add_venue()

When calling BacktestEngine.add_venue() in nautilus_trader/backtest/engine.pyx, pass your fill model via the fill_model parameter:

from nautilus_trader.backtest.models import BestPriceFillModel
from nautilus_trader.backtest.engine import BacktestEngine
from nautilus_trader.model.identifiers import Venue
from nautilus_trader.model.enums import OmsType, AccountType
from nautilus_trader.model.objects import Money, USD

engine = BacktestEngine()

engine.add_venue(
    venue=Venue("SIM"),
    oms_type=OmsType.NETTING,
    account_type=AccountType.CASH,
    starting_balances=[Money(100_000, USD)],
    fill_model=BestPriceFillModel(),  # No slippage, unlimited depth

)

If you omit fill_model, the engine constructs the default FillModel() as shown in lines 27-33 of nautilus_trader/backtest/engine.pyx.

Changing Fill Models at Runtime

You can dynamically switch fill models during a backtest using change_fill_model():

from nautilus_trader.backtest.models import OneTickSlippageFillModel

engine.change_fill_model(
    venue=Venue("SIM"),
    model=OneTickSlippageFillModel(),
)

This method is defined in nautilus_trader/backtest/engine.pyx around lines 302-314.

Configuring Fee Models for Accurate Cost Accounting

Available Fee Model Implementations

The nautilus_trader/backtest/models/fee.pyx module provides:

  • MakerTakerFeeModel: Uses the instrument's configured maker_fee and taker_fee percentages. This is the default.
  • FixedFeeModel: Charges a flat monetary amount per order regardless of size.
  • PerContractFeeModel: Multiplies a per-contract rate by the filled quantity.

Setting a Custom Fee Model in add_venue()

Pass your fee model via the fee_model parameter when adding a venue:

from nautilus_trader.backtest.models import FixedFeeModel

engine.add_venue(
    venue=Venue("SIM"),
    oms_type=OmsType.NETTING,
    account_type=AccountType.CASH,
    starting_balances=[Money(100_000, USD)],
    fee_model=FixedFeeModel(Money(2.5, USD)),  # $2.5 flat commission per order

)

If omitted, the engine defaults to MakerTakerFeeModel() as implemented in nautilus_trader/backtest/engine.pyx.

Changing Fee Models at Runtime

Similar to fill models, you can update fee models mid-simulation:

from nautilus_trader.backtest.models import PerContractFeeModel

engine.change_fee_model(
    venue=Venue("SIM"),
    model=PerContractFeeModel(Money(0.01, USD)),  # $0.01 per contract

)

Complete Configuration Example

Here is a comprehensive setup combining both fill and fee models for a realistic backtest scenario:

from nautilus_trader.backtest.models import (
    TwoTierFillModel,
    PerContractFeeModel,
)
from nautilus_trader.backtest.engine import BacktestEngine
from nautilus_trader.model.identifiers import Venue
from nautilus_trader.model.enums import OmsType, AccountType
from nautilus_trader.model.objects import Money, USD

engine = BacktestEngine()

# Configure venue with realistic execution and cost models

engine.add_venue(
    venue=Venue("SIM"),
    oms_type=OmsType.HEDGING,
    account_type=AccountType.MARGIN,
    starting_balances=[Money(50_000, USD)],
    fill_model=TwoTierFillModel(),  # 10 contracts at best, remainder at worse price

    fee_model=PerContractFeeModel(Money(0.01, USD)),  # $0.01 per contract traded

)

How the Engine Wires Models into the Simulation

Understanding the internal plumbing helps debug custom models. In nautilus_trader/backtest/engine.pyx, the add_venue method creates a SimulatedExchange and forwards your models:


# Lines 27-33 in engine.pyx

if fill_model is None:
    fill_model = FillModel()                # default probabilistic model

if fee_model is None:
    fee_model = MakerTakerFeeModel()        # default maker/taker schedule

The SimulatedExchange stores these models, and the matching engine consults them during execution. When processing an order, the engine calls the fill model's get_orderbook_for_fill_simulation method (if overridden) to produce a synthetic OrderBook that determines available liquidity:


# Lines 5812-5823 in engine.pyx

if self._fill_model is None:
    # use real order book

else:
    simulated_book = self._fill_model.get_orderbook_for_fill_simulation(...)

After a fill is generated, the fee model's get_commission method calculates costs:


# Lines 5900-5905 in engine.pyx

commission = self._fee_model.get_commission(order, fill_qty, fill_px, instrument)

These hooks provide full control over market behavior and cost accounting without modifying the core matching engine.

Summary

  • Fill models control how the simulated exchange matches orders, allowing you to inject slippage, limit liquidity, or enforce deterministic execution.
  • Fee models calculate trading costs using maker/taker schedules, fixed amounts, or per-contract rates.
  • Configure both via the fill_model and fee_model parameters in BacktestEngine.add_venue() in nautilus_trader/backtest/engine.pyx.
  • Change models dynamically during backtests using change_fill_model() and change_fee_model().
  • Default implementations are FillModel() for execution and MakerTakerFeeModel() for costs.

Frequently Asked Questions

What is the default fill model if I don't specify one?

If you omit the fill_model parameter when calling add_venue(), the engine instantiates the base FillModel() class from nautilus_trader/backtest/models/fill.pyx. This provides a simple probabilistic fill behavior that you can override with more sophisticated implementations like BestPriceFillModel or TwoTierFillModel.

How do I simulate slippage in my backtests?

Use the OneTickSlippageFillModel available in nautilus_trader/backtest/models/fill.pyx. This implementation injects exactly one tick of slippage on every fill, simulating the cost of crossing the spread. Pass it to add_venue() via the fill_model parameter to apply it to a specific simulated venue.

Can I switch fee models mid-backtest to test different broker scenarios?

Yes. The BacktestEngine provides change_fee_model() and change_fill_model() methods defined in nautilus_trader/backtest/engine.pyx. You can call these methods at any point during the simulation to switch between FixedFeeModel, PerContractFeeModel, or MakerTakerFeeModel without restarting the engine, enabling dynamic "what-if" analysis across different cost structures.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →