0050 在 2014-01-02 有一個不存在的暴跌,成因是分割還原只套到某個日期之後。它會把整年的高點灌成四倍,把結論整個翻過來,而且不會噴任何錯誤。
發布於 2026-09-06・約 4 分鐘
我在驗一個很單純的問題:0050 從 52 週高點回落 12% 的時候加碼,長期下來會不會贏過定期定額。
第一版跑出來的結論是「贏 5.42%」。看起來還行,但有個地方不對勁:0050 的訊號數量比其他三檔 ETF 多了一截,而它的波動並沒有比較大。
去畫價格序列,2014-01-02 那天有一根 -75% 的長黑。
台灣沒有這一天。
0050 在 2025 年做過一次一股分割四股。yfinance 拿到的序列裡,分割還原只套用到 2014 年以後的資料,2014 年以前的還是分割前的價格。
於是在 2014-01-02 那個接縫上,價格從舊制的一百多塊「跌」到新制的三十幾塊,看起來就是一根 -75%。
最惡劣的是:
>>> df["Stock Splits"].sum() 0.0
splits 欄位是空的。 所以你沒辦法用「有沒有分割事件」去偵測這個接縫。資料本身沒有留下任何「這裡動過手腳」的痕跡。
splits
我的策略核心是「距 52 週高點回落多少」。而 52 週高點是滾動視窗的最大值。
一根假的四倍價格,會讓它之後整整一年的 52 週高點都被灌成四倍。分母錯了四倍,「回落幅度」就永遠滿足門檻。
修掉之後所有數字都變了:
修正前 修正後 訊號天數 950 705 資金曲線 vs 定期定額 +5.42% -2.97% 120 日超額 95% CI —— [-1.74, +9.09] 牛市那一格的超額 —— -4.83pp
結論從「0050 加碼有效」變成「0050 加碼無效,而且區間包含 0,是四檔裡唯一一檔」。
原本我把 0050 排除在提示名單外,理由寫的是「保守選擇」。修完資料之後,理由從保守選擇變成有證據。同一個決定,但是知道自己為什麼這樣決定,跟不知道,是兩件事。
不要相信 splits 欄位,直接找「不合理的單日變動」:
import yfinance as yf import numpy as np def find_fake_breaks(symbol, threshold=0.30): df = yf.Ticker(symbol).history(period="max", auto_adjust=False) r = df["Close"].pct_change() bad = df[abs(r) > threshold].copy() bad["ret"] = r[abs(r) > threshold] return bad[["Close", "ret", "Stock Splits", "Dividends"]] print(find_fake_breaks("0050.TW"))
判斷準則很直接:台股有 10% 的漲跌幅限制。 單日變動超過 10%(留點餘裕抓 30%)而且不是除權息、不是分割,那筆資料就是錯的,沒有第二種可能。
這條在台股特別好用,因為漲跌幅限制給了你一個硬性的物理上界。美股沒有這個上界,同樣的偵測要改用別的方式(比對第二個資料源、或看成交量有沒有同步異常)。
除權息也會造成大跌,所以 Dividends 那一欄要一起看。真正的除息當天,Dividends 會有值。
Dividends
如果你只需要近期資料,最省事的是不要抓那麼久:
df = yf.Ticker("0050.TW").history(period="1y", auto_adjust=False)
我的正式程式就是這樣,只抓一年。所以線上行為從來沒被這個錯誤影響過,出事的只有回測。這也是為什麼它藏了那麼久:每天跑的東西是對的,只有做研究的時候才碰得到那段爛資料。
如果你需要長期序列,接縫之前的資料自己乘上比例:
BREAK_DATE = "2014-01-02" RATIO = 4.0 # 一股分割四股 mask = df.index < BREAK_DATE for col in ["Open", "High", "Low", "Close"]: df.loc[mask, col] /= RATIO df.loc[mask, "Volume"] *= RATIO
改完一定要回頭跑一次偵測,確認那根長黑不見了,而且沒有製造出新的。
如果這份資料要拿來做決策,換資料源比較實在。台灣的話證交所和櫃買中心都有公開的歷史資料端點,是原始成交價,沒有任何還原處理,也就沒有還原錯誤的可能。代價是除權息要自己處理。
我在這個專案上連續五次得到錯誤結論,五次的成因都一樣:拿一個沒校驗過的數字當事實往下推。
價格序列感覺是最不需要懷疑的東西,它是「原始資料」。但它其實是加工品——還原、補值、時區轉換、代號對應,每一層都可能出錯,而且出錯的時候通常不噴例外,只是給你一個看起來很正常的數字。
所以現在任何一份外部資料進來,我都會先跑三個檢查:極端值、缺漏、跟另一個來源比對。 三個都過才准拿去算東西。跑這三個檢查的成本大概十分鐘,比起後來要把整套結論重新推一遍,便宜太多了。
標籤:yfinance、台股、ETF、資料品質、Python、回測