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

模組 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
圖 1:數據從來源到回測引擎的完整管線

二、Wrangler — 數據格式轉換器

💡 為什麼需要 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 欄位輸出數據類型適用場景
QuoteTickDataWranglerbid, ask, bid_size, ask_sizeQuoteTick有完整報價數據(外匯、加密貨幣)
TradeTickDataWranglerprice, quantity, aggressor_sideTradeTick逐筆成交數據
BarDataWrangleropen, high, low, close, volumeBarK 線數據(最常見)
OrderBookDeltaDataWrangleraction, side, price, sizeOrderBookDelta訂單簿增量數據(高頻)

三、DataCatalog — 數據倉庫

💡 為什麼需要 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
圖 2:DataCatalog 的寫入與讀取流程

寫入數據到 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 線聚合 — 自定義時間框架

💡 為什麼需要 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 存儲所有數據?

❌ 用 JSON 存 100 萬根 K 線:檔案大小約 500MB,讀取需要 30+ 秒。
✅ 用 Parquet 存 100 萬根 K 線:檔案大小約 50MB(壓縮 10 倍),讀取只需 1-2 秒。

七、互動練習

🧠 練習 1:選擇正確的 Wrangler

你有以下三種數據,應該分別用哪個 Wrangler?

  1. 來自 Binance 的 bid, ask, bid_size, ask_size 報價數據
  2. 來自 TradingView 的 open, high, low, close, volume K 線數據
  3. 來自某交易所的逐筆成交 price, quantity, side 數據
  1. QuoteTickDataWrangler
    → 有 bid/ask 就是報價數據
  2. BarDataWrangler
    → 有 OHLCV 就是 K 線數據
  3. TradeTickDataWrangler
    → 有 price/quantity/side 就是逐筆成交

💡 訣竅:看 DataFrame 的欄位名稱就能判斷用哪個 Wrangler。

🧠 練習 2:解讀 BarType 字串

以下 BarType 字串分別代表什麼?

  1. ETH-USDT.BINANCE-5-MINUTE-LAST-EXTERNAL
  2. BTC-USD.COINBASE-1000-VOLUME-LAST-INTERNAL
  3. EUR-USD.SIM-1-HOUR-BID-INTERNAL
  1. ETH-USDT.BINANCE-5-MINUTE-LAST-EXTERNAL
    • 品種: ETH-USDT(Binance 交易所)
    • 時間: 5 分鐘
    • 價格: LAST(最後成交價)
    • 來源: EXTERNAL(數據由外部提供,不是自己聚合)
  2. BTC-USD.COINBASE-1000-VOLUME-LAST-INTERNAL
    • 品種: BTC-USD(Coinbase 交易所)
    • 聚合: 每 1000 單位成交量
    • 價格: LAST(最後成交價)
    • 來源: INTERNAL(由 NautilusTrader 自己從 tick 聚合)
  3. EUR-USD.SIM-1-HOUR-BID-INTERNAL
    • 品種: EUR-USD(模擬交易所)
    • 時間: 1 小時
    • 價格: BID(買入價)
    • 來源: INTERNAL(引擎自己聚合)
🧠 練習 3:設計數據管線

你有一個 CSV 檔案,包含過去 1 年的 BTC 1 分鐘 K 線數據(約 52 萬行)。你的策略需要 15 分鐘 K 線。描述你的數據處理步驟。

  1. 步驟 1: 用 pandas 讀取 CSV
    df = pd.read_csv("btc_1m.csv")
  2. 步驟 2: 用 BarDataWrangler 轉換為 Bar 對象
    wrangler = BarDataWrangler(bar_type=..., instrument=...)
    bars = wrangler.process(df)
  3. 步驟 3: 存入 DataCatalog(一次性操作)
    catalog = ParquetDataCatalog("./my_catalog")
    catalog.write_data(bars)
  4. 步驟 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 線。

八、本模組重點回顧

📋 你應該記住的關鍵點
  1. Wrangler:把 pandas DataFrame 轉換成 NautilusTrader 專用數據對象
  2. DataCatalog:用 Parquet 格式存儲數據,讀取速度比 CSV 快 10-50 倍
  3. BarAggregator:自動把小時間框架 K 線聚合成大時間框架(時間/成交量/筆數/價值)
  4. 自定義數據:可以定義自己的數據類型(情緒、新聞等)
  5. 序列化格式:Parquet 存數據、MsgPack 傳網路、JSON 做配置
  6. INTERNAL vs EXTERNAL:INTERNAL 由引擎自己聚合,EXTERNAL 由外部數據源提供