第二階段:核心組件深度解讀

模組 04:領域模型 — 交易的語言NautilusTrader 如何把交易世界的每一個概念都轉化為精確的代碼

領域模型是整個系統的基石。它定義了「價格是什麼」、「訂單是什麼」、「持倉是什麼」。如果你理解了領域模型,就等於掌握了和系統對話的「語言」。

一、領域模型全景圖

flowchart LR
                        subgraph layer_1["💎 基礎與標識"]
                        direction TB
                        values["值類型 (Price/Qty/Money)"]
                        identifiers["標識符 (ID/Venue)"]
                        end

                        subgraph layer_2["🧩 核心對象"]
                        direction TB
                        instruments["金融工具 (Instrument)"]
                        orders["訂單 (Order)"]
                        end

                        subgraph layer_3["📊 狀態與資金"]
                        direction TB
                        events["事件 (OrderFilled...)"]
                        positions["持倉 (Position)"]
                        accounts["帳戶 (Account)"]
                        events --> positions --> accounts
                        end

                        layer_1 ==> layer_2 ==> layer_3
圖 1:領域模型全景圖 — 各概念之間的依賴關係

二、值類型 — 為什麼不能用普通數字?

🚨 經典的浮點數陷阱

在電腦中,0.1 + 0.2 = 0.30000000000000004,而不是 0.3。如果你的交易系統用浮點數計算價格,可能會出現:下單 100.10 美元,到帳 100.09999999 美元。看起來差距很小,但在高頻交易中,百萬次計算後誤差會累積成巨大的金額。

NautilusTrader 使用定點數(整數 + 精度)來解決這個問題:

值類型內部表示最大精度範圍用途
Pricei128 有符號整數16 位小數±17 萬億報價、成交價格
Quantityu128 無符號整數16 位小數0 ~ 34 萬億訂單數量、持倉量
Moneyi128 有符號整數16 位小數±17 萬億帳戶餘額、盈虧
▶ 🔍 深入理解:Price 的內部運作原理

假設你要表示 1.23450(5 位精度):

  • 系統將其存儲為整數 123450,精度 5
  • 顯示時,除以 105 = 1.23450
  • 加法:1.23450 + 2.10000 → 123450 + 210000 = 333450 → 3.33450

好處:整數加法永遠精確,沒有浮點誤差!

# Python 代碼範例
from nautilus_trader.model.objects import Price, Quantity, Money

# 建立價格(5 位小數精度)
price = Price.from_str("1.23450")
print(price)           # 1.23450
print(price.precision) # 5
print(price.raw)       # 123450000000000(內部整數值)

# 精確計算,不會出現浮點誤差
p1 = Price.from_str("0.10000")
p2 = Price.from_str("0.20000")
result = Price(p1.raw + p2.raw, precision=5)
print(result)  # 0.30000 ✅(不是 0.30000000000000004)

三、金融工具 — 你交易的「東西」

每一種可以交易的東西在 NautilusTrader 中都有對應的類型:

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

                        subgraph subs["🎻 子類實現"]
                        direction LR
                        E["Equity<br/>(股票)"]
                        F["Futures<br/>(期貨)"]
                        O["Options<br/>(期權)"]
                        C["Crypto<br/>(加密永續)"]
                        FX["CurrencyPair<br/>(外匯)"]
                        end

                        base ==> subs

                        style base fill:#0f1419,stroke:#4fc3f7,stroke-width:2px
                        style subs fill:#0f1419,stroke:#ab47bc,stroke-width:1px
圖 2:金融工具類別層次圖
金融工具用途特有屬性例子
CurrencyPair外匯交易基礎貨幣、報價貨幣EUR/USD
Equity股票交易ISIN 代碼、貨幣AAPL
FuturesContract期貨交易到期日、合約大小ES 2024-03
OptionsContract期權交易行權價、看漲/看跌AAPL C 180
CryptoPerpetual加密永續合約是否反向合約BTC-USDT-PERP
BettingInstrument體育博彩賽事類型、市場足球讓球盤
💡 為什麼要這樣設計?

每種金融工具的結算方式不同。股票用美元結算,期貨有合約大小和到期日,反向合約用 BTC 結算而非 USDT。把它們分成不同類型,保證每種工具的特殊規則都被正確處理。

四、訂單類型 — 你下達的指令

NautilusTrader 支援 11 種訂單類型(9 種基礎 + OrderList + 模擬訂單),覆蓋所有交易場景:

💡 為什麼需要這麼多訂單類型?

❌ 如果只有市價單和限價單:你無法實現「當價格跌破支撐位時自動止損」或「讓止損跟著利潤上移」。你必須自己寫一個定時器不斷檢查價格,這既慢又容易出 bug。

✅ 有豐富的訂單類型:把止損/移動止損邏輯交給交易所(或 NautilusTrader 的模擬器),速度更快、更可靠。

