想查一家台股公司的財報,最完整的地方不是看盤軟體,是公開資訊觀測站(mops.twse.com.tw)。公司依法申報的月營收、季報、股利、重大訊息,全部公告在這裡。
這篇介紹這個網站,以及怎麼把上面的報表變成選股用的因子——最後會拿一個因子,用十二年的資料實測給你看。
它是證交所建置的法定揭露平台。證交法要求公司申報的資訊——月營收、季報、年報、股利、重大訊息——申報進來就公告在這裡。這個定位決定了三個性質:
**它是源頭。**看盤軟體、財經網站上的財報數字,上游都是這裡。別人的整理可能漏、可能改格式,源頭不會。
它完整。涵蓋上市、上櫃、興櫃到公開發行公司,而且下市的公司還查得到。Day 5 講過生存者偏差——那 263 家消失的公司,資料都還在這裡。用廠商整理包最常見的問題就是只剩活著的公司。
**它免費。**不用註冊、不用金鑰、沒有額度。
網站上你會反覆用到的區塊:
營運概況 → 每月營業收入 月營收,每月 10 日前申報
財務報表 → 三大報表 損益表、資產負債表、現金流量表,逐檔查
彙總報表 同一張表全市場一次拉,適合批次
重大訊息與公告 即時的文字揭露,之後講 LLM 會回來用它
股東會及股利 股利分派,除權息還原(Day 8)的原料
2024 年網站改版成新介面,舊版搬到 mopsov.twse.com.tw 繼續活著——對寫程式的人來說是好消息,因為舊版每張表背後就是一個簡單的端點,POST 幾個欄位就回傳 HTML 表格:
body = fetcher.get(
"https://mopsov.twse.com.tw/mops/web/ajax_t164sb05", # 現金流量表
method="POST",
data={
"encodeURIComponent": 1, "step": 1, "firstin": 1, "off": 1,
"queryName": "co_id", "inpuType": "co_id", "TYPEK": "all",
"isnew": "false", "co_id": stock_id,
"year": year - 1911, # 民國年
"season": f"{quarter:02d}",
},
)
tables = pd.read_html(io.StringIO(body.decode("utf-8")))
彙總版更好用:ajax_t163sb04 一次回傳整個市場當季的綜合損益表、t163sb05 是資產負債表、t163sb06 是財務比率——批次抓的時候用彙總版,一季一個請求就拉完全市場。月營收最簡單,直接是靜態檔案 nas/t21/{market}/t21sc03_{年}_{月}.html。我照這個方式把 2012 年以來全市場的現金流量表抓完了——376 萬列、1,575 檔。
拿到報表不等於能分析。原始數字要過三道轉換,每一道跳過都會做出錯的結論。
MOPS 報表上的流量數字(營收、淨利、現金流)是從年初起的累計:查 Q2 給你的是一到六月,Q4 那欄就是全年。要單季得自己差分:
台積電 2023 營業活動現金流(十億元)
表上讀到的 385 → 552 → 847 → 1,242 (累計)
差分後 385 → 167 → 295 → 395 (單季)
直接拿表上的 Q2 當「第二季」,會把 167 用成 552。另外兩個小地雷:Q4 的表頭寫「113年度」而不是期間格式,解析時要另外認;「期初現金餘額」這類時點欄位不是累計,不能差分。
財報屬於哪一季,和你哪一天拿得到,是兩回事。證交法第 36 條的期限:Q1–Q3 是季末後 45 天,年報是年度終了後 3 個月。
def available_date(year, quarter):
"""財報最晚可取得日(證交法 §36)。45 天從「季末」起算——
Q1 是 3/31 + 45 天 = 5/15,不是 4 月中。"""
if quarter == 4:
return dt.date(year + 1, 3, 31)
month = 3 * quarter
quarter_end = dt.date(year, month, calendar.monthrange(year, month)[1])
return quarter_end + dt.timedelta(days=45)
2023Q1 → 2023-05-15 2023Q3 → 2023-11-14
2023Q2 → 2023-08-14 2023Q4 → 2024-03-31
2023 年報要到 2024 年 3 月底才保證拿得到;在那之前的回測用了它就是偷看(Day 4 第一條規矩)。等一下的實測全部照這條走:用 2023 年報選股,2024 年 4 月才建倉。
台積電的淨利是兆元等級,小公司是千萬等級,絕對值放在一起沒有意義。可比較的形式只有兩種:跟自己比(年增率、季增率)或跟另一個科目比(毛利 ÷ 營收、現金流 ÷ 淨利)。因子幾乎都長這樣——兩個科目的比值,或一個科目的變化率。
三道轉換做完,才輪到「分析」。而檢驗一個因子有沒有用,我用整個系列會反覆出現的標準流程——分位數檢驗:每年把所有股票按因子值排序、切成五等份,分別算之後的報酬,看兩件事:
示範一個從現金流量表長出來的經典因子。它回答一個損益表回答不了的問題:
損益表說賺錢。錢真的進來了嗎?
淨利是會計數字,混著應收帳款、存貨、各種「認列了但還沒收到錢」的項目;營業現金流是實際進出的錢。兩者一除:
coverage = operating_cashflow / net_income # 盈餘的現金含量,全年值
含金量 1 以上,賺的錢都收回來了;遠低於 1 甚至為負,獲利是紙上的。
先看極端。全庫有賺錢的公司裡,每七家就有一家營業現金流是負的(14.7%)。這群公司之後的表現:

