Agentic Research
首頁/工具/可比公司分析自動化:一次跑完一整個板塊

可比公司分析自動化:一次跑完一整個板塊

2026/09/3019 分鐘君澤智庫最後更新 2026/09/30

「把整個板塊的可比公司跑一遍」聽起來是個體力活,於是很多人把希望寄託在 AI 上:丟一串股票代碼進去,讓它吐出倍數表。結果往往是一張看起來專業、實際上每家公司口徑都不一樣的表格——A 家用的是上一個財政年度的數據,B 家幣別沒換,C 家的 EBITDA 含著一筆處置收益。這種表格比沒有表格更危險,因為它把「不可比」偽裝成了「可比」。

這篇文章的目標是把可比公司分析(Comparable Company Analysis,簡稱 comps)改造成一條可重跑、可審計、口徑統一的自動化流水線。讀完你會得到:同業挑選的準入與排除標準、六類必須統一的口徑問題與處理規則、倍數選擇的適用情境與陷阱、五段式流水線的 schema 設計與 Python 骨架,以及一套「AI 會在哪裡污染這張表」的機械化驗證清單。文中出現的任何數字都是教學用假設值,公司均為虛構,與真實市場數據無關。

一、先把問題定義清楚:難的是分母,不是算術

倍數的算術只有一行:

EV = 市值 + 淨負債(口徑見第二節)
P/E       = 每股股價 ÷ 每股盈餘
P/B       = 每股股價 ÷ 每股淨值
EV/EBITDA = 企業價值 ÷ EBITDA
EV/Sales  = 企業價值 ÷ 收入

真正的工作量與風險全在分母的口徑上:EBITDA 是哪個期間的?調整過一次性項目沒有?租賃怎麼處理?幣別是什麼?淨負債含不含非控股權益?這些問題不解決,倍數之間的比較就是噪音。所以自動化的第一性原則不是「算得快」,而是:每一個進入表格的數字,都帶著期間、幣別、調整記錄與來源指針。沒有這四樣 metadata 的數字,禁止進入比較表。

「一次跑完一整個板塊」的正確含義也因此改變了:不是讓 AI 一口氣生成 20 家公司的倍數,而是把單家公司的「抽取→標準化→計算→驗證」固化成可批量重跑的技能,再對板塊內每家公司獨立執行、獨立驗證,最後才彙總。批量是結果,標準化才是原因。

二、同業挑選:五條準入標準與一張負面清單

挑選同業的錯誤通常是「用行業分類代碼代替業務判斷」。兩家公司掛同一個行業代碼,可能一家是品牌商、一家是代工廠,利潤結構完全不同。準入標準建議固定為五條,每條都要能在數據中找到證據:

標準具體問題證據來源
業務重疊收入構成是否相近?主要產品線、客戶類型、商業模式(品牌/代工/平台)是否同類?年報分部資料、收入構成附註
規模帶收入、資產、市值是否落在可比區間?損益表、資產負債表、市場數據
地區與監管主要營運地、適用的會計準則與監管環境是否相近?年報公司資料、會計政策附註
成長與盈利階段成長期、成熟期還是衰退期?毛利率與利潤率量級是否相近?多年損益表趨勢
會計口徑財政年度截止日、報告幣別、會計準則(IFRS/US GAAP/當地準則)是否可統一?年報基本資料

同樣重要的是負面清單——以下情況直接排除或降級,不進入主比較表:

□ 僅因行業代碼相同、但收入構成明顯不同的公司
□ 財報期間或幣別無法可靠統一的公司(缺披露、缺匯率口徑)
□ 處於重大特殊狀態的公司:停牌、破產程序、剛完成重大重組、借殼
   (不是永遠排除,而是「本期不可比」,留檔並註明原因)
□ 樣本不足時的硬湊:可比公司湊不夠,寧可縮小結論範圍,
   也不要放寬標準把不可比的公司塞進來充數

挑選結果本身要產出一張「樣本表」:入選公司、入選理由(對應五條標準)、排除公司、排除理由。這張表是整份 comps 可被挑戰的第一層——挑戰樣本比挑戰倍數容易得多,先把樣本論證做扎實,後面的工作才有意義。

三、六類必須先統一的口徑問題

