Phase 2: Core Components

Module 04: Domain Model — The Language of TradingHow NautilusTrader translates every trading concept into precise, immutable code

The domain model is the bedrock of the entire system. It defines exactly what a “price”, “order”, or “position” is. Understanding the domain model means mastering the native “language” required to interface with the system.

1. Domain Model Overview

flowchart LR
                        subgraph layer_1["💎 Primitives & IDs"]
                        direction TB
                        values["Value Types (Price/Qty/Money)"]
                        identifiers["Identifiers (ID/Venue)"]
                        end

                        subgraph layer_2["🧩 Core Objects"]
                        direction TB
                        instruments["Instruments"]
                        orders["Orders"]
                        end

                        subgraph layer_3["📊 State & Capital"]
                        direction TB
                        events["Events (OrderFilled...)"]
                        positions["Positions"]
                        accounts["Accounts"]
                        events --> positions --> accounts
                        end

                        layer_1 ==> layer_2 ==> layer_3
Figure 1: Domain Model Overview — Dependencies Between Concepts

2. Value Types — Why Standard Floating-Point Numbers Fail

🚨 The Classic Floating-Point Trap

In computer science, 0.1 + 0.2 = 0.30000000000000004, not exactly 0.3. If your trading system uses standard floating-point numbers to calculate prices, you might encounter scenarios where an order placed at $100.10 clears at $100.09999999. While the discrepancy seems microscopic, in high-frequency trading (HFT), compounding this error over millions of calculations can result in massive accounting discrepancies.

NautilusTrader solves this by employing fixed-point arithmetic (integers coupled with defined precision):

Data TypeInternal RepresentationMax PrecisionRangeApplication Target
Pricei128 signed integer16 decimal places±17 TrillionQuotes, execution prices
Quantityu128 unsigned integer16 decimal places0 ~ 34 TrillionOrder quantities, position sizing
Moneyi128 signed integer16 decimal places±17 TrillionAccount balances, PnL (Profit and Loss)
▶ 🔍 Under the Hood: Price Representation

Assume you need to represent 1.23450 (5 decimals of precision):

  • The system stores it internally as the integer 123450 with a precision of 5
  • For display purposes, it is divided by 105 = 1.23450
  • Addition operations are strictly integer-based: 1.23450 + 2.10000 → 123450 + 210000 = 333450 → 3.33450

Why it matters: Integer-backed arithmetic avoids the floating-point drift that can break trading calculations.

# Python Code Example
from nautilus_trader.model.objects import Price, Quantity, Money

# Instantiate a Price object (5 decimal places of precision)
price = Price.from_str("1.23450")
print(price)           # 1.23450
print(price.precision) # 5
print(price.raw)       # 123450000000000 (Internal raw integer value)

# Exact calculation without floating-point drift
p1 = Price.from_str("0.10000")
p2 = Price.from_str("0.20000")
result = Price(p1.raw + p2.raw, precision=5)
print(result)  # 0.30000 ✅ (Not 0.30000000000000004)

3. Instruments — The Assets You Trade

Every tradable asset in NautilusTrader is represented by a specific type:

flowchart LR
                        subgraph base["🏁 Instrument Base Class"]
                        direction TB
                        In["+id, +symbol, +venue<br />+price_precision, +size_precision"]
                        end

                        subgraph subs["🎻 Subclass Implementations"]
                        direction LR
                        E["Equity<br />(Stocks)"]
                        F["Futures<br />(Derivatives)"]
                        O["Options<br />(Options)"]
                        C["Crypto<br />(Perpetuals)"]
                        FX["CurrencyPair<br />(Forex)"]
                        end

                        base ==> subs

                        style base fill:#0f1419,stroke:#4fc3f7,stroke-width:2px
                        style subs fill:#0f1419,stroke:#ab47bc,stroke-width:1px
Figure 2: Instrument Typology Hierarchy
Instrument TypeApplicationIntrinsic AttributesExample
CurrencyPairForex TradingBase Currency, Quote CurrencyEUR/USD
EquityEquity TradingISIN Code, CurrencyAAPL
FuturesContractFutures TradingExpiry Date, Contract MultiplierES 2024-03
OptionsContractOptions TradingStrike Price, Call/PutAAPL C 180
CryptoPerpetualCrypto Perpetual ContractsInverse Contract FlagBTC-USDT-PERP
BettingInstrumentSports BettingEvent Type, MarketFootball Handicap
💡 Why design it this way?

Different instruments have fundamentally different settlement mechanisms. Equities settle in fiat currencies like USD; futures have specific contract multipliers and expiration dates; inverse crypto perpetuals settle in the base asset (e.g., BTC) rather than the quote asset (USDT). Categorizing them into distinct subclasses ensures that the idiosyncratic rules of each instrument are processed correctly.