樣本 隔年中位報酬
紅旗組 約 1,600 −1.0%
正常組 約 12,000 +1.6%
12 個財報年度裡,紅旗組落後 8 年
方向對,但 8/12 不算穩。問題出在二分法太粗——把「含金量 0.99」和「含金量負的」切在同一刀的兩側,浪費了資訊。上五分位檢驗:

Q1(最低) +0.4%
Q2 +1.3%
Q3 +2.4%
Q4 +2.9%
Q5(最高) +2.9%
Q5 贏 Q1:11 / 12 年(約 10,800 組樣本)
用第二節的兩個標準來讀:
**單調性過關。**Q1 到 Q4 一路上升,五個組別自己排好了隊——這比紅旗二分的 8/12 有說服力得多。
**逐年勝率過關,但要看輸的那年。**十二年裡十一年為正;唯一輸的是 2022 財報年度(2023 年建倉),Q5 反輸 14 個百分點——那年低含金量的公司反彈最兇。單一因子就是會有這種年份,這正是逐年檢查存在的理由。
強度要老實講。頭尾差約 2.5 個百分點的中位年報酬。這不是一條可以直接下單的策略——Day 3 算過台股來回成本 0.4710%,換股幾次就吃掉一半。它是給模型的一個輸入;模型的工作是把幾十個這種強度的訊號疊起來,那是第三幕的事。
方法比因子值錢。「造比率 → 對齊可得日 → 每年分位 → 看單調性和逐年勝率」這條流程不變,換個分子分母就是新因子:
投資活動現金流 ÷ 折舊 擴張強度——資本支出是折舊的幾倍
籌資活動現金流的正負 跟市場拿錢,還是還錢給市場
自由現金流 ÷ 市值 現金流版本的便宜程度
應計項目(淨利 − OCF) 盈餘品質的連續版,學術上最經典的那個
每一個都是十幾行程式的事。難的部分在資料,不在因子——這也是這個系列花五天打資料地基的原因:地基好了,上面蓋什麼都快。
**公開資訊觀測站是台股基本面的源頭。**免費、涵蓋全市場、下市公司也查得到。舊版的 ajax_* 端點對程式友善,彙總表一季一個請求就能拉完全市場。
**報表不能直接用,要過三道轉換。**累計拆成單季、對齊「拿得到的那一天」、絕對數字化成比率。三道都做完,因子才是乾淨的。
**檢驗因子就看兩件事:分位單調性、逐年勝率。**現金含量這個因子兩關都過——Q1 +0.4% 一路排到 Q5 +2.9%,十二年裡贏十一年。但頭尾差只有 2.5 個百分點,扣掉交易成本剩不了多少:它是給模型的一個輸入,不是一條能直接下單的策略。
單一因子的強度就是這樣。真正的火力來自把幾十個這種等級的訊號疊起來——那是模型的工作,第三幕見。