
執行這個策略
複製提示詞,讓 AI 依照上方證據的資料、條件與風險檢查重跑一次。
告訴你的AI:
幫我設定 FinLab,重跑月營收 7 個進階訊號的單獨與組合回測,請讀:https://finlab.finance/setup?relatedUrl=/blog/monthly-revenue-signals-backtest
月營收是台股獨有的月頻基本面資料,最常見的用法是年增率與營收創新高。我們把論文與實務上常見的 8 種進階用法搬到台股:營收公布早晚、營收驚喜、年增率加速、累計年增率、年增由負轉正、連續年增、產業擴散與單月營收創新高。 其中「公布早晚」沒有可靠的歷史公布時間,無法回測;其餘 7 個各做兩種版本、每種 9 組參數,從 2015 年 2 月跑到 2026 年 10 月。
結果:單獨選股只有營收驚喜三項驗證都過,日夏普值 1.23 對 0050 含息的 1.06;年增率加速、累計年增率、由負轉正、連續年增與產業擴散 45 組參數沒有一組贏 0050。加在月營收動能選股上的組合版,只有營收創新高優先通過驗證(1.62 對原版 1.42);年增率加速原參數 1.58 也高於原版,但 9 組只有 6 組高於原版,其餘有的追平、有的變差。
14 種寫法的關鍵數字
| 訊號 | 單獨版日夏普 | 9 組贏 0050 | 前/後段 | 組合版日夏普 | 9 組高於原版 | 結果 |
|---|---|---|---|---|---|---|
| 營收驚喜(SURGE) | 1.23 | 9/9 | 1.16/1.29 | 1.41 | 6/9 | 單獨版成立 |
| 年增率加速 | 0.62 | 0/9 | 0.69/0.58 | 1.58 | 6/9 | 都不成立 |
| 累計年增率(年初至今) | 0.49 | 0/9 | 0.37/0.59 | 1.18 | 2/9 | 都不成立 |
| 年增由負轉正 | 0.40 | 0/9 | 0.50/0.31 | 1.27 | 2/9 | 都不成立 |
| 連續 6 個月年增 | 0.79 | 0/9 | 0.73/0.85 | 1.35 | 2/9 | 都不成立 |
| 產業擴散 | 0.90 | 0/9 | 1.08/0.79 | 0.88 | 0/9 | 都不成立 |
| 單月營收創 12 個月新高 | 1.07 | 4/9 | 0.93/1.21 | 1.62 | 9/9 | 組合版成立 |
| 營收公布早晚 | — | — | — | — | — | 無法回測 |
| 0050 含息 | 1.06 | 基準 | 0.87/1.22 | — | — | — |
| 原版月營收動能 10 檔 | — | — | 1.38/1.47 | 1.42 | 基準 | — |
全部從 2015-02-11 起算到 2026-10-30,0050 是同一段日子的含息買進持有,前後兩段在 2020-12-18 切開。單獨版每月持有分數最好的 30 檔,組合版持有 10 檔;「9 組高於原版」比的是同持股數的原版月營收動能。用 finlab 2.2.24 實際跑出,手續費 0.1425% 與證交稅 0.3% 用 sim() 預設值扣除。
三項驗證怎麼判
本文的「贏」只有一個意思:同一段交易日裡,日夏普高於 0050 含息。日夏普=日報酬平均 ÷ 日報酬標準差 × √252,不扣無風險利率。一個訊號要三項都過才算成立:
- 換鄰近參數也要贏:每個訊號的主要參數取原值與上下各一個鄰近值,配上三種持股數,共 9 組,至少 7 組要贏 0050。
- 前後兩段都要贏:回測期間在中點切開,原參數在兩段要各自贏 0050。
- 含交易成本:
sim()預設的手續費與證交稅,不關成本。
組合版另外要比原版好:原參數日夏普高於原版 10 檔的 1.42,而且 9 組裡至少 7 組高於同持股數的原版(8、10、12 檔分別是 1.26、1.42、1.49)。原版月營收動能本身就比 0050 強很多,組合版幾乎都會贏 0050;這一條是用來判斷月營收訊號有沒有額外貢獻。
這些定義、參數與門檻都在看到任何結果之前寫在研究紀錄裡,之後沒有改過。
8 個訊號的定義與台股做法
月營收用 FinLab 的 monthly_revenue,每列是一個月,index 是營收公告期限日(每月 10 日,遇假日順延),當成可取得日。所有 rolling、連續 N 個月的運算都在月表上做完,再展開到每天;先展開再 rolling 會變成交易日,意思完全不同。年增率用 去年同月增減(%) 欄。
| 訊號 | 定義 | 參數(粗體=原值) | 出處 |
|---|---|---|---|
| 營收公布早晚 | 每月前 N 天就公布營收的公司 | — | Kross 與 Schroeder(1984) |
| 營收驚喜 | (本月營收 − 去年同月)減過去 L 個月同一差額的平均,除以標準差 | L 12/24/36 | Jegadeesh 與 Livnat(2006) |
| 年增率加速 | 本月年增率 − 前 k 個月年增率平均 | k 1/3/6 | — |
| 累計年增率 | 累計營收比去年同期 | 近 3 個月/年初至今/近 12 個月 | — |
| 年增由負轉正 | 本月年增 > 0、前 k 個月年增都 < 0 | k 1/3/6 | — |
| 連續年增 | 連續 N 個月年增 > 0 | N 3/6/12 | — |
| 產業擴散 | 產業內年增 > 0 的公司比例,取前 K 個產業 | K 3/5/8 | — |
| 單月營收創新高 | 本月營收是近 N 個月最高 | N 6/12/24 | — |
單獨版:上市櫃 4 碼普通股,排除金融保險與存託憑證,保留已下市股票;20 日平均成交金額 2,000 萬元以上。每月 11 日依分數取前 20/30/50 檔、等權持有。營收驚喜、加速、累計年增直接依分數排;由負轉正、連續年增、產業擴散、創新高是「符合條件」的訊號,符合的股票再依本月年增率排。產業分類用 security_categories,只算當月有年增資料的公司在 5 家以上的產業。
組合版:底是月營收動能選股(收盤價站上 20、60、120 日均線,近 3 個月平均營收高於近 12 個月,依 5 日平均漲跌幅取前 8/10/12 檔,每月 11 日換股、單檔 10% 停損)。候選股裡符合訊號條件的排前面:營收驚喜、加速、累計年增是當月全市場分數前 30%,其他照單獨版的條件,產業擴散是屬於前 K 個產業。
下面是 5 個訊號在月表上的寫法,7 個訊號、單獨與組合版、三項驗證的完整程式在文末的 research.py:
顯示程式碼
import numpy as np
import pandas as pd
from finlab import data
rev = pd.DataFrame(data.get("monthly_revenue:當月營收"))
yoy = pd.DataFrame(data.get("monthly_revenue:去年同月增減(%)")).replace([np.inf, -np.inf], np.nan)
# 營收驚喜:年成長金額比過去 24 個月的平常水準高出幾個標準差
yoy_change = rev - rev.shift(12)
past = yoy_change.shift(1).rolling(24, min_periods=24)
std = past.std()
surge = (yoy_change - past.mean()) / std.where(std > 0)
# 年增率加速:本月年增率減前 3 個月平均
acceleration = yoy - yoy.shift(1).rolling(3, min_periods=3).mean()
# 年增由負轉正:本月年增為正,前 3 個月都是負
turn_positive = (yoy > 0) & (yoy.shift(1) < 0) & (yoy.shift(2) < 0) & (yoy.shift(3) < 0)
# 連續 6 個月年增
grow_6m = (yoy > 0).rolling(6).sum() == 6
# 單月營收創 12 個月新高
new_high = (rev > 0) & (rev >= rev.rolling(12, min_periods=12).max())訊號 1:營收公布早晚為什麼無法回測
Kross 與 Schroeder(1984)在 Journal of Accounting Research 研究美國季報,發現提早公布財報的公司平均是好消息、之後股價較好,延後公布的偏向壞消息。台股公司每月 10 日前公布營收,有的 1 號、2 號就公布,有的拖到最後一天,看起來是做這個題目的好資料。
要做這個回測,需要每家公司每個月實際公布營收的日期。我們逐筆檢查 FinLab 月營收的寫入時間(monthly_revenue:key_date):2023 年以前的紀錄全部是同一天的資料回補時間,不是公司實際公布的時間;2025 年也是整批回補,只有 2024 年與 2026 年是公告當天前後寫入的。兩段加起來不到兩年、中間還斷了一年,不夠做前後兩段驗證,用推估的公布日回測又等於自己造資料,所以這個訊號照實列為無法回測,不納入成立或不成立的計算。
結果一:單獨選股,只有營收驚喜三項驗證都過
開頭的圖是 7 個訊號原參數的日夏普。營收驚喜 1.23 是唯一明顯高於 0050 1.06 的;單月營收創 12 個月新高原參數 1.07,只比 0050 多 0.01,但後段 1.21 對 0050 的 1.22 沒贏,9 組也只有 4 組贏,所以不成立。
另外 5 個差得很遠:年增率加速 0.62、累計年增率 0.49、由負轉正 0.40、連續 6 個月年增 0.79、產業擴散 0.90。它們的年化波動在 23% 到 27% 之間,和營收驚喜的 26.6% 差不多,差在報酬:年化分別只有 12.7%、9.1%、6.5%、18.9%、20.9%,營收驚喜是 32.4%。不扣交易成本時,這 5 個的日夏普也只有 0.57 到 1.06,沒有一個高於 0050,輸不是因為成本。
由負轉正每個月符合的公司平均約 80 家,扣掉流動性門檻後常常湊不滿 30 檔,原參數平均只持有 20 檔。產業擴散在前段(2015-2020)還有 1.08 贏 0050,後段掉到 0.79。
結果二:加在月營收動能上,只有營收創新高明顯更好