4. Order Types — Your Execution Directives

NautilusTrader natively supports 11 distinct order types (9 foundational types + OrderList + simulated orders), comprehensively covering all execution scenarios:

💡 Why do we need so many order types?

❌ If only Market and Limit orders were available: You couldn’t easily implement logic like “automatically trigger a stop-loss when price breaches a support level” or “trail a stop-loss as profits accrue”. You would have to manually write timer loops to constantly poll market data—a suboptimal approach highly susceptible to latency and severe slippage.

✅ Utilizing a rich set of order types: Offloads stop-loss or trailing logic directly to the exchange’s matching engine (or NautilusTrader’s simulated exchange), ensuring significantly faster execution and deterministic reliability.

Order TypologyOperational ContextStrategic Deployment
MARKETMarket Order — Executes immediately at the best available priceWhen immediate market entry/exit is required
LIMITLimit Order — Specifies a maximum buy price or minimum sell priceWhen seeking a more favorable entry price
STOP_MARKETStop Market — Triggers a market order once a specified stop price is reachedHard stop-loss to truncate drawdowns
STOP_LIMITStop Limit — Triggers a limit order once a specified stop price is reachedPrecise control over the execution price of a stop
MARKET_TO_LIMITMarket-to-Limit — Executes at market first; any unfilled portion becomes a resting limit orderDesiring rapid execution without chasing excessive slippage
MARKET_IF_TOUCHEDMarket if Touched — Triggers a market order once a specified price is touchedEntering a position at a specific price inflection point
LIMIT_IF_TOUCHEDLimit if Touched — Similar to above but executes as a limit orderSame as above, but with a strictly bounded execution price
TRAILING_STOP_MARKETTrailing Stop Market — Stop price dynamically trails the market priceLocking in unrealized PnL and letting profits run
TRAILING_STOP_LIMITTrailing Stop Limit — Similar to above but executes as a limit orderHighly precise trailing stops with bounded slippage
OrderListOrder Bracket — Binds multiple constituent orders into a unified logical group (e.g., Bracket Order)Simultaneously placing an entry, take-profit, and stop-loss order

Order Lifecycle

Every order transitions through a definitive sequence of states from inception to termination:

stateDiagram-v2
                        direction LR
                        [*] --> INITIALIZED
                        INITIALIZED --> SUBMITTED : Submitting
                        SUBMITTED --> ACCEPTED : Exchange Accepted
                        ACCEPTED --> FILLED : Order Filled
                        ACCEPTED --> CANCELED : Order Canceled
                        FILLED --> [*]
                        CANCELED --> [*]

                        state "Tertiary Transients" as other
                        SUBMITTED --> REJECTED : Exchange Rejected
                        ACCEPTED --> PARTIALLY_FILLED : Partial Fill Event
                        PARTIALLY_FILLED --> FILLED
                        REJECTED --> [*]
Figure 3: Order State Machine — All potential paths from initialization to termination

Time in Force (TiF)

TiF CodeFull NomenclatureBehavioral Semantic
GTCGood Till CancelRemains working until explicitly canceled by the client
IOCImmediate or CancelExecutes immediately; any unexecuted portion is canceled (no resting liquidity)
FOKFill or KillMust be executed in its entirety immediately, otherwise the entire order is canceled
GTDGood Till DateRemains working until a specifically designated date/time
DAYDay OrderValid for the current trading day; canceled at market close
AT_THE_OPENAt The Open (MOO/LOO)Valid exclusively during the market open auction cross
AT_THE_CLOSEAt The Close (MOC/LOC)Valid exclusively during the market close auction cross

5. Positions — The Event-Sourced Model

The Position construct features a profoundly elegant design. Rather than functioning as a static state record, a position is dynamically synthesized via event sourcing—an aggregation of sequential execution events:

sequenceDiagram
                        participant E as ⚡ Event Stream
                        participant P as 📊 Position Object

                        Note over P: Position does not exist
                        E->>P: OrderFilled(Buy 1.0 BTC @ 50,000)
                        Note over P: Open Position<br />Side=LONG, Quantity=1.0<br />Avg Entry Price=50,000

                        E->>P: OrderFilled(Buy 0.5 BTC @ 51,000)
                        Note over P: Scale In (Add to Position)<br />Side=LONG, Quantity=1.5<br />Avg Entry Price=50,333.33

                        E->>P: OrderFilled(Sell 1.0 BTC @ 55,000)
                        Note over P: Partial Close<br />Side=LONG, Quantity=0.5<br />Realized PnL=+4,666.67

                        E->>P: OrderFilled(Sell 0.5 BTC @ 54,000)
                        Note over P: Flat (Position Closed)<br />Side=FLAT, Quantity=0<br />Total Realized PnL=+6,500
