
執行這個策略
複製提示詞,讓 AI 依照上方證據的資料、條件與風險檢查重跑一次。
告訴你的AI:
幫我設定 FinLab,重跑月營收驚喜(SURGE)前 30 檔選股並和 0050 含息比較,請讀:https://finlab.finance/setup?relatedUrl=/blog/monthly-revenue-surprise-strategy
營收成長多少不重要,重要的是比這家公司自己過去的成長「多出多少」。我們把學術上的標準化營收驚喜(SURGE)改成台股月營收版:每月營收公布後,買進驚喜分數最高的 30 檔。 2015 年 2 月到 2026 年 10 月回測,年化報酬 32.4%、日夏普 1.23、最大回撤 -35.1%,同期 0050 含息是 20.2%、1.06、-34.0%。9 組鄰近參數全部贏 0050,前後兩段也都贏。
代價也要先講清楚:這個策略的日夏普低於同一段期間重跑的月營收動能選股(1.42),而且 2024 到 2026 年每一年都落後 0050,近 3 年日夏普 1.29、0050 是 1.87。它的價值在於選股跟月營收動能幾乎不重疊,平均每天只有 15% 的持股相同,是另一條路線。
關鍵數字:營收驚喜、月營收動能與 0050
| 2015-02-11 至 2026-10-30 | 年化報酬 | 日夏普 | 最大回撤 | 年化波動 | 近 3 年日夏普 | 持股 |
|---|---|---|---|---|---|---|
| 月營收驚喜 30 檔(本文) | 32.4% | 1.23 | -35.1% | 26.6% | 1.29 | 30 檔 |
| 月營收動能 10 檔(對照) | 45.9% | 1.42 | -35.6% | 30.9% | 1.87 | 10 檔 |
| 0050 含息買進持有 | 20.2% | 1.06 | -34.0% | 19.8% | 1.87 | — |
本文策略在年化報酬與日夏普都勝過 0050 含息,最大回撤和 0050 差不多,年化波動比 0050 高 6.8 個百分點。11 年 8 個月下來淨值成長約 26.8 倍,0050 含息約 8.7 倍。所有數字都用 finlab 2.2.24、資料截至 2026-10-30 實際跑出,手續費 0.1425% 與證交稅 0.3% 用 sim() 預設值扣除。「近 3 年」指 2023-10-30 到 2026-10-30。
營收驚喜是什麼:跟自己的過去比
一般看月營收,最常用的是年增率:本月營收比去年同月多幾 %。問題是每家公司的「正常成長」不一樣。一家每個月都成長 30% 的公司,這個月成長 35% 只是正常表現;一家多年持平的公司突然成長 15%,才是真的出乎意料。
Jegadeesh 與 Livnat(2006)在 Journal of Accounting and Economics 用美國季報研究這件事,提出標準化營收驚喜(standardized unexpected revenue growth,SURGE):本季營收減去年同季,再減掉過去幾季這個差額的平均,除以它的標準差。換句話說,就是「這次的年成長,比公司平常的年成長多出幾個標準差」。他們發現營收驚喜高的股票在財報公布後繼續上漲,而且營收驚喜和盈餘驚喜各自帶有資訊。Ertimur、Livnat 與 Martikainen(2003)在 Review of Accounting Studies 也發現,市場對營收超乎預期的反應比對費用省下來的反應更大。
台股的優勢是月營收:上市櫃公司每月 10 日前就要公布上個月的營收,資訊比季報早一到三個月。本文把 SURGE 改成月頻:
- 差額 = 本月營收 − 去年同月營收(新台幣金額,不是百分比)。
- 對照組 = 前 24 個月的差額,算平均與標準差(不含本月)。
- SURGE = (本月差額 − 對照組平均)÷ 對照組標準差。
用金額差而不是年增率,是照論文原本的寫法;年增率在去年同月基期很低的公司會爆出幾百 % 的數字,金額差除以自己的標準差就不會被基期影響。跟盈餘公布後漂移(Bernard 與 Thomas(1989))比較,兩者都是「好消息公布後股價還在反應」,但那篇研究的是季盈餘,不是月營收;台股月營收公布後的漂移,在月營收公告漂移另有回測。
營收驚喜分五組:分數越高,之後報酬越好
在看策略之前,先確認這個分數本身有沒有用。每月 11 日把流動性門檻內的股票依 SURGE 分成五組,每組約 128 檔、組內等權持有一個月,同樣扣手續費與證交稅:

| SURGE 分組 | 年化報酬 | 日夏普 | 最大回撤 |
|---|---|---|---|
| Q1(驚喜最低) | -3.9% | -0.10 | -63.6% |
| Q2 | 1.9% | 0.20 | -44.3% |
| Q3 | 6.6% | 0.42 | -38.9% |
| Q4 | 14.0% | 0.73 | -36.7% |
| Q5(驚喜最高) | 25.7% | 1.12 | -36.6% |
五組由低到高一路遞增,沒有反轉。最高組約 129 檔全部買,日夏普 1.12 已經略高於 0050 的 1.06;本文策略再從裡面挑分數最前面的 30 檔,日夏普提高到 1.23。最低組 11 年多來年化 -3.9%,營收大幅低於自己平常水準的公司,之後的股價也最差。
策略規則與程式碼
規則只有四條:
- 股票池:上市櫃 4 碼普通股,排除金融保險與存託憑證,保留已下市股票;20 日平均成交金額 2,000 萬元以上。
- 訊號:上面定義的 SURGE,回看 24 個月。
- 選股:分數最高的 30 檔,等權持有;同分時成交金額大的優先。
- 換股:每月 11 日,也就是營收公告期限的隔天。沒有停損。
下面是核心程式,完整版(含統計與輸出檔案)在文末的 strategy.py:
顯示程式碼
import pandas as pd
from finlab import data
from finlab.backtest import sim
# 1. 股票池:4 碼普通股,排除 ETF、金融與存託憑證
adj_close = data.get("etl:adj_close")
categories = data.get("security_categories").set_index("stock_id")["category"]
excluded = {"金融保險業", "金融業", "存託憑證"}
stocks = [
s for s in adj_close.columns
if len(s) == 4 and s.isdigit() and not s.startswith(("0", "28", "91"))
and categories.get(s) not in excluded
]
days = adj_close.loc["2014-01-01":].index
# 2. 營收驚喜在月表上算:每列是一個月,index 是營收公告期限日
rev = pd.DataFrame(data.get("monthly_revenue:當月營收")).reindex(columns=stocks)
rev.index = pd.to_datetime(rev.index)
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. 展開到每個交易日,套用流動性門檻
surge = surge.reindex(days, method="ffill")
turnover = pd.DataFrame(data.get("price:成交金額")).reindex(columns=stocks)
turnover = turnover.rolling(20).mean().reindex(days)
score = surge.where(turnover > 2e7)
# 4. 分數最高的 30 檔,每月 11 日換股
rank = score.rank(axis=1, ascending=False)
tie_break = turnover.where(score.notna()).rank(axis=1, ascending=False, pct=True) * 0.5
position = ((rank + tie_break).rank(axis=1, method="first") <= 30).loc["2015-01-01":]
report = sim(position, resample="M", resample_offset="11D", upload=False)rolling 一定要在月表上做:先展開到每日再 rolling(24) 就變成 24 個交易日,意思完全不同。
回測結果:贏 0050,但最近三年落後

| 年度 | 營收驚喜 30 檔 | 0050 含息 |
|---|---|---|
| 2015(2 月起) | 5.1% | -9.2% |
| 2016 | 17.2% | 19.6% |
| 2017 | 30.9% | 18.1% |
| 2018 | -0.7% | -4.9% |
| 2019 | 42.5% | 33.5% |
| 2020 | 80.3% | 31.1% |
| 2021 | 76.9% | 21.9% |
| 2022 | -6.2% | -21.4% |
| 2023 | 48.1% | 27.5% |
| 2024 | 33.6% | 48.6% |
| 2025 | 24.4% | 36.8% |
| 2026(到 10 月底) | 59.4% | 63.2% |
12 個年度裡 8 年贏 0050,大部分領先集中在 2019 到 2023 年。2024 到 2026 年 0050 分別漲 48.6%、36.8%、63.2%,這個策略連續三年落後。它的持股以中小型股為主:持股的市值排名中位數約第 395 名,屬於市值前 50 大的只占 6.4%,和以大型權值股為主的 0050 走勢差很多。近 5 年(2021-10-30 起)的日夏普是 1.17,0050 是 1.24,也落後。
最大回撤 -35.1% 發生在 2018-06-11 到 2018-10-30,2019-11-04 才回到前高。單月最差是 2026 年 7 月的 -23.4%,下個月反彈 30.0%。每次換股約有 58% 的持股換掉、其餘續抱;回測把兩次換股之間的每一檔持有算成一筆,共 4,250 筆,其中 50.4% 賺錢。
三項穩健性驗證:鄰近參數、前後兩段、交易成本
「贏」只有一個意思:同一段交易日裡,日夏普高於 0050 含息。日夏普=日報酬平均 ÷ 日報酬標準差 × √252,不扣無風險利率。三項都要過:
- 鄰近參數:回看月數取 12、24、36,持股取 20、30、50 檔,共 9 組,至少 7 組要贏。
- 前後兩段:回測期間在中點切開,原參數在兩段都要贏。
- 交易成本:用
sim()預設的手續費與證交稅,不關成本。
這些條件在看到任何結果之前就寫在研究紀錄裡,之後沒有改過。