以下是批次比較中最常見的六種污染,每一種都對應一條明確的統一規則。規則要寫成流水線中的代碼,而不是寫在備忘錄裡。

口徑問題不統一時的症狀統一規則
財政年度不同A 家是 12 月結、B 家是 3 月結,直接拿「年度數據」相比統一滾算至最近可得的 LTM(最近四季加總);無法滾算的標記為「期間不對齊」並降級
幣別不同報告幣別、上市地幣別、收入幣別三者混用明確登記每家的報告幣別;換算統一用同一時點或同一期間平均匯率,匯率來源與日期入檔
一次性項目某家 EBITDA 含大額處置收益或減值,倍數突然「很便宜」建立調整規則:處置損益、減值、重大訴訟和解等非經常項目逐項調整,每筆調整記錄金額與附註出處
EV 構成差異淨負債定義各算各的固定 EV 公式並逐項登記口徑:是否含租賃負債、非控股權益、權益法投資、退休金缺口;受限現金不得計入可動用現金
股數口徑基本股數與攤薄股數混用,市值差出一截統一用攤薄股數(庫藏股法處理期權與認股權證),註明截止日期;可轉債的處理方式入檔
市場數據時點股價是今天的、財報是半年前的全表統一 as-of 日期;股價與財報期間的錯配寫進輸出備註

其中最容易出事的是租賃。現行租賃會計準則(如 IFRS 16)把多數經營租賃資本化上表,EBITDA 與淨負債同時被墊高;若樣本中有公司仍按舊準則或不同準則編製,EV/EBITDA 就系統性失真。處理方式不是「都調整」或「都不調整」二選一,而是登記每家的租賃口徑,調整到統一口徑,並把調整過程留檔。

統一後的 EV 公式建議固定成這樣(逐項可開關、口徑入檔):

EV = 市值(攤薄股數 × as-of 股價)
   + 總債務(含/不含租賃負債:登記選擇)
   + 非控股權益(按帳面或公允:登記選擇)
   − 現金及約當現金(排除受限現金)
   + 退休金淨缺口(是否計入:登記選擇)
   − 權益法投資(是否扣除:登記選擇,需與 EBITDA 口徑一致——
     若 EBITDA 不含聯營損益,EV 也應扣除對應投資)

分子分母口徑必須成對一致:EV/Sales 的分母若含合併收入,分子的 EV 也應含對應子公司;EBITDA 若剔除了某分部,EV 也要剔除該分部對應的價值。這條「成對原則」是 comps 自動化裡最值得寫成斷言的規則。

四、倍數選擇:適用情境與各自的陷阱

沒有一個倍數適用於所有公司。選擇邏輯是「分母必須為正、必須與分子口徑匹配、必須反映你真正想比較的經濟變量」:

倍數適用情境主要陷阱
P/E盈利穩定、資本結構相近的公司虧損時失效;盈餘可被會計政策影響;高槓桿公司 P/E 偏低是風險不是便宜
P/B金融業、重資產行業負權益時失效;商譽與無形資產佔比高時淨值失真;不同會計準則下帳面不可比
EV/EBITDA資本結構差異大、折舊攤提重的業務EBITDA 忽略維持業務所需的資本支出;租賃口徑污染(見上節);一次性項目未調整
EV/Sales尚未盈利的成長期公司完全忽略利潤率差異——高毛利與低毛利業務的 Sales 不可直接比
行業特定倍數有明確計量單位的行業(每用戶、每單位產能、每儲量等)計量單位定義本身可能不統一;需行業知識才能審計

統計口徑上還有三條紀律:

1. 樣本含極端值時用中位數,不用平均數;同時報告四分位距
2. 樣本數太少時(例如個位數),不報「平均倍數」,逐家列示
3. 任何「行業平均倍數」都必須是當場從樣本算出來的,
   禁止引用記憶中或模型輸出的「行業慣例值」——那是幻覺的高發區

第三條值得展開:LLM 被問「這個行業的 EV/EBITDA 通常是多少倍」時,幾乎總會給出一個流暢的數字,而且語氣篤定。這個數字沒有任何可追溯來源,是典型的編造。流水線層面的對策是:倍數只能由「抽取的原始數據 ÷ 計算」產生,模型不允許直接輸出倍數。這條規則的架構含義見 AI 在金融場景的幻覺風險。

