iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0

台股的免費資料源不少。券商 API、政府開放資料、幾個做得不錯的第三方套件,裝一裝就有日線和財報可以用。

我還是自己爬了 868 萬列。

倒不是因為喜歡跟交易所的限流器搏鬥。是有四件事,用現成的拿不到,而其中三件會讓回測失真,你還不會發現。


一、下市的公司

大部分現成資料源的做法是:先拿一份股票清單,再逐檔去查歷史。

那份清單是現在的清單。

我把 20 年日線攤開來數,只算普通股(權證、ETF、存託憑證都排除):

20 年間有過成交紀錄的普通股   2,241 檔
最近一個月仍有報價            1,978 檔
已經沒有報價                    263 檔(11.7%)

存活者偏差

用今天的清單去回溯 2012 年,你會漏掉那年 1,463 檔裡的 147 檔。每十檔漏一檔,年代越早漏得越多。

漏掉的那些公司,長得不太一樣

如果那 10% 跟其他 90% 差不多,其實無所謂。問題是不一樣。

我算了每檔股票「消失前最後 12 個月」的報酬。有足夠歷史可以算的下市公司是 239 檔:

                      中位數    跌超過 50% 的比例
消失的 239 檔          +1.8%          24.3%
仍在市場的 1,898 檔     +0.6%           2.1%

中位數差不多,但崩跌的比例差了十二倍。

存活者偏差漏掉的不是平均值,是災難。四分之一的下市公司在消失前一年腰斬過,而你的回測裡沒有它們,所以你的策略從來不必面對那種情況。

(這個對照不算嚴謹。消失的公司各自有不同的「最後 12 個月」,仍在市場的則是同一段近期。不過尾巴差十二倍,不是這種偏差能解釋的。)

逐日抓全市場就沒這個問題。每天抓一次當天的收盤行情表,那天在市場上的股票自然都在裡面,包括三個月後要下市的那些。清單變成資料的結果,不是輸入。

順帶一提,這樣抓也比較省。逐檔逐月查要幾十萬次請求,逐日抓上市是五千次左右。


二、發布日

現成 API 幾乎都不給的一個欄位:這筆數字,哪一天才公開?

API 告訴你「2024 年 1 月營收 2,157.9 億元」。它不會說這個數字要到 2024 年 2 月 10 日才公布。

少了這欄,你就沒辦法從架構上保證回測沒偷看未來,只能每次寫程式的時候自己記得。

這件事值多少我量過。同一個月營收訊號,只把下單時點從「營收公布後」改成「營收月底」,提早十天,年化報酬從 5.23% 變成 24.33%,夏普 0.34 變 1.05。而且偷看那版的最大回落還比較小,績效表上看不出任何異常。

十天。所以每筆資料都得帶著「哪天才拿得到」,而這只能在爬的時候順手記下來。


三、額度

這條最平凡,但它決定你能不能做研究。

回測不是跑一次。一個特徵要試十種算法,一個參數要掃十個值,改一次程式要重跑全部歷史。免費 API 一天幾百到幾千次請求,跑兩輪就沒了,然後等明天。

自己爬完,資料躺在本機的 parquet 檔裡。20 年日線 868 萬列、幾百 MB,讀進記憶體幾秒鐘。想跑幾次跑幾次。


四、parser 一定會錯,而你要能離線修

前三條是老生常談。第四條是我今天才又被上了一課的。

交易所把「沒有成交」寫成 0 元

做資料品質檢查的時候,我看到還原股價裡有一堆 +inf% 的單日報酬。那代表前一天的價格是 0。

追下去是這個:

0 元的無成交日

2011-11-08  8077   收盤 6.98   成交量 1000
2011-11-09  8077   收盤 0.00   成交量 0      ← 沒有成交
2011-11-10  8077   收盤 0.00   成交量 0      ← 沒有成交
2011-11-14  8077   收盤  NaN   成交量 0      ← 同樣沒有成交
2011-11-15  8077   收盤 6.91   成交量 1000

上櫃的收盤行情表對「當日無成交」有兩種寫法。我一開始以為是年代差異,翻了原始回應才發現,這兩種寫法出現在同一個月裡:

交易所原始回應(8077,2011 年 11 月)
  11/09  "0.00"      11/14  "--"
  11/10  "0.00"      11/16  "--"
  11/11  "0.00"      11/17  "--"

我的清洗規則認得 --,會轉成 NaN。0.00 是合法數字,沒有任何規則會攔它。於是「這天沒人交易」變成「這天股價是 0 元」,還原股價照樣把它乘上調整係數,跨過這天的報酬計算就是 ±inf 或 −100%。

規模是 112,356 列,佔全部日線的 1.29%,全部集中在上櫃 2008 到 2011 年。

它不報錯。程式跑完了,資料看起來也正常,除非你特地去找。

修好花了六分鐘,沒對交易所發任何一次請求

這才是第四條的重點。

我的爬蟲有個設計:所有網路回應,在解析之前先原封不動 gzip 存進 data/raw/

所以發現 parser 有問題的時候,我做的是:改 parser(加三行,把 OHLC 全為 0 的列的價格設成 NaN),然後用同一個指令重跑,快取直接命中。

連網抓取    3.1 秒/天
離線重解析  0.1 秒/天        快 30 倍
4,869 個交易日約 6 分鐘,網路請求 0 次

修完之後:

殘留的 0 元價格         112,356 → 0
無法解釋的單日跳空       0.828% → 0.120%

如果沒存原始回應,這件事的代價是重新對 TPEx 發 4,869 次請求、等四個小時,還要祈禱那幾天交易所沒改版沒擋我。實務上大部分人會選擇「算了,把異常值濾掉就好」,然後 bug 永遠留在資料裡。

現成 API 沒辦法這樣做,因為你拿到的已經是別人解析過的結果。別人的 parser 有沒有犯同樣的錯,你不知道,也修不了。


上一篇
Day 4 - 開工前的四條規矩,違反任何一條回測都會騙你
下一篇
Day 6 - 被交易所擋了幾百次,還能接著跑的爬蟲
系列文
台股日頻量化交易:開發一條從資料、特徵、模型到每日下單清單的產線8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言