Figure 4: Position instantiation via deterministic OrderFill aggregation sequences
💡 Why use Event Sourcing instead of persisting final state?

❌ If only the final state is recorded: You know you currently “hold 0.5 BTC”, but you lose the execution lineage. Was it acquired in a single transaction, or did the account buy 1.0 BTC and later sell 0.5 BTC? What is the exact Volume-Weighted Average Price (VWAP) of the entry?

✅ Using Event Sourcing: Every state mutation is immutably logged. You can reconstruct the position’s lifecycle: when it was opened, scaled in, scaled out, and flattened, along with the precise price and quantity of every constituent fill. This is critical for quantitative trade analysis, backtesting fidelity, and institutional auditability.

Position Introspection (Code Syntax)

# Core Position attributes (accessible within a strategy)
position = self.cache.position(position_id)

# --- Identity & Topology ---
position.id           # PositionId: P-001
position.instrument_id # InstrumentId: BTC-USDT-PERP.BINANCE
position.strategy_id  # StrategyId: EMACross-001
position.side         # PositionSide: LONG / SHORT / FLAT

# --- Volumetric Matrices ---
position.quantity     # Quantity: 1.5 (Current open quantity)
position.peak_qty     # Quantity: 2.0 (Maximum historical quantity held)
position.signed_qty   # float: 1.5 (Positive for LONG, negative for SHORT)

# --- Pricing Metrics ---
position.avg_px_open  # float: 50333.33 (Average entry price)
position.avg_px_close # float: 55000.0 (Average exit price, 0 if currently open)
position.last_px      # Price: Last observed market price

# --- Profit & Loss (PnL) ---
position.realized_pnl   # Money: Realized PnL (from closed portions)
position.unrealized_pnl(last_price)  # Money: Unrealized PnL (Mark-to-Market)

# --- Temporal Horizons ---
position.ts_opened    # uint64_t: Opening timestamp (in nanoseconds)
position.ts_closed    # uint64_t: Closing timestamp (in nanoseconds, 0 if open)
position.duration_ns  # uint64_t: Duration held (in nanoseconds)

# --- Event Lineage ---
position.events       # list: List of all associated events
position.trade_ids    # list: List of all constituent Trade IDs

6. Account Types

💵

CashAccount — Cash Account

Trading is strictly constrained by available settled capital. Purchasing equities debits the balance; selling credits it. No margin or leverage is permitted. Supports both single and multi-currency denominations.

📊

MarginAccount — Margin Account

Facilitates leveraged trading by requiring only a fractional margin deposit to hold larger notional positions. The standard paradigm for futures and forex trading. Supports both single and multi-currency denominations.

🏇

BettingAccount — Betting Account

A specialized account structure engineered for betting exchanges (e.g., Betfair). Operates strictly on a single currency basis.

7. The Identifier System

In a distributed infrastructure interfacing with multiple exchanges and running concurrent strategies, deterministic identification of every entity is mission-critical:

Semantic IdentifierSyntax ExampleOperational Target
InstrumentIdBTC-USDT-PERP.BINANCEUnique identifier for a tradable asset (Symbol + Venue)
VenueBINANCEThe exchange or market data provider’s nomenclature
ClientOrderIdO-20240105-001Client-generated unique nonce for an order
VenueOrderId123456789Exchange-assigned order identifier
TradeIdT-20240105-001Unique identifier for a specific execution (fill)
StrategyIdEMACross-001Unique namespace for a specific strategy instance
AccountIdBINANCE-mainUnique identifier for a specific financial account
PositionIdP-001Unique identifier aggregating a specific position lifecycle
⚠️ Note the InstrumentId Formatting

InstrumentId explicitly incorporates the Venue designation. Therefore, BTC-USDT.BINANCE and BTC-USDT.OKX are treated as entirely distinct instruments, even if they reference the same underlying base pair. This guarantees cross-venue isolation and prevents routing collisions in multi-exchange topologies.

8. Time System — Nanosecond Precision

All timestamps in NautilusTrader use nanosecond-precision UTC time:

🕐 Timestamp Format

Syntax: 2024-01-05T15:30:45.123456789Z

  • 2024-01-05 — Gregorian Date
  • T — Standard Time Delimiter
  • 15:30:45 — UTC Chronology
  • .123456789 — 9-decimal fractional seconds (Nanoseconds)
  • Z — Absolute UTC Meridian Indicator
