跳轉到

回測:資料與週期

所屬章節:回測

資料怎麼進引擎:從哪裡取歷史 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

core.feed_data(df,
               args=['DateTime', '開盤價', '最高價', '最低價', '收盤價', '成交量'],
               columns=['Settlement'])
參數 說明
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(對標商品)

core.set_other_period(freq=60, period=..., need_settlement=True, args=..., columns=[])

對標商品只供訊號參考、完全不進帳務,觸發 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 用)

core.feed_tick({'time': '20260821101500', 'price': 18500.0, 'vol': 3})
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

bt.Tools.get_stock_ohlc(symbol, freq=1, adj=True, period=[['0900', '1330']])

取個股/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

bt.Tools.get_fut_ohlc(symbol='TXF', freq=1,
                      period=[['0845', '1345'], ['1500', '0500']])

取期貨歷史 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 本來就是亂的。


相關頁