模組 05B:數據管理與持久化從你的 CSV 到 NautilusTrader 能讀的格式
NautilusTrader 需要特定格式的數據才能運行回測。這個模組教你如何把各種來源的數據轉換、存儲、和載入到系統中。
一、數據管線全景
數據從你自己的檔案到回測引擎,需要經過以下步驟:
flowchart LR
subgraph source["📁 數據來源"]
direction TB
csv["CSV 檔案"]
api["交易所 API"]
vendor["數據供應商<br/>(Databento/Tardis)"]
end
subgraph transform["🔄 數據轉換"]
direction TB
wrangler["Wrangler<br/>數據整理器"]
loader["DataLoader<br/>數據載入器"]
end
subgraph storage["💾 數據存儲"]
direction TB
catalog["DataCatalog<br/>(Parquet 格式)"]
memory["直接載入記憶體"]
end
subgraph engine["⚙️ 回測引擎"]
direction TB
backtest["BacktestEngine"]
end
source ==> transform ==> storage ==> engine
二、Wrangler — 數據格式轉換器
❌ 如果直接用 pandas DataFrame:NautilusTrader 不認識 DataFrame。它需要的是
QuoteTick、TradeTick、Bar 等專用數據對象,因為這些對象經過 Rust 優化,速度比原始
Python 快 100 倍以上。
✅ 用 Wrangler:它幫你把 pandas DataFrame 轉換成 NautilusTrader 專用的數據對象,一行代碼搞定。
從 CSV 載入 K 線數據
"""
從自己的 CSV 檔案載入 K 線數據到 NautilusTrader
"""
import pandas as pd
from nautilus_trader.persistence.wranglers import BarDataWrangler
from nautilus_trader.model.data import BarType
from nautilus_trader.model.identifiers import InstrumentId
# 1. 用 pandas 讀取你的 CSV
df = pd.read_csv("my_btc_data.csv")
# 你的 CSV 欄位可能是:timestamp, open, high, low, close, volume
# 2. 確保索引是時間戳
df["timestamp"] = pd.to_datetime(df["timestamp"])
df = df.set_index("timestamp")
# 3. 定義 BarType(什麼品種、什麼時間框架)
bar_type = BarType.from_str("BTC-USDT-PERP.BINANCE-1-MINUTE-LAST-EXTERNAL")
# 品種 時間框架 價格類型 來源
# 4. 用 Wrangler 轉換!
wrangler = BarDataWrangler(bar_type=bar_type, instrument=instrument)
bars = wrangler.process(df) # 返回 list[Bar]
print(f"✅ 載入了 {len(bars)} 根 K 線")
print(f"第一根: {bars[0]}")
print(f"最後根: {bars[-1]}")其它 Wrangler 種類
| Wrangler | 輸入 DataFrame 欄位 | 輸出數據類型 | 適用場景 |
|---|---|---|---|
QuoteTickDataWrangler | bid, ask, bid_size, ask_size | QuoteTick | 有完整報價數據(外匯、加密貨幣) |
TradeTickDataWrangler | price, quantity, aggressor_side | TradeTick | 逐筆成交數據 |
BarDataWrangler | open, high, low, close, volume | Bar | K 線數據(最常見) |
OrderBookDeltaDataWrangler | action, side, price, size | OrderBookDelta | 訂單簿增量數據(高頻) |
三、DataCatalog — 數據倉庫
❌ 如果每次回測都從 CSV 重新讀取:CSV 是文字格式,解析慢。100 萬根 K 線可能需要 30 秒以上。而且你需要每次都重新做 Wrangler 轉換。
✅ 用 DataCatalog:它把數據存為 Parquet 格式(二進制列式存儲),讀取速度比 CSV 快 10-50 倍。而且一次寫入、多次讀取,不需要重複轉換。
flowchart LR
subgraph write["✍️ 寫入(只做一次)"]
direction TB
w1["CSV → Wrangler → Bars"]
w2["catalog.write_data(bars)"]
w1 --> w2
end
subgraph read["📖 讀取(每次回測)"]
direction TB
r1["catalog.bars()"]
r2["直接得到 list 對象"]
r1 --> r2
end
subgraph format["📦 Parquet 格式"]
direction TB
f1["二進制列式存儲"]
f2["壓縮比 5-10x"]
f3["支援時間範圍查詢"]
end
write --> format --> read
style format fill:#0f1419,stroke:#4fc3f7,stroke-width:2px
寫入數據到 Catalog
"""
把數據存入 DataCatalog(只需做一次)
"""
from nautilus_trader.persistence.catalog import ParquetDataCatalog
# 1. 指定 Catalog 的存儲目錄
catalog = ParquetDataCatalog("./my_catalog")
# 2. 寫入數據(bars 是上一步 Wrangler 產出的)
catalog.write_data(bars)
print(f"✅ 已寫入 {len(bars)} 根 K 線到 Catalog")
# 目錄結構會變成:
# ./my_catalog/
# └── bar/
# └── BTC-USDT-PERP.BINANCE-1-MINUTE-LAST-EXTERNAL/
# └── part-0.parquet ← 二進制數據檔案從 Catalog 讀取數據
"""
從 DataCatalog 讀取數據(每次回測都用這個)
"""
from nautilus_trader.persistence.catalog import ParquetDataCatalog
catalog = ParquetDataCatalog("./my_catalog")
# 讀取所有 K 線
bars = catalog.bars()
# 或者按時間範圍讀取(只讀 2024 年 1 月的數據)
from datetime import datetime
bars_jan = catalog.bars(
start=datetime(2024, 1, 1),
end=datetime(2024, 2, 1),
)
# 讀取特定品種
bars_btc = catalog.bars(
bar_types=["BTC-USDT-PERP.BINANCE-1-MINUTE-LAST-EXTERNAL"],
)
print(f"📊 讀取了 {len(bars)} 根 K 線(速度:毫秒級!)")四、K 線聚合 — 自定義時間框架
❌ 如果只能用數據源提供的時間框架:你的數據源可能只有 1 分鐘 K 線。但你的策略需要 15 分鐘或 4 小時 K 線。你需要自己寫代碼把 1 分鐘 K 線合併成更大的時間框架——容易出錯。
✅ 用 BarAggregator:NautilusTrader 內建 K 線聚合器,可以自動把任何時間框架的數據聚合成你需要的時間框架。而且它支援不只是時間,還能按成交量、成交筆數聚合。
"""
在策略中訂閱自定義時間框架的 K 線
"""
class MyStrategy(Strategy):
def on_start(self):
# 訂閱 15 分鐘 K 線(引擎會自動從 1 分鐘 K 線聚合)
bar_type_15m = BarType.from_str(
"BTC-USDT-PERP.BINANCE-15-MINUTE-LAST-INTERNAL"
# ^^^^^^^^
# INTERNAL = NautilusTrader 自己聚合
# EXTERNAL = 由外部數據源提供
)
self.subscribe_bars(bar_type_15m)
# 你甚至可以訂閱按「成交量」聚合的 K 線!
bar_type_volume = BarType.from_str(
"BTC-USDT-PERP.BINANCE-1000-VOLUME-LAST-INTERNAL"
# 每累積 1000 單位成交量就生成一根 K 線
)
self.subscribe_bars(bar_type_volume)
def on_bar(self, bar: Bar):
# 當聚合完的 K 線到達時,這個回調被觸發
self.log.info(f"收到 K 線: {bar}")K 線聚合類型
| 聚合方式 | BarType 格式 | 說明 | 適用策略 |
|---|---|---|---|
| 時間聚合 | 15-MINUTE | 每 15 分鐘一根 | 趨勢跟隨策略 |
| 成交量聚合 | 1000-VOLUME | 每 1000 單位成交量一根 | 量能突破策略 |
| 筆數聚合 | 500-TICK | 每 500 筆交易一根 | 高頻交易策略 |
| 價值聚合 | 1000000-VALUE | 每 100 萬單位價值一根 | 合約類策略 |
五、自定義數據類型
如果你有自己的數據(例如:情緒指標、新聞事件),可以定義自定義數據類型在 NautilusTrader 中使用:
"""
定義一個自定義數據類型:市場情緒指數
"""
from nautilus_trader.model.custom import customdataclass
# 用 @customdataclass 裝飾器定義自定義數據
@customdataclass
class SentimentData:
instrument_id: str # 品種 ID
score: float # 情緒分數 (-1.0 到 1.0)
source: str # 來源(Twitter, Reddit 等)
# 在策略中訂閱和使用
class MyStrategy(Strategy):
def on_start(self):
# 訂閱自定義數據
self.subscribe_data(
data_type=SentimentData,
metadata={"instrument_id": "BTC-USDT-PERP.BINANCE"},
)
def on_data(self, data):
if isinstance(data, SentimentData):
if data.score > 0.8:
self.log.info("🟢 市場情緒極度樂觀!")
elif data.score < -0.8:
self.log.info("🔴 市場情緒極度悲觀!")六、序列化格式
NautilusTrader 支援多種序列化格式,用於不同場景:
| 格式 | 用途 | 速度 | 說明 |
|---|---|---|---|
| Apache Arrow / Parquet | 數據持久化(DataCatalog) | ⚡⚡⚡ | 列式存儲,壓縮率高,讀取極快 |
| MsgPack | 網路傳輸、Redis 緩存 | ⚡⚡ | 二進制 JSON,體積小 |
| JSON | 配置文件、日誌 | ⚡ | 人類可讀,方便除錯 |
❌ 用 JSON 存 100 萬根 K 線:檔案大小約 500MB,讀取需要 30+ 秒。
✅ 用 Parquet 存 100 萬根 K 線:檔案大小約 50MB(壓縮 10 倍),讀取只需 1-2 秒。
七、互動練習
你有以下三種數據,應該分別用哪個 Wrangler?
- 來自 Binance 的
bid, ask, bid_size, ask_size報價數據 - 來自 TradingView 的
open, high, low, close, volumeK 線數據 - 來自某交易所的逐筆成交
price, quantity, side數據
QuoteTickDataWrangler
→ 有bid/ask就是報價數據BarDataWrangler
→ 有OHLCV就是 K 線數據TradeTickDataWrangler
→ 有price/quantity/side就是逐筆成交
💡 訣竅:看 DataFrame 的欄位名稱就能判斷用哪個 Wrangler。
以下 BarType 字串分別代表什麼?
ETH-USDT.BINANCE-5-MINUTE-LAST-EXTERNALBTC-USD.COINBASE-1000-VOLUME-LAST-INTERNALEUR-USD.SIM-1-HOUR-BID-INTERNAL
ETH-USDT.BINANCE-5-MINUTE-LAST-EXTERNAL- 品種: ETH-USDT(Binance 交易所)
- 時間: 5 分鐘
- 價格: LAST(最後成交價)
- 來源: EXTERNAL(數據由外部提供,不是自己聚合)
BTC-USD.COINBASE-1000-VOLUME-LAST-INTERNAL- 品種: BTC-USD(Coinbase 交易所)
- 聚合: 每 1000 單位成交量
- 價格: LAST(最後成交價)
- 來源: INTERNAL(由 NautilusTrader 自己從 tick 聚合)
EUR-USD.SIM-1-HOUR-BID-INTERNAL- 品種: EUR-USD(模擬交易所)
- 時間: 1 小時
- 價格: BID(買入價)
- 來源: INTERNAL(引擎自己聚合)
你有一個 CSV 檔案,包含過去 1 年的 BTC 1 分鐘 K 線數據(約 52 萬行)。你的策略需要 15 分鐘 K 線。描述你的數據處理步驟。
- 步驟 1: 用 pandas 讀取 CSV
df = pd.read_csv("btc_1m.csv") - 步驟 2: 用
BarDataWrangler轉換為Bar對象wrangler = BarDataWrangler(bar_type=..., instrument=...) bars = wrangler.process(df) - 步驟 3: 存入
DataCatalog(一次性操作)catalog = ParquetDataCatalog("./my_catalog") catalog.write_data(bars) - 步驟 4: 在策略的
on_start()中訂閱 15 分鐘 K 線bar_type_15m = BarType.from_str( "BTC-USDT-PERP.BINANCE-15-MINUTE-LAST-INTERNAL" ) self.subscribe_bars(bar_type_15m)
💡 關鍵:你不需要自己把 1 分鐘 K 線合併成 15 分鐘!
NautilusTrader 的 BarAggregator 會自動幫你做。你只需要載入 1 分鐘數據,然後訂閱 15 分鐘 K 線。
八、本模組重點回顧
- Wrangler:把 pandas DataFrame 轉換成 NautilusTrader 專用數據對象
- DataCatalog:用 Parquet 格式存儲數據,讀取速度比 CSV 快 10-50 倍
- BarAggregator:自動把小時間框架 K 線聚合成大時間框架(時間/成交量/筆數/價值)
- 自定義數據:可以定義自己的數據類型(情緒、新聞等)
- 序列化格式:Parquet 存數據、MsgPack 傳網路、JSON 做配置
- INTERNAL vs EXTERNAL:INTERNAL 由引擎自己聚合,EXTERNAL 由外部數據源提供