💡 Why is nanosecond precision necessary?

In high-frequency trading (HFT) and market making, microsecond-level timing differences can change queue position, fill probability, and adverse-selection exposure. Nanosecond timestamps preserve event ordering at the precision needed for realistic replay and post-trade analysis.

9. Market Data Types

Data TopologyArchitectural DefinitionData Vector Footprint
QuoteTickTop-of-book snapshot (Best Bid/Ask Price & Size)4 scalar values
TradeTickDiscrete execution fill (Price, Quantity, Aggressor Side)3 scalar values
BarOHLCV Candlestick (Open, High, Low, Close, Volume)5 scalar values
OrderBookDeltaIncremental L2 Order Book updateSingle price-level mutation
OrderBookDepth10L2 Order Book snapshot (Top 10 Bids/Asks)40 scalar values
InstrumentStatusInstrument trading state transitionEnum state flag

10. Interactive Exercises

🧠 Exercise 1: Floating-Point vs. Fixed-Point

Suppose your strategy calculates a price utilizing: 0.1 + 0.2. What are the resulting evaluations in generic Python versus NautilusTrader?

Generic Python (float):

>>> 0.1 + 0.2
0.30000000000000004 ❌ Precision loss!

NautilusTrader (Price):

>>> Price.from_str("0.1") + Price.from_str("0.2")

Equivalates to the integer operation 1000... + 2000... = 3000...

Emitted Result: 0.3 ✅ Exact decimal result

Conclusion: Do not use generic float types to represent pricing structures in a production trading system.

🧠 Exercise 2: Order Type Selection

Which order types should you deploy for the following execution scenarios?

  1. BTC is currently trading at 50,000. You require a hard stop-loss to flatten the position if the price breaches 45,000.
  2. You want to acquire BTC exposure, but strictly refuse to pay more than 49,500.
  3. BTC is trending upward. You want to lock in unrealized PnL by trailing your stop price behind the market peak.
  4. You require immediate BTC exposure, aggressively crossing the spread regardless of execution price.
  1. STOP_MARKET (Stop Market)
    Triggers when the price breaches 45,000, immediately routing a market order for liquidation.
  2. LIMIT (Limit Order)
    Rests a passive bid in the limit order book, capping execution at a maximum of 49,500.
  3. TRAILING_STOP_MARKET (Trailing Stop Market)
    The stop trigger dynamically ratchets upward with new market highs, systematically protecting accrued profit.
  4. MARKET (Market Order)
    Instantly sweeps the order book at the best available ask, optimizing for minimal latency rather than price.
🧠 Exercise 3: Debug the Syntax

The following code block attempting to instantiate a LimitOrder contains 2 critical type errors:

from nautilus_trader.model.orders import LimitOrder
from nautilus_trader.model.enums import OrderSide, TimeInForce

order = LimitOrder(
    instrument_id="BTC-USDT-PERP.BINANCE",  # 🤔
    order_side=OrderSide.BUY,
    quantity=1.5,                             # 🤔
    price=Price.from_str("50000.00"),
    time_in_force=TimeInForce.GTC,
)
  • Error 1: instrument_id strictly requires an InstrumentId object, not a primitive string.
    → Correct: instrument_id=InstrumentId.from_str("BTC-USDT-PERP.BINANCE")
  • Error 2: quantity demands a Quantity object, not a standard float.
    → Correct: quantity=Quantity.from_str("1.5")

💡 Takeaway: The Fail-Fast Philosophy of NautilusTrader

NautilusTrader leverages strong typing (Price, Quantity, InstrumentId) instead of generic primitives (float, str). This architectural choice guarantees that structural errors are caught decisively during compilation/initialization, entirely averting catastrophic failures during live production trading.

11. Module Summary

📋 Key Takeaways
  1. Value Types: Price, Quantity, and Money use fixed-point arithmetic (internal integers + precision bounds) to avoid floating-point drift.
  2. Instruments: Six distinct subclasses comprehensively cover all asset classes, each encapsulating its specific settlement and tick-size logic.
  3. 11 Order Types: Spanning from baseline Market orders to Trailing Stops and complex OrderLists (Bracket Orders), covering every conceivable execution requirement.
  4. Order State Machine: A rigorously defined lifecycle where every state transition correlates to a deterministic event.
  5. Positions as Event Aggregates: Positions are dynamically synthesized via sequential OrderFilled events, preserving a perfect, immutable historical audit trail.
  6. Identifier System: InstrumentId inherently concatenates the specific venue, structurally preventing routing collisions in multi-exchange deployments.
  7. Nanosecond Precision: Every timestamp within the engine natively resolves to a billionth of a second (UTC).