原版月營收動能已經用了「近 3 個月平均營收高於近 12 個月」這一條,再加別的月營收條件,多半只是在同一批候選股裡換排序:
- 營收創新高(1.62):9 組都高於同持股數的原版,前後兩段都贏 0050,是唯一過關的新策略,詳見營收創新高選股。它的改善集中在前段,後段 1.39 略低於原版的 1.47。
- 年增率加速(1.58):原參數高於原版,但 9 組只有 6 組高於原版。只拿上個月比(k=1)的三組是 1.20、1.41、1.41,全部低於同持股數的原版 1.26、1.42、1.49,換一個鄰近參數就不成立,不是一整片高原。
- 營收驚喜(1.41):和原版 1.42 幾乎一樣,9 組 6 組高於原版。營收驚喜單獨用有效,當成月營收動能的加分條件沒有幫助。
- 連續年增(1.35)、由負轉正(1.27)、累計年增(1.18):都比原版差。由負轉正與累計年增的後段(0.97、1.05)還輸 0050 的 1.22。
- 產業擴散(0.88):比原版差 0.55,9 組只有 3 組贏 0050。優先買「同產業多數公司營收成長」的股票,可能反而把月營收動能選到的強勢股換掉了。
結果三:9 組鄰近參數

每一列是一個訊號、每一格是一組參數(參數低中高 × 持股 20、30、50 檔)。營收驚喜 9 格都是藍色,日夏普最低 1.17、最高 1.35;營收創新高 50 檔與 30 檔的幾格是藍色,20 檔都是紅色。其他 5 個訊號 45 格裡最高的是連續年增 12 個月、50 檔的 0.94,仍低於 0050。
結果四:前後兩段