訂單類型說明交易場景
MARKET市價單 — 立即以目前最優價成交需要立刻進場/出場時
LIMIT限價單 — 指定最高買價或最低賣價想以更好的價格進場
STOP_MARKET止損市價單 — 觸發價到了後以市價執行防止虧損擴大的止損
STOP_LIMIT止損限價單 — 觸發價到了後以限價執行精確控制止損的執行價
MARKET_TO_LIMIT市轉限 — 先以市價嘗試,未完全成交部分轉限價希望快速成交但不想追價
MARKET_IF_TOUCHED觸價市價單 — 價格觸及指定價後以市價執行在特定價位入場
LIMIT_IF_TOUCHED觸價限價單 — 價格觸及指定價後以限價執行同上但限制執行價格
TRAILING_STOP_MARKET移動止損市價 — 止損價跟隨市場移動鎖定利潤,讓利潤奔跑
TRAILING_STOP_LIMIT移動止損限價 — 同上但以限價執行更精確的移動止損
OrderList訂單組合 — 將多個訂單綁定為一組(如 Bracket Order)同時設定入場、止盈、止損三個訂單

訂單生命週期

每個訂單從建立到結束,會經歷一系列的狀態變化:

stateDiagram-v2
                        direction LR
                        [*] --> INITIALIZED
                        INITIALIZED --> SUBMITTED : 下單
                        SUBMITTED --> ACCEPTED : 接受
                        ACCEPTED --> FILLED : 成交
                        ACCEPTED --> CANCELED : 取消
                        FILLED --> [*]
                        CANCELED --> [*]

                        state "其它狀態" as other
                        SUBMITTED --> REJECTED : 拒絕
                        ACCEPTED --> PARTIALLY_FILLED : 部分成交
                        PARTIALLY_FILLED --> FILLED
                        REJECTED --> [*]
圖 3:訂單狀態機 — 訂單從建立到結束的所有可能路徑

有效期類型 (Time in Force)

代碼全名說明
GTCGood Till Cancel有效直到主動取消
IOCImmediate or Cancel立即成交或取消(不留掛單)
FOKFill or Kill全部成交或全部取消
GTDGood Till Date有效到指定日期/時間
DAYDay Order當天有效,收盤自動取消
AT_THE_OPEN開盤有效只在開盤時有效
AT_THE_CLOSE收盤有效只在收盤時有效

五、持倉 — 事件聚合模型

持倉 (Position) 是一個非常精妙的設計。它不是一個簡單的數據記錄,而是由一系列事件聚合而成:

sequenceDiagram
                        participant E as ⚡ 事件流
                        participant P as 📊 Position 持倉

                        Note over P: 持倉不存在
                        E->>P: OrderFilled(買入 1.0 BTC @ 50,000)
                        Note over P: 建立持倉<br/>方向=LONG, 數量=1.0<br/>入場均價=50,000

                        E->>P: OrderFilled(買入 0.5 BTC @ 51,000)
                        Note over P: 加倉<br/>方向=LONG, 數量=1.5<br/>入場均價=50,333.33

                        E->>P: OrderFilled(賣出 1.0 BTC @ 55,000)
                        Note over P: 部分平倉<br/>方向=LONG, 數量=0.5<br/>已實現盈虧=+4,666.67

                        E->>P: OrderFilled(賣出 0.5 BTC @ 54,000)
                        Note over P: 完全平倉<br/>方向=FLAT, 數量=0<br/>總已實現盈虧=+6,500
圖 4:持倉由訂單成交事件逐步聚合而成
💡 為什麼用事件聚合而不是直接記錄最終結果?

❌ 如果只記錄最終持倉:你知道「現在持有 0.5 BTC」,但不知道它是怎麼來的。是一次買入的?還是買了 1.0 又賣了 0.5?入場均價是多少?

✅ 用事件聚合:每一次變化都有完整記錄。你可以回溯:什麼時候建倉、加倉、減倉、平倉,每一筆的價格和數量。這對交易分析和審計至關重要。

Position 的關鍵屬性(代碼範例)

# Position 的重要屬性(策略中可以這樣訪問)
position = self.cache.position(position_id)

# --- 基礎資訊 ---
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

# --- 數量 ---
position.quantity     # Quantity: 1.5(當前持倉數量)
position.peak_qty     # Quantity: 2.0(歷史最大持倉)
position.signed_qty   # float: 1.5(LONG 正,SHORT 負)

# --- 價格 ---
position.avg_px_open  # float: 50333.33(入場均價)
position.avg_px_close # float: 55000.0(出場均價,未平倉為 0)
position.last_px      # Price: 當前市場價格

# --- 盈虧(最重要!)---
position.realized_pnl   # Money: 已實現盈虧(已平倉部分)
position.unrealized_pnl(last_price)  # Money: 未實現盈虧

# --- 時間 ---
position.ts_opened    # uint64_t: 開倉時間(納秒)
position.ts_closed    # uint64_t: 平倉時間(納秒,未平倉為 0)
position.duration_ns  # uint64_t: 持倉時長(納秒)

# --- 事件歷史 ---
position.events       # list: 所有相關事件
position.trade_ids    # list: 所有成交 ID

六、帳戶類型

💵

CashAccount — 現金帳戶

用多少錢就有多少。買入股票就扣款、賣出就入帳。不能借錢交易。支援單一和多幣種。

📊

