模組 12:回測系統與實盤部署同一份策略代碼,從歷史回測無縫切換到實盤交易
一、三種運行環境
flowchart LR
subgraph bt["🔬 回測 Backtest"]
hist["歷史數據"] --> sim1["模擬交易所"]
sim1 --> report["績效報告"]
end
subgraph sb["🏖️ 沙箱 Sandbox"]
live_data["即時數據"] --> sim2["模擬交易所"]
sim2 --> monitor["即時監控"]
end
subgraph lv["🚀 實盤 Live"]
live_data2["即時數據"] --> real["真實交易所"]
real --> pnl["真金白銀"]
end
bt -->|"驗證通過"| sb
sb -->|"觀察一段時間"| lv
在這三種環境中,你的策略代碼完全不需要修改。只需要改變配置中的數據源和交易所適配器。這就是 NautilusTrader 的核心價值主張之一。
❌ 如果只用簡單的 「price * quantity」 回測:你會忽略手續費、滑點、延遲,回測結果可能比實盤好 30-50%,上線後才發現全部虎頭蛇尾。
✅ 用 NautilusTrader 的真實模擬:配置 FillModel(滑點)、FeeModel(手續費)、LatencyModel(延遲),回測結果更接近實盤。
二、回測配置完整範例
from nautilus_trader.backtest.engine import BacktestEngine
from nautilus_trader.backtest.config import (
BacktestEngineConfig,
BacktestVenueConfig,
BacktestDataConfig,
BacktestRunConfig,
)
from nautilus_trader.model.enums import AccountType, OmsType
# ===== 1. 配置引擎 =====
engine_config = BacktestEngineConfig(
trader_id="BACKTESTER-001",
logging=LoggingConfig(log_level="INFO"),
risk_engine=RiskEngineConfig(
max_order_submit_rate="100/00:00:01",
),
)
# ===== 2. 配置模擬交易所 =====
venue_config = BacktestVenueConfig(
name="BINANCE",
oms_type=OmsType.NETTING, # 淨額結算
account_type=AccountType.MARGIN, # 保證金帳戶
starting_balances=["100_000 USDT"],
default_leverage=10, # 10 倍槓桿
fill_model=FillModel(
prob_fill_on_limit=0.2, # 限價單成交概率
prob_fill_on_stop=0.95, # 止損單成交概率
prob_slippage=0.5, # 滑點概率
),
)
# ===== 3. 配置數據源 =====
data_config = BacktestDataConfig(
catalog_path="./data/catalog",
data_cls="QuoteTick",
instrument_id="BTCUSDT.BINANCE",
start_time="2023-01-01",
end_time="2023-12-31",
)
# ===== 4. 配置策略 =====
strategy_config = MyStrategyConfig(
instrument_id=InstrumentId.from_str("BTCUSDT.BINANCE"),
bar_type=BarType.from_str("BTCUSDT.BINANCE-1-MINUTE-LAST"),
fast_ema_period=10,
slow_ema_period=20,
)
# ===== 5. 運行回測 =====
engine = BacktestEngine(config=engine_config)
engine.add_venue(venue_config)
engine.add_data(data_config)
engine.add_strategy(MyStrategy(strategy_config))
engine.run()
# ===== 6. 查看結果 =====
engine.trader.generate_order_fills_report()
engine.trader.generate_positions_report()
engine.trader.generate_account_report()三、數據目錄 (Data Catalog)
NautilusTrader 使用 Parquet 格式高效存儲回測數據:
數據寫入
適配器自動將收到的數據寫入 Parquet 文件。也可以手動從 CSV 導入。
高效讀取
Parquet 是列式格式,讀取 1 年的分鐘 K 線只需要幾秒鐘。
按需加載
支援只加載指定時間範圍和品種的數據,節省記憶體。
# 將數據寫入目錄
from nautilus_trader.persistence.catalog import ParquetDataCatalog
catalog = ParquetDataCatalog("./data/catalog")
# 寫入報價數據
catalog.write_data(quote_ticks)
# 讀取數據(按時間範圍)
ticks = catalog.quote_ticks(
instrument_ids=["BTCUSDT.BINANCE"],
start="2023-01-01",
end="2023-06-30",
)三B、FillModel / FeeModel / LatencyModel — 確保回測真實
這三個 Model 是區分「玩具回測」和「專業回測」的關鍵:
| Model | 控制什麼 | 為什麼重要 |
|---|---|---|
| FillModel | 訂單成交的滑點和部分成交。可模擬「市價單滑 2 tick」、「限價單只成交 80%」 | 實盤中幾乎不可能總以理想價格成交 |
| FeeModel | 交易手續費。可配置 maker/taker 費率,也可配置固定手續費 | 高頻策略每天數百筆交易,手續費累積影響巨大 |
| LatencyModel | 網路延遲。可模擬「提交訂單到交易所確認」的時間差 | 實盤中延遲可能導致錯過最佳入場點 |
"""配置真實的回測環境"""
from nautilus_trader.backtest.models import (
FillModel, FeeModel, LatencyModel,
)
# 滑點模型:市價單滑 1 tick,限價單 80% 機率全額成交
fill_model = FillModel(
prob_fill_on_limit=0.80, # 限價單 80% 機率成交
prob_fill_on_stop=0.95, # 停損單 95% 機率成交
prob_slippage=0.50, # 50% 機率有滑點
random_seed=42, # 固定隨機種子,確保可重現
)
# 手續費模型:make 單 0.02%, take 單 0.04%
fee_model = FeeModel(
maker_fee=0.0002,
taker_fee=0.0004,
)
# 延遲模型:提交 50ms, 取消 30ms
latency_model = LatencyModel(
submit_order_latency_ms=50,
cancel_order_latency_ms=30,
)
# 將 model 加入交易所配置
venue_config = BacktestVenueConfig(
name="BINANCE",
oms_type=OmsType.NETTING,
account_type=AccountType.MARGIN,
starting_balances=["100_000 USDT"],
fill_model=fill_model,
fee_model=fee_model,
latency_model=latency_model, # 可選
)三C、Tearsheet — 回測結果可視化
跑完回測後,一行代碼生成完整的互動式績效報表:
from nautilus_trader.analysis import create_tearsheet
# 回測結束後,用這一行生成報表
create_tearsheet(engine, output_path="tearsheet.html")
# → 打開 tearsheet.html 就能看到互動式 Plotly 圖表!
# 也可以單獨生成某一張圖
from nautilus_trader.analysis import (
create_equity_curve, # 資金曲線
create_drawdown_chart, # 回撤圖
create_monthly_returns_heatmap, # 月度熱力圖
create_rolling_sharpe, # 滾動 Sharpe
create_returns_distribution,# 收益分布
)
# 資金曲線圖
fig = create_equity_curve(
engine,
output_path="equity.html",
title="My EMA Strategy",
)Equity Chart
資金曲線,可疊加 Benchmark 對比
Drawdown Chart
回撤深度和持續時間
Monthly Returns
月度收益熱力圖,一眼看出季節性
Rolling Sharpe
滾動 Sharpe 比率,觀察策略穩定性
Returns Distribution
收益分布圖,看是否偏斜
Bars with Fills
K 線 + 買賣點標記
四、實盤部署
# 實盤配置 — 只改適配器,策略代碼不變!
from nautilus_trader.live.node import TradingNode
from nautilus_trader.adapters.binance.config import (
BinanceLiveDataClientConfig,
BinanceLiveExecClientConfig,
)
node = TradingNode(config=TradingNodeConfig(
trader_id="LIVE-001",
data_clients={
"BINANCE": BinanceLiveDataClientConfig(
api_key="your-api-key",
api_secret="your-api-secret",
),
},
exec_clients={
"BINANCE": BinanceLiveExecClientConfig(
api_key="your-api-key",
api_secret="your-api-secret",
),
},
))
# 加入同一個策略!
node.trader.add_strategy(MyStrategy(strategy_config))
node.run()策略代碼和配置一模一樣。唯一改變的是 DataClient 和 ExecClient 從模擬變成了 Binance 真實適配器。
實盤安全機制
NautilusTrader 為實盤提供了幾個關鍵的安全機制:
| 機制 | 作用 | 來源 |
|---|---|---|
| Reconciliation | 線上訂單對帳:定期檢查本地狀態和交易所是否一致 | live/reconciliation.py |
| Retry | 訂單提交失敗自動重試(可配置重試次數和間隔) | live/retry.py |
| Heartbeat | 定時心跳檢測,斷線自動重連 | 各 Adapter 內建 |
五、回測常見陷阱
| 陷阱 | 說明 | 如何避免 |
|---|---|---|
| 前視偏差 | 使用了未來才知道的數據做決策 | 只用 on_bar 中收到的 bar 數據 |
| 過擬合 | 策略只在特定歷史數據上表現好 | 分成訓練集和測試集 |
| 忽略手續費 | 不計入手續費導致虛假盈利 | 配置真實的 maker/taker 費率 |
| 忽略滑點 | 假設總以理想價格成交 | 配置 FillModel 滑點 |
| 存活者偏差 | 只測試現在還存在的品種 | 包含已退市品種的數據 |
七、互動練習
你的 EMA 策略回測結果很好,你需要修改哪些東西才能實盤運行?列出具體步驟。
步驟(只改配置,不改策略代碼):
- ❌ 不需要修改策略代碼
- ✅ 將
BacktestEngine改為TradingNode - ✅ 將數據源從
BacktestDataConfig改為BinanceLiveDataClientConfig - ✅ 將執行客戶端改為
BinanceLiveExecClientConfig - ✅ 填入真實的 API Key 和 Secret
- ✅ 調整風控參數(實盤應更保守)
- ✅ 建議先用小資金的沙箱模式觀察一段時間
總結:策略邏輯 0 行改動,只改「基礎設施」配置。
你的策略回測顯示年化回報 200%,但上實盤後卻持續虧損。可能是什麼原因?
可能原因(按可能性排序):
- 沒配置手續費 (FeeModel)
- 回測時 0 手續費,實際每筆要扣 0.04%
- 沒配置滑點 (FillModel)
- 回測假設總是以理想價成交,實際可能滑 1-3 tick
- 過擬合 (前視偏差)
- 參數只在歷史數據上最優,未來失效
- 使用 Walk-Forward 或 Out-of-Sample 測試
- 存活者偏差
- 只測了現在還在交易的品種,忽略了已下架的
- 延遲影響
- 回測無延遲,實盤有 50-200ms 延遲
以下公式計算了回測 vs 實盤的差異。如果你的策略平均每天交易 50 筆,每筆成交金額 $1000,taker 費率 0.04%,一年下來手續費總共多少?
計算:
50 筆/天 × $1000/筆 × 0.04% × 250 交易日
= 50 × 1000 × 0.0004 × 250
= $5,000
如果你的回測年化收益只有 $3,000,那它在實盤中其實是虧損的!這就是為什麼 FeeModel 如此重要。
七、本模組重點回顧
- 三種環境:回測 → 沙箱 → 實盤,策略代碼不變
- 回測配置:引擎 + 模擬交易所 + 數據 + 策略
- FillModel / FeeModel / LatencyModel:模擬真實的滑點、手續費、延遲
- Tearsheet:一行代碼生成互動式績效報表(6 種圖表)
- Data Catalog:Parquet 格式高效存儲歷史數據
- 實盤部署:只需將適配器從模擬改為真實
- 安全機制:Reconciliation 對帳、Retry 重試、Heartbeat 心跳
- 回測陷阱:前視偏差、過擬合、忽略費用和滑點