9 組日夏普介於 1.17 到 1.35,全部高於 0050 的 1.06。原參數(24 個月、30 檔)的 1.23 在中間,不是挑出來的最高點;最高的是回看 36 個月、20 檔的 1.35。

前段(2015-02-11 至 2020-12-18)日夏普 1.16 對 0050 的 0.87,後段(2020-12-21 至 2026-10-30)1.29 對 1.22。兩段都贏,但後段只領先 0.07,這也是前面說「最近落後」的另一面。不扣成本時日夏普是 1.40,手續費與證交稅吃掉 0.17。
和月營收動能的關係:選到的股票幾乎不同
站上的月營收動能選股用「近 3 月平均營收高於近 12 月」加上股價趨勢與短期動能,只買 10 檔。營收驚喜不看股價,只看營收和自己過去比的落差。兩者每天的持股平均只有 14.5% 相同,日報酬相關係數 0.63。
我們也照研究紀錄試了把兩者合在一起:在月營收動能的候選股裡,SURGE 排全市場前 30% 的排前面。原參數日夏普 1.41,原版是 1.42,9 組鄰近參數只有 6 組高於同持股數的原版,沒有達到「至少 7 組」的門檻,所以不算新策略。營收驚喜和月營收動能各自有效,但把驚喜當成月營收動能的加分條件並沒有幫助。
另外算了一個參考數字:兩個策略各放一半資金、每天再平衡,日夏普 1.48、最大回撤 -31.8%,日夏普與最大回撤都比兩者單獨好,年化 40.1% 則介於兩者之間。這個搭配沒有做三項驗證,只說明兩者的報酬來源不同。其他月營收訊號(年增率加速、由負轉正、連續年增、產業擴散等)的結果整理在月營收進階訊號回測。
回測方法與限制
| 項目 | 本文做法 |
|---|---|
| 交易成本 | sim() 預設手續費 0.1425%(買賣各一次)與證交稅 0.3%(賣出),未打折 |
| 滑價 | 未另外假設,以收盤價成交 |
| 股票池 | 上市櫃 4 碼普通股,排除金融保險與存託憑證,含已下市股票 |
| 流動性過濾 | 20 日平均成交金額 2,000 萬元以上 |
| 排除類別 | 金融保險、存託憑證、ETF;未排除 KY 股、全額交割股 |
| 前視偏差 | 月營收以每月公告期限日(10 日,遇假日順延)當可取得日,11 日換股、sim() 再隔一天成交;產業分類用目前的分類 |
| 權重 | 30 檔等權,沒有停損 |
| 周轉率 | 每次換股約換掉 58% 的持股,30 檔中每月約 18 檔換新 |
| 樣本內外 | 全段樣本內;參數與三項驗證條件在跑之前寫定,沒有樣本外測試 |
月營收用的是 FinLab 的鎖定版(monthly_revenue),數值以公告後第一個開盤前的最後一次寫入為準,之後的更正不回寫。少數公司晚於期限才公布,這幾筆在本文會被當成期限日就知道,影響很小但存在。本文沒有估算策略容量,30 檔中小型股實際能放多少資金取決於成交量。在同一段資料上測試很多訊號,總有幾個會碰巧過關,這種風險可以看回測過度擬合的機率,前視偏差與周轉率的定義在名詞解釋頁。
適合誰、不適合誰
適合:已經在用月營收選股、想要一個和月營收動能不同來源的策略的人;能接受連續幾年落後大盤的人;想研究台股特有月頻資料的人。
不適合:要求每年都贏 0050 的人,這個策略 2024 到 2026 年連續落後;不想每月換股的人;資金很大、需要集中在權值股的人。
常見問題
營收驚喜和營收年增率有什麼不同?
年增率是本月營收比去年同月多幾 %,所有公司用同一把尺。營收驚喜是拿本月的年成長金額和這家公司前 24 個月的年成長金額比,看高出幾個標準差,所以同樣成長 20%,對平常成長 5% 的公司是很大的驚喜,對平常成長 30% 的公司反而是退步。
為什麼不用分析師預估的營收當基準?
台股大部分中小型公司沒有分析師預估,就算有也不是每個月更新。用公司自己過去的營收當基準,每家公司每個月都算得出來,也是 Jegadeesh 與 Livnat 在論文裡的做法。
回看月數為什麼是 24 個月?
24 個月是原始設定,取上下各一個鄰近值 12 與 36 個月一起測。三個回看長度配上三種持股數,9 組都贏 0050,最高的是 36 個月,代表結果不是靠剛好挑到 24 個月。
近 3 年落後 0050,這個策略還有效嗎?
這個策略的持股以中小型股為主,市值前 50 大只占 6.4%,大型權值股領漲的年度很難跟上。11 年多來的日夏普與前後兩段都贏 0050,但最近三年確實落後,未來是否回到領先沒有人能保證。策略頁會每天更新,可以持續追蹤。
營收驚喜高的股票,公布後多久還會漲?
本文只測了「公布後持有一個月、下個月重新選」的寫法,沒有測更長或更短的持有期。分五組的結果顯示這一個月內,驚喜最高組和最低組年化差了將近 30 個百分點。
可以和月營收動能一起用嗎?
兩者持股平均只有約 15% 相同。把營收驚喜當成月營收動能的加分條件,日夏普沒有提高;各放一半資金的參考組合日夏普 1.48,但沒有做三項驗證。要搭配使用,建議先自己用不同期間重跑。
這個策略可以直接照做嗎?
這是研究與回測結果,不是投資建議。實際操作要考慮滑價、成交量與資金規模,本文沒有估算容量。想自己重跑或修改參數,可以下載文末的 strategy.py。
延伸閱讀
- 月營收動能選股:本文的對照策略,含三項驗證,策略頁有淨值曲線與程式碼。
- 月營收進階訊號回測:年增率加速、累計年增、由負轉正、連續年增、產業擴散與營收創新高的完整結果。
- 營收創新高選股:同一批研究裡另一個成立的策略,在月營收動能上優先買營收創 12 個月新高的股票。
- 月營收公告漂移:月營收公布前後的股價反應。
- 美股盈餘驚喜策略:美股用季盈餘做的同類研究。
- 營收暴增的陷阱:為什麼只看年增率容易買到一次性營收。
- 股票選股完整指南:基本面、技術面、籌碼面選股方法的總覽。
- 月營收資料:FinLab 月營收資料欄位說明。
下載資源
| 檔案 | 內容 |
|---|---|
| strategy.py | 本文策略的完整程式碼,會輸出 data.csv 與 metrics.json |
| data.csv | 本文策略與 0050 含息的每日淨值 |
| metrics.json | 本文核心數字與每年報酬 |
| research.py | 9 組鄰近參數、前後兩段、分五組、持股重疊與其他月營收訊號的驗證程式 |
| report_strategy.html | 本文策略的互動式回測報告 |
風險提示
本文所有策略與回測結果僅供教學與研究用途,不構成投資建議。過去績效不代表未來表現,本文策略最大回撤 -35.1%,2024 到 2026 年連續落後 0050。投資一定有風險,請依自身風險承受能力審慎評估。
最後更新:2026-10|回測區間:2015-02 ~ 2026-10|作者:FinLab 量化研究團隊(經量化研究員審閱)
FinLab AI
想建立自己的策略?
用自然語言描述你的選股想法,AI 自動驗證、回測、給你答案
免費開始