MarginAccount — 保證金帳戶

只需繳納保證金即可交易更大的金額(槓桿)。期貨和外匯通常使用保證金帳戶。支援單一和多幣種。

🏇

BettingAccount — 博彩帳戶

專為 Betfair 等博彩交易所設計的帳戶類型。單一幣種。

七、標識符系統

在一個同時連接多個交易所、運行多個策略的系統中,準確識別每一個對象極為重要:

標識符格式範例用途
InstrumentIdBTC-USDT-PERP.BINANCE唯一標識一個金融工具(品種 + 交易所)
VenueBINANCE交易所或數據源的名稱
ClientOrderIdO-20240105-001客戶端生成的訂單唯一 ID
VenueOrderId123456789交易所分配的訂單 ID
TradeIdT-20240105-001成交的唯一 ID
StrategyIdEMACross-001策略實例的唯一 ID
AccountIdBINANCE-main帳戶的唯一 ID
PositionIdP-001持倉的唯一 ID
⚠️ 注意 InstrumentId 的格式

InstrumentId 包含交易所名稱。所以 BTC-USDT.BINANCE 和 BTC-USDT.OKX 是兩個不同的金融工具,即使它們底層都是 BTC/USDT。這確保了多交易所環境中不會混淆。

八、時間系統 — 納秒精度

NautilusTrader 中的所有時間戳都使用納秒精度的 UTC 時間:

🕐 時間格式

格式:2024-01-05T15:30:45.123456789Z

  • 2024-01-05 — 日期
  • T — 日期和時間的分隔符
  • 15:30:45 — 時間(UTC)
  • .123456789 — 9 位小數(納秒)
  • Z — UTC 時區標記
💡 為什麼要納秒精度?

在高頻交易中,毫秒甚至微秒的差異就決定了你是賺錢還是虧錢。納秒精度確保:事件順序精確排列、回測時每一筆成交的時間精確到十億分之一秒。

九、數據類型 — 市場數據的分類

數據類型說明資訊量
QuoteTick報價快照(最佳買賣價 + 數量)4 個數值
TradeTick逐筆成交(價格 + 數量 + 方向)3 個數值
BarK 線(開高低收 + 成交量)5 個數值
OrderBookDelta訂單簿增量更新單層變化
OrderBookDepth10訂單簿快照(買賣各 10 檔)40 個數值
InstrumentStatus金融工具狀態變更狀態枚舉

十、互動練習

🧠 練習 1:浮點數 vs 定點數

假設你的策略計算一個價格:0.1 + 0.2。在普通 Python 和 NautilusTrader 中的結果分別是什麼?

普通 Python (float):

>>> 0.1 + 0.2
0.30000000000000004 ❌ 精度丟失!

NautilusTrader (Price):

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

等效於整數運算 1000... + 2000... = 3000...

顯示結果:0.3 ✅ 精確!

結論:在交易系統中,永遠不要用 float 表示價格。

🧠 練習 2:訂單選擇

以下交易場景,你應該用哪種訂單類型?

  1. BTC 目前價格 50,000,你想在跌到 45,000 時止損出場
  2. 你想以不超過 49,500 的價格買入 BTC
  3. BTC 正在上漲,你想鎖定利潤,讓止損價跟著價格上移
  4. 你需要立刻買入 BTC,不管什麼價格
  1. STOP_MARKET(止損市價單)
    當價格跌到 45,000 時觸發,以市價賣出
  2. LIMIT(限價單)
    掛一個 49,500 的買入限價單,只在 49,500 或更低時成交
  3. TRAILING_STOP_MARKET(移動止損市價單)
    止損價會跟著最高價上移,保護已有利潤
  4. MARKET(市價單)
    立即以當前最優價成交,速度最快
🧠 練習 3:找出代碼錯誤

以下代碼嘗試建立一個限價單,有 2 個錯誤:

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,
)
  • 錯誤 1: instrument_id 需要 InstrumentId 對象,不是字串
    → 正確: instrument_id=InstrumentId.from_str("BTC-USDT-PERP.BINANCE")
  • 錯誤 2: quantity 需要 Quantity 對象,不是 float
    → 正確: quantity=Quantity.from_str("1.5")

💡 教訓:NautilusTrader 的 Fail-Fast 哲學

它使用強類型(Price, Quantity, InstrumentId)而非原始類型(float, str)。這確保你在編碼階段就發現錯誤,而不是在實盤交易時。

十一、本模組重點回顧

📋 你應該記住的關鍵點
  1. 值類型:Price/Quantity/Money 用定點數(整數 + 精度),避免浮點誤差
  2. 金融工具:6 種子類覆蓋所有資產類別,各有特定屬性
  3. 9 種訂單類型:從市價單到移動止損再到 OrderList(Bracket Order),覆蓋所有交易場景
  4. 訂單狀態機:明確的生命週期,每種轉換都有相應的事件
  5. 持倉 = 事件聚合:由 OrderFilled 事件逐步構建,保留完整歷史
  6. 標識符系統:InstrumentId 包含交易所名稱,防止多交易所混淆
  7. 納秒精度:所有時間戳精確到十億分之一秒