前段(2015-02 至 2020-12)0050 只有 0.87,營收驚喜 1.16、產業擴散 1.08、營收創新高 0.93 三個都贏;後段 0050 升到 1.22,只剩營收驚喜的 1.29 還贏。後段是 0050 很強的一段,本文多數單獨版都跟不上。
為什麼大部分月營收訊號不成立
單月營收雜訊很大。 年增率加速、由負轉正、累計年增都在比較年增率的變化,而年增率在去年基期很低的公司會出現幾百 % 的數字,一筆接單或一次性收入就能讓排名翻轉。營收驚喜用金額差除以公司自己的標準差,等於把每家公司「平常會波動多少」扣掉,這可能是它在單獨版裡唯一成立的原因。
月營收公開而且很早就被反映。 月營收每月 10 日前就公告,市場有很多人在看,年增率、創新高這些最直觀的用法很難再有多餘的報酬。月營收公告漂移的回測也發現,只依年增率排名買前 30 檔贏不了 0050。
這 11 年是 0050 很強的 11 年。 0050 含息在這段期間年化 20.2%、日夏普 1.06,後段日夏普 1.22。只做多、等權持有 30 檔,要在風險調整後贏過市值加權的 0050 並不容易,這和台股學術異常回測看到的情況相同。
組合版的底已經很強。 原版月營收動能日夏普 1.42,加任何條件都是在它的候選股裡換排序。只有營收創新高能讓 9 組全部高於原版,其他條件不是沒差,就是把好的候選股換掉。
回測方法與限制
| 項目 | 本文做法 |
|---|---|
| 交易成本 | sim() 預設手續費 0.1425%(買賣各一次)與證交稅 0.3%(賣出),未打折;另列零成本對照 |
| 滑價 | 未另外假設,以收盤價成交 |
| 股票池 | 單獨版:上市櫃 4 碼普通股,排除金融保險與存託憑證,含已下市股票;組合版沿用原版月營收動能的股票池 |
| 流動性過濾 | 單獨版 20 日平均成交金額 2,000 萬元以上;組合版沒有,沿用原版 |
| 排除類別 | 金融保險、存託憑證、ETF;未排除 KY 股、全額交割股 |
| 前視偏差 | 月營收以公告期限日當可取得日,11 日換股、sim() 再隔一天成交;產業分類與金融股排除用目前的分類 |
| 權重 | 等權;單獨版沒有停損,組合版單檔 10% 停損 |
| 周轉率 | 每月換股;營收驚喜單獨版每次約換掉 58% 的持股 |
| 樣本內外 | 全段樣本內;定義、參數與門檻在跑之前寫定,沒有樣本外測試 |
security_categories 是目前的產業分類,已下市公司保留下市前最後的分類,分類改過的公司會用到當時還沒公布的分類,產業擴散的結果會受影響。月營收用 FinLab 的鎖定版,少數晚於期限才公布的公司,在本文會被當成期限日就知道。14 種寫法、126 組參數在同一段資料上測試,碰巧過關的機率不低,這是本文把過關的兩個寫成獨立文章、但同時列出所有失敗結果的原因;這類風險可以看回測過度擬合的機率,前視偏差的定義在名詞解釋頁。
這個結果代表什麼、不代表什麼
代表:在本文的設定與 2015 到 2026 年這段期間,年增率加速、累計年增率、由負轉正、連續年增與產業擴散,單獨拿來選股都贏不過 0050,加在月營收動能上也沒有比原版好。營收驚喜可以單獨用,營收創新高適合當月營收動能的排序條件。
不代表:這些訊號完全沒有資訊。本文只測了「每月持有 20/30/50 檔、等權、持有一個月」與「加在月營收動能上」兩種用法,沒有測多空、分五組價差(營收驚喜的五組結果見營收驚喜選股)、更長的持有期或其他市場。月營收的這些用法也可能在不同的股票池(例如只看大型股)或不同期間有不同結果。
常見問題
月營收選股最有用的是哪個指標?
在本文測的 7 個訊號裡,單獨選股最有用的是營收驚喜,也就是本月年成長比公司平常的年成長多出幾個標準差。如果已經在用月營收動能,加上「營收創 12 個月新高的先買」最有幫助。
營收年增率加速是好訊號嗎?
單獨用不是:9 組參數日夏普介於 0.42 到 0.75,全部輸 0050 的 1.06。加在月營收動能上原參數是 1.58,看起來不錯,但只拿上個月比的三組都低於原版,鄰近參數不穩,照研究前寫定的規則不算成立。
營收由負轉正(營收拐點)之後會漲嗎?
本文的回測裡不會比大盤好。前 3 個月年增都是負、本月轉正的股票,每月最多持有 30 檔(符合的股票不多,平均只持有約 20 檔),年化 6.5%、日夏普 0.40,9 組參數全部輸 0050。
連續好幾個月營收成長的股票值得買嗎?
連續 6 個月年增的股票,年化 18.9%、日夏普 0.79,輸 0050。連續 3 個月與 12 個月的版本也一樣,9 組都沒贏。
為什麼不測營收公布早晚?
需要每家公司每個月實際公布營收的日期。FinLab 月營收的寫入時間在 2023 年以前與 2025 年都是資料回補的時間,不是公司公布的時間,只有 2024 年與 2026 年的紀錄不夠做前後兩段驗證,所以列為無法回測。
產業擴散(類股營收輪動)有效嗎?
前段 2015 到 2020 年日夏普 1.08 贏 0050,後段 0.79 輸。加在月營收動能上更差,日夏普從 1.42 降到 0.88。
可以自己改參數重跑嗎?
可以。文末的 research.py 會跑完 7 個訊號的單獨版與組合版、每種 9 組參數與前後兩段,約 5 分鐘,輸出 metrics.json 與 data.csv。
延伸閱讀
- 營收驚喜選股:本批唯一成立的單獨版訊號,含策略頁與完整驗證。
- 營收創新高選股:本批唯一成立的組合版,月營收動能優先買營收創 12 個月新高的股票。
- 月營收動能選股:組合版的底,策略頁每天更新。
- 用 Python 做月營收選股的三種方法:年增率、創新高與動能三種基本寫法。
- 月營收公告漂移:月營收公布前後的股價反應。
- 台股學術異常回測:10 個學術因子在台股的同類回測。
- Python 抓取月營收:自己下載全台股月營收。
- 股票選股完整指南:基本面、技術面、籌碼面選股方法的總覽。
下載資源
| 檔案 | 內容 |
|---|---|
| research.py | 7 個訊號的單獨版、組合版與三項驗證完整程式 |
| metrics.json | 每個訊號 9 組參數、前後兩段與零成本對照的結果,以及營收驚喜分五組、持股重疊與原版交易拆分 |
| data.csv | 14 種寫法原參數、原版月營收動能與 0050 含息的月底淨值 |
風險提示
本文所有策略與回測結果僅供教學與研究用途,不構成投資建議。過去績效不代表未來表現,本文多數訊號在回測期間落後 0050,成立的兩個訊號近幾年也落後。投資一定有風險,請依自身風險承受能力審慎評估。
最後更新:2026-10|回測區間:2015-02 ~ 2026-10|作者:FinLab 量化研究團隊(經量化研究員審閱)
FinLab AI
想建立自己的策略?
用自然語言描述你的選股想法,AI 自動驗證、回測、給你答案
免費開始

