模組 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
二、值類型 — 為什麼不能用普通數字?
在電腦中,0.1 + 0.2 = 0.30000000000000004,而不是 0.3。如果你的交易系統用浮點數計算價格,可能會出現:下單
100.10 美元,到帳 100.09999999 美元。看起來差距很小,但在高頻交易中,百萬次計算後誤差會累積成巨大的金額。
NautilusTrader 使用定點數(整數 + 精度)來解決這個問題:
| 值類型 | 內部表示 | 最大精度 | 範圍 | 用途 |
|---|---|---|---|---|
Price | i128 有符號整數 | 16 位小數 | ±17 萬億 | 報價、成交價格 |
Quantity | u128 無符號整數 | 16 位小數 | 0 ~ 34 萬億 | 訂單數量、持倉量 |
Money | i128 有符號整數 | 16 位小數 | ±17 萬億 | 帳戶餘額、盈虧 |
假設你要表示 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
| 金融工具 | 用途 | 特有屬性 | 例子 |
|---|---|---|---|
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 --> [*]
有效期類型 (Time in Force)
| 代碼 | 全名 | 說明 |
|---|---|---|
GTC | Good Till Cancel | 有效直到主動取消 |
IOC | Immediate or Cancel | 立即成交或取消(不留掛單) |
FOK | Fill or Kill | 全部成交或全部取消 |
GTD | Good Till Date | 有效到指定日期/時間 |
DAY | Day 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
❌ 如果只記錄最終持倉:你知道「現在持有 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 等博彩交易所設計的帳戶類型。單一幣種。
七、標識符系統
在一個同時連接多個交易所、運行多個策略的系統中,準確識別每一個對象極為重要:
| 標識符 | 格式範例 | 用途 |
|---|---|---|
InstrumentId | BTC-USDT-PERP.BINANCE | 唯一標識一個金融工具(品種 + 交易所) |
Venue | BINANCE | 交易所或數據源的名稱 |
ClientOrderId | O-20240105-001 | 客戶端生成的訂單唯一 ID |
VenueOrderId | 123456789 | 交易所分配的訂單 ID |
TradeId | T-20240105-001 | 成交的唯一 ID |
StrategyId | EMACross-001 | 策略實例的唯一 ID |
AccountId | BINANCE-main | 帳戶的唯一 ID |
PositionId | P-001 | 持倉的唯一 ID |
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 個數值 |
Bar | K 線(開高低收 + 成交量) | 5 個數值 |
OrderBookDelta | 訂單簿增量更新 | 單層變化 |
OrderBookDepth10 | 訂單簿快照(買賣各 10 檔) | 40 個數值 |
InstrumentStatus | 金融工具狀態變更 | 狀態枚舉 |
十、互動練習
假設你的策略計算一個價格: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 表示價格。
以下交易場景,你應該用哪種訂單類型?
- BTC 目前價格 50,000,你想在跌到 45,000 時止損出場
- 你想以不超過 49,500 的價格買入 BTC
- BTC 正在上漲,你想鎖定利潤,讓止損價跟著價格上移
- 你需要立刻買入 BTC,不管什麼價格
STOP_MARKET(止損市價單)
當價格跌到 45,000 時觸發,以市價賣出LIMIT(限價單)
掛一個 49,500 的買入限價單,只在 49,500 或更低時成交TRAILING_STOP_MARKET(移動止損市價單)
止損價會跟著最高價上移,保護已有利潤MARKET(市價單)
立即以當前最優價成交,速度最快
以下代碼嘗試建立一個限價單,有 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)。這確保你在編碼階段就發現錯誤,而不是在實盤交易時。
十一、本模組重點回顧
- 值類型:Price/Quantity/Money 用定點數(整數 + 精度),避免浮點誤差
- 金融工具:6 種子類覆蓋所有資產類別,各有特定屬性
- 9 種訂單類型:從市價單到移動止損再到 OrderList(Bracket Order),覆蓋所有交易場景
- 訂單狀態機:明確的生命週期,每種轉換都有相應的事件
- 持倉 = 事件聚合:由 OrderFilled 事件逐步構建,保留完整歷史
- 標識符系統:InstrumentId 包含交易所名稱,防止多交易所混淆
- 納秒精度:所有時間戳精確到十億分之一秒