跳至主要內容
台股研究 約 8 分鐘閱讀

營收驚喜選股:月營收 SURGE 前 30 檔年化 32.4%,
夏普贏 0050

營收驚喜選股怎麼做?把 Jegadeesh 與 Livnat 的標準化營收驚喜(SURGE)改成台股月營收版,每月買分數最高的 30 檔:2015 到 2026 年年化 32.4%、日夏普 1.23,0050 含息 20.2%、1.06;9 組鄰近參數全贏,但近 3 年落後 0050,附程式碼與三項驗證。

免安裝,用 AI 重跑這篇的策略

在瀏覽器開啟 FinLab Studio,AI 依本文的條件重跑回測,接著可以改條件再測。

在 Studio 重跑
營收超乎預期 CAGR 32.4% Sharpe 1.23

月營收驚喜選股回測:SURGE 前 30 檔、月營收動能 10 檔與 0050 含息 2015-2026 累積淨值,年化分別為 32.4%、45.9%、20.2%

依本文重跑

執行這個策略

複製提示詞,讓 AI 依照上方證據的資料、條件與風險檢查重跑一次。

告訴你的AI:

幫我設定 FinLab,重跑月營收驚喜(SURGE)前 30 檔選股並和 0050 含息比較,請讀:https://finlab.finance/setup?relatedUrl=/blog/monthly-revenue-surprise-strategy

免安裝,開啟 FinLab Studio

營收成長多少不重要,重要的是比這家公司自己過去的成長「多出多少」。我們把學術上的標準化營收驚喜(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 改成月頻:

  1. 差額 = 本月營收 − 去年同月營收(新台幣金額,不是百分比)。
  2. 對照組 = 前 24 個月的差額,算平均與標準差(不含本月)。
  3. SURGE = (本月差額 − 對照組平均)÷ 對照組標準差。

用金額差而不是年增率,是照論文原本的寫法;年增率在去年同月基期很低的公司會爆出幾百 % 的數字,金額差除以自己的標準差就不會被基期影響。跟盈餘公布後漂移(Bernard 與 Thomas(1989))比較,兩者都是「好消息公布後股價還在反應」,但那篇研究的是季盈餘,不是月營收;台股月營收公布後的漂移,在月營收公告漂移另有回測。

營收驚喜分五組:分數越高,之後報酬越好

在看策略之前,先確認這個分數本身有沒有用。每月 11 日把流動性門檻內的股票依 SURGE 分成五組,每組約 128 檔、組內等權持有一個月,同樣扣手續費與證交稅:

營收驚喜分五組 2015-2026 年化報酬與日夏普:驚喜最低組年化 -3.9%、日夏普 -0.10,最高組年化 25.7%、日夏普 1.12,五組由低到高遞增

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%,營收大幅低於自己平常水準的公司,之後的股價也最差。

策略規則與程式碼

規則只有四條:

  1. 股票池:上市櫃 4 碼普通股,排除金融保險與存託憑證,保留已下市股票;20 日平均成交金額 2,000 萬元以上。
  2. 訊號:上面定義的 SURGE,回看 24 個月。
  3. 選股:分數最高的 30 檔,等權持有;同分時成交金額大的優先。
  4. 換股:每月 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,但最近三年落後

月營收驚喜選股每年報酬與 0050 含息比較:2015 到 2023 年多數年度領先,2024、2025、2026 年都落後

年度 營收驚喜 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,不扣無風險利率。三項都要過:

  1. 鄰近參數:回看月數取 12、24、36,持股取 20、30、50 檔,共 9 組,至少 7 組要贏。
  2. 前後兩段:回測期間在中點切開,原參數在兩段都要贏。
  3. 交易成本:用 sim() 預設的手續費與證交稅,不關成本。

這些條件在看到任何結果之前就寫在研究紀錄裡,之後沒有改過。

月營收驚喜 9 組鄰近參數日夏普熱圖:回看 12、24、36 個月與持股 20、30、50 檔,9 組介於 1.17 到 1.35,全部高於 0050 含息 1.06

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

月營收驚喜前後兩段日夏普:前段 2015-02 至 2020-12 為 1.16、0050 含息 0.87;後段 2020-12 至 2026-10 為 1.29、0050 含息 1.22

前段(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。

延伸閱讀

下載資源

檔案 內容
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 自動驗證、回測、給你答案

免費開始

更多多因子選股研究

查看全部