五、自動化流水線:從抽取到輸出的五段設計

整條流水線分五段,每段有明確的輸入輸出契約,段與段之間用結構化 JSON 傳遞:

┌────────────┐   ┌────────────┐   ┌────────────┐   ┌──────────┐   ┌────────────┐
│ 1 樣本與    │→ │ 2 資料抽取  │→ │ 3 口徑標準化│→ │ 4 倍數計算│→ │ 5 異常處理  │
│   metadata │   │(帶來源)  │   │(帶調整記錄)│   │(帶斷言)│   │   與輸出    │
└────────────┘   └────────────┘   └────────────┘   └──────────┘   └────────────┘
     每家公司獨立走 2→4,互不共享狀態;任何一段失敗只標記該公司,不污染全表

第 1 段:樣本與 metadata。 輸出樣本表(入選/排除與理由),以及每家的靜態 metadata:代碼、上市地、報告幣別、財政年度截止日、會計準則。

第 2 段:資料抽取。 每個數值都是「值+來源指針+as-of」三元組。來源優先級:結構化數據 API > 年報 PDF 抽取 > 網頁資訊(需人工複核)。年報抽取的做法見 300 頁年報轉結構化 JSON 與 PaddleOCR 抽取港股年報;API 資訊源的選擇見 金融數據 API 調研。抽取層的輸出 schema 示例:

{
  "target_id": "FAKE-001",
  "target_name": "甲公司(虛構,教學用)",
  "as_of": "2026-06-30",
  "reporting_currency": "HKD",
  "fiscal_year_end": "12-31",
  "accounting_standard": "HKFRS",
  "facts": [
    {
      "field": "ebitda_ltm",
      "value": 850.0,
      "unit": "million",
      "period": "LTM_2026Q2",
      "source": {"type": "filing", "doc": "2025 annual report", "page": 68},
      "adjustments": []
    },
    {
      "field": "net_debt",
      "value": 1200.0,
      "unit": "million",
      "period": "2026-06-30",
      "source": {"type": "filing", "doc": "2026 interim report", "page": 12},
      "adjustments": [
        {"rule": "exclude_restricted_cash", "amount": -150.0,
         "reason": "受限現金不得計入可動用現金", "source_page": 45}
      ]
    }
  ]
}

第 3 段:口徑標準化。 規則引擎逐條套用第三節的統一規則,每筆調整寫入 adjustments 陣列。關鍵紀律:調整只能由規則觸發,不能由模型「覺得」觸發;規則本身有版本號,改了規則就要全表重跑。

第 4 段:倍數計算。 純函數計算,帶斷言:

def ev_from_facts(facts: dict) -> float:
    """EV = 市值 + 總債務 + 非控股權益 - 可動用現金(口徑由 policy 固定)"""
    ev = facts["market_cap"] + facts["total_debt"] + facts["minority_interest"] \
         - facts["cash_and_equivalents_unrestricted"]
    assert ev > 0, "EV 為負:口徑錯誤或數據異常,標記人工裁決,不得靜默輸出"
    return ev

def multiples(facts: dict, ev: float) -> dict:
    out = {}
    if facts.get("ebitda_ltm_adjusted", 0) > 0:
        out["ev_ebitda"] = ev / facts["ebitda_ltm_adjusted"]
    if facts.get("net_profit_ltm_adjusted", 0) > 0:
        out["pe"] = facts["market_cap"] / facts["net_profit_ltm_adjusted"]
    if facts.get("net_assets", 0) > 0:
        out["pb"] = facts["market_cap"] / facts["net_assets"]
    # 分母為負或缺失 → 該倍數輸出 null + 原因,而不是硬算或留空不解釋
    for key in ("ev_ebitda", "pe", "pb"):
        out.setdefault(key, None)
    return out

第 5 段:異常值處理與輸出。 異常值的紀律是「標記→查因→裁決→留檔」,永遠不要靜默刪除。落在四分位距之外的倍數,先跑查因清單:一次性項目沒調乾淨?期間沒對齊?業務真的不同類?公司處於特殊狀態?查因結果與裁決(保留/剔除/降級)全部入檔。統計時剔除的樣本,仍要出現在附錄裡。

