回測:資料與週期
所屬章節:回測
資料怎麼進引擎:從哪裡取歷史 K 線、有哪幾種餵入方式、框架對時序的要求。
三種餵入方式
| 方式 | 呼叫 | 觸發事件 | 適用 |
|---|---|---|---|
| 一、整份 K 棒 | core.feed_data(df) |
on_bar、on_intrabar、on_position |
最快;單一頻率、不需多週期訊號 |
| 二、週期聚合 | core.set_tick_period(...) + core.feed_tick_df(df) |
上述 + on_bar2 / on_bar3、on_other* |
多週期訊號;用小 K 模擬盤中,停損觸發順序最準 |
| 三、單筆 tick | core.set_tick_period(...) + core.feed_tick(row) |
同上 | live 即時行情 |
feed_data不走週期聚合系統,所以不會觸發on_bar2/on_bar3/on_other*。feed_tick/feed_tick_df是呼叫set_tick_period之後才動態掛上的方法 (會印出[加載] feed_tick)。
feed_data
| 參數 | 說明 |
|---|---|
df |
K 棒 DataFrame,欄位由 args / columns 對應 |
args |
[DateTime欄, Open欄, High欄, Low欄, Close欄, Volume欄] 六個欄名,依序對應 |
columns |
要一併帶進每根 bar 的額外欄位(例如積木訊號欄、['Settlement']) |
args 只是告訴引擎哪一欄扮演哪個角色;row 裡的 key 一律是英文
Open/High/Low/Close/Volume。columns 帶進去的則保留原欄名。
# 積木訊號當進出場:sig 欄一定要在 columns 宣告,否則 row 裡沒有
bars['sig'] = bars['DateTime'].map(sig_series).fillna(False)
core.feed_data(bars, ['DateTime','開盤價','最高價','最低價','收盤價','成交量'],
columns=['sig'])
set_tick_period
core.set_tick_period(freq=60,
period=[['0845','1345'], ['1500','0500']],
need_settlement=True,
args=['DateTime','開盤價','最高價','最低價','收盤價','成交量'],
columns=[])
| 參數 | 說明 | 預設 |
|---|---|---|
freq |
聚合頻率。int(分鐘)為單一週期;list 為多週期,例如 [60, 'D'] 代表 base=60 分(觸發 on_bar)+ 日線(觸發 on_bar2,純訊號不進帳務)。'D' 代表日線,日盤 + 前一夜盤視為同一交易日 |
60 |
period |
交易時段區間列表。期貨預設日盤 08:45–13:45 + 夜盤 15:00–隔日 05:00;股票常用 [['0900','1330']] |
期貨日夜盤 |
need_settlement |
是否計算結算日(期貨適用) | True |
args / columns |
同 feed_data |
— |
freq 是 list 時:freq[0] → on_bar(走帳務),freq[1] → on_bar2,
freq[2] → on_bar3,以此類推(純訊號)。
設了第 N 週期卻沒實作對應的 on_barN,框架會印警告並略過。
set_other_period(對標商品)
對標商品只供訊號參考、完全不進帳務,觸發 on_other / on_other2 / on_other3。
feed_tick_df
core.feed_tick_df(df1) # 只餵主商品
core.feed_tick_df(None, df2) # 只餵對標
core.feed_tick_df(df1, df2) # 主 + 對標,依時間交錯(同一時點對標先行)
feed_tick(單筆,live 用)
| key | 說明 |
|---|---|
time |
該筆 tick 的時間,字串 'YYYYMMDDHHMMSS' |
price |
成交價 |
vol |
成交量 |
聚合週期 freq
get_*_ohlc 的 freq 與 set_tick_period 的 freq 用同一套轉換邏輯,
所以「先轉好再餵」與「餵小週期讓引擎聚合」兩條路徑產生的 K 棒逐根相同(已逐欄驗證 OHLCV):
# 路徑 A:先轉好再餵
dfD = bt.Tools.get_fut_ohlc('TXF', freq='D')
core.feed_data(dfD)
# 路徑 B:餵小週期,讓引擎自己聚合
df60 = bt.Tools.get_fut_ohlc('TXF', freq=60)
core.set_tick_period(freq='D')
core.feed_tick_df(df60) # 餵 1 分 K 也可以,結果一樣
freq 可傳分鐘數(int)或日線('D')。日線的規則:日盤 + 前一夜盤視為同一交易日。
取得歷史資料
bt.Tools 提供個股與期貨的歷史 K 線下載,內部依 symbol 分兩段快取:
原始資料只下載一次,之後不同 freq / period 組合各自快取聚合結果,
重複呼叫不會重複下載或重算。
get_stock_ohlc
取個股/ETF 歷史 K 線。內部固定抓「回溯約 4 年、到昨日為止」的 1 分 K 原始資料再聚合。
| 參數 | 說明 | 預設 |
|---|---|---|
symbol |
個股/ETF 代號,例如 '0050'、'2330' |
必填 |
freq |
聚合頻率(分鐘) | 1 |
adj |
是否還原權息。True 時用「還原日 K 價量資料」的 收盤價/次日開盤參考價 比例,依交易日往前填補後調整開高低收(乘)與成交量(除) |
True |
period |
交易時段區間列表 | [['0900','1330']] |
回傳欄位:DateTime(K 棒結束時間)、dt_period(原始 tick 涵蓋的起訖時間字串)、
開盤價 / 最高價 / 最低價 / 收盤價 / 成交量。
adj=True 時多一欄 ratio(還原比例係數,供除錯驗證)。
get_fut_ohlc
取期貨歷史 K 線(日盤+夜盤合併)。symbol 省略或 None 視為 'TXF',
而 'TXF' 直接讀預先快取好的近期資料(不用另外下載),其他商品才會即時下載。
內部固定抓「回溯約 2 年、到昨日為止」的 1 分 K。
回傳欄位:DateTime、dt_period、開盤價 / 最高價 / 最低價 / 收盤價 / 成交量、
Settlement(該筆所屬的結算日)、結算(布林,標記結算日 13:30 強制切斷的那根 K 棒)。
df = bt.Tools.get_stock_ohlc('0050', freq=60, adj=False)
df_fut = bt.Tools.get_fut_ohlc('TXF', freq=60)
要跟積木訊號對齊時:不要用 get_stock_ohlc
積木的 sig 是日頻矩陣(來自 api.MSMP.日_K),而 get_stock_ohlc 是
1 分 K 聚合的結果。頻率對不上,還要多下載一份 1 分 K。
兩邊都取 MSMP 日頻時,日期 index 天然完全對齊,不需要 reindex/ffill:
import pandas as pd
def daily_bars(api, symbol):
k = api.MSMP.日_K
zh2en = {'開盤價':'Open','最高價':'High','最低價':'Low','收盤價':'Close','成交量':'Volume'}
df = pd.DataFrame({zh: getattr(k, en)[symbol] for zh, en in zh2en.items()})
df.index.name = 'DateTime'
return df.reset_index()
完整範例見 回測總覽。
時序管控
feed_data / feed_tick / feed_tick_df / feed_other 四個入口都有時序管控。
時間倒退的資料若不擋,會被當成新的一根接在後面,造成 row_list 時序錯亂、
self.row 變成舊價、停損停利以錯誤價位判斷,而且不會產生任何錯誤訊息。
處理分兩道:
| 道 | 處理對象 | 做法 |
|---|---|---|
| 排序 | 同一批資料內部亂序 | 餵入前檢查是否遞增,否則自動穩定排序並提示。已排序時零成本 |
| 守門 | 與先前已餵入的資料重疊 | 重疊區段整段丟棄,並輸出一行總結 |
| 情況 | 行為 |
|---|---|
| 同一批資料內部時間亂序 | 自動穩定排序後餵入 |
| 同一份 df 內同一時間多列 | 收下,維持原相對順序 |
時間與前一筆相同(單筆 feed_tick) |
收下(逐筆行情同秒多筆成交屬正常) |
| 與先前已餵資料重疊的區段 | 整段丟棄 |
| 邊界上時間完全相同的一列 | feed_tick_df 丟棄(增量累加,重複會重計成交量)feed_data 保留(整根覆蓋,且需支援重餵更新當前 K 棒) |
主商品與對標各自維護水位線,互不影響。重疊區段在進入迴圈前就向量化濾除, 整段重疊時不做 K 棒轉換(20 萬列實測 1.98 秒 → 0.004 秒)。
會看到的警告:
[警告] 主商品資料時間 20240102103000 早於前一筆 20240102120000,已忽略(時序守門)
[警告] feed_tick_df 主商品: DateTime 沒有依時間遞增,已自動排序後才餵入
[警告] feed_data: 有 2 列時間早於已餵入的前一筆,已忽略(時序守門)
框架會幫你排序,但看到排序警告就該回頭查資料來源——那代表你的 df 本來就是亂的。