批量執行:「跑完一整個板塊」的正確打開方式

板塊級掃描不是把二十幾家公司塞進同一次對話,而是把單公司流水線當作原子任務批量調度。三條工程紀律:

1. 每家公司獨立上下文執行,互不共享工作記憶
2. 增量重跑:以(target_id, as_of, 規則版本)為快取鍵,
   只有新財報發布或標準化規則升版時才重抽重算,其餘復用存檔
3. 全表彙總發生在所有單公司任務通過驗證之後;
   彙總層只做統計與排序,禁止在彙總時「順手」修改任何一家的數字

第 1 條不是紙上談兵:連續處理多家公司時,上一家的數據洩漏進下一家,是本站實測記錄過的事故模式,完整復盤與 Clean Context 設計見 子代理隔離架構。

批量輸出的檔頭固定三行 metadata:全表 as-of 日期、標準化規則版本號、樣本構成(A/B/C 級家數與剔除名單)。任何人拿到這張表,先讀這三行,再讀倍數。

六、「不可比」是輸出而不是失敗:可比性分級

流水線的最終輸出表格,每家虛擬公司(教學假設值,均為虛構)都帶可比性等級與口徑備註:

公司(虛構)EV/EBITDA (LTM)P/E (LTM)P/B可比性口徑備註
甲公司7.211.51.4A12 月結;LTM 已剔除處置收益
乙公司9.815.22.1A12 月結;租賃已統一資本化口徑
丙公司6.1n/a0.9B3 月結,LTM 滾算;本期虧損故無 P/E
丁公司14.528.03.8B含高毛利新業務分部,業務重疊度僅部分達標
戊公司———C(剔除)重大重組進行中,本期不可比,留檔待下期重評
中位數(A+B 級)8.515.21.8樣本 4 家,逐家列示如上

分級定義:

A 級:五條準入標準全達標,口徑全部統一,無需重大調整
B 級:準入達標但存在口徑調整或期間滾算,調整已留檔
C 級:本期不可比(特殊狀態/無法統一),剔除出統計、保留在附錄
規則:統計量只用 A+B;C 級必須附原因;A 級不足 3 家時,
     結論降級為「逐家比較」,不輸出板塊統計量

這個設計的好處是:「不可比」從流水線的失敗,變成了流水線的正式產出之一。整張表自帶論證結構——讀者可以逐家檢查調整記錄,而不是只能選擇相信中位數。

七、AI 會在這裡出錯:七種污染與機械驗證

把這條流水線交給 AI Agent 時,可預測的失敗模式與對應驗證:

AI 的典型錯誤症狀機械化驗證
憑記憶給數字倍數「看起來合理」但無來源指針schema 強制:無 source 欄位的 fact 一律拒收
LTM/NTM/FY 混用同表內期間口徑不一致period 欄位枚舉校驗;全表 as-of 一致性斷言
幣別與單位錯百萬/億、HKD/USD 混用unit 與 currency 必填;跨公司匯算必須引用同一匯率記錄
一次性項目未剔除某家倍數異常「便宜」調整規則清單逐條核對;異常倍數強制觸發查因流程
股數口徑錯市值系統性偏低或偏高登記基本/攤薄口徑;市值=攤薄股數×股價的重算斷言
把異常值直接刪掉中位數「變漂亮」但無記錄剔除必須有裁決記錄;統計樣本與附錄樣本對帳
編造「行業平均」輸出記憶中的倍數當共識架構規則:倍數只能由計算產生,模型輸出中出現直接倍數即判失敗

在此之上加兩道整體性驗證。第一道是重算抽檢:隨機抽 2–3 家,從來源文件重新抽取同一字段,與流水線結果比對;不一致即整批凍結排查。第二道是雙源交叉:關鍵字段(收入、淨負債)若同時有 API 與年報兩個來源,自動比對差異,超過容差就標記人工裁決——兩個獨立來源不一致,本身就是最有價值的警報。

這套「假設產出會被污染、所以每段都設卡」的思路,與 多 Agent 投研流水線 的失敗隔離設計同源;把驗證升級成系統級門控與評測集,則見 自建投研系統。

下一步