这篇是教程,不是 reference。读完你能拿到一个真正能跑、能扩展的港股估值看板雏形。

0. 你需要什么

  • Python 3.10+(推荐 3.12)
  • 一个 HKFilings 账号(免费注册)—— Free 套餐每月 10 份够你跑这个 demo
  • 15 行 Pandas / 30 行 Streamlit 的基础

1. 安装 + 配置

pip install hkfilings pandas streamlit

# 或用 uv(更快)
uv pip install hkfilings pandas streamlit

配 API key:登录后到 /settings → API 创建一把 key(注册时已签发的默认 key 也能用)。

export HKFILINGS_API_KEY="sk_..."
# 或者写到 .env,python-dotenv 加载

2. SDK 基础用法

SDK 把所有 REST 端点包成同步 + 异步两套接口。看 5 个最常用的:

from hkfilings import HKFilingsClient

client = HKFilingsClient()  # 自动读 env var HKFILINGS_API_KEY

# (1) 提交分析任务(按 ticker + 年度)
task = client.submit_task(
    ticker="00700",
    period="FY2024",  # 或 "FY2024H1" / "FY2024Q3"
    language="zh",
)
print(task.task_id, task.duplicate)  # 重复提交会返回已有 task_id + duplicate=True

# (2) 等任务完成(轮询 + SSE 双模式)
result = client.wait_for_task(task.task_id, timeout_sec=600)
# 或者用流式:
# for event in client.stream_task(task.task_id):
#     print(event.kind, event.payload)

# (3) 拿结构化事实
facts = client.get_facts(ticker="00700", period="FY2024")
print(facts.revenue, facts.net_income_attributable, facts.operating_cash_flow)
# 每个 fact 是一个对象,含 .value / .source_page / .bbox / .validation_status

# (4) 跨期矩阵
matrix = client.get_company_matrix(ticker="00700", metric="revenue", periods=4)
# pandas.DataFrame: rows=periods, cols=[revenue, yoy_pct, source_page]

# (5) 行业模板信号
signals = client.get_layer2_signals(ticker="00700")
print(signals.industry_template, signals.supply_chain_nodes, signals.catalysts)

3. 30 行:50 家港股的横向估值脚本

我们用 HKFilings 拿三个数字(净利润、净资产、EBITDA),再用 Yahoo Finance 拿股价计算 P/E / P/B / EV/EBITDA。

import asyncio
import pandas as pd
import yfinance as yf
from hkfilings import AsyncHKFilingsClient

TICKERS = ["00700", "09988", "03690", "01299", "01211", "01810",
           "00939", "01398", "02318", "00388", "00005", "09618",
           "09868", "09866", "02015", "01024", "02382", "09626",
           "02020", "02269"]  # 加更多 ticker 即可

async def fetch_one(client, ticker):
    facts = await client.get_facts(ticker=ticker, period="latest")
    return {
        "ticker": ticker,
        "net_income": facts.net_income_attributable.value,
        "equity": facts.shareholders_equity.value,
        "ebitda": facts.ebitda.value if facts.ebitda else None,
        "cash": facts.cash_and_investments.value,
        "currency": facts.reporting_currency,
    }

async def main():
    async with AsyncHKFilingsClient() as client:
        rows = await asyncio.gather(*[fetch_one(client, t) for t in TICKERS])

    df = pd.DataFrame(rows)

    # 拉股价
    yf_tickers = [f"{t}.HK" for t in df["ticker"]]
    quotes = yf.download(yf_tickers, period="1d")["Adj Close"].iloc[-1]
    df["price"] = df["ticker"].map(lambda t: quotes.get(f"{t}.HK"))

    # 估算(需要根据币种统一,这里简化)
    df["pe"] = df["price"] / (df["net_income"] / 1e9)  # 简化的 P/E
    df["pb"] = df["price"] / (df["equity"] / 1e9)
    df["ev_ebitda"] = (df["price"] - df["cash"] / 1e9) / (df["ebitda"] / 1e9)

    df_sorted = df.sort_values("pe").round(2)
    print(df_sorted[["ticker", "pe", "pb", "ev_ebitda"]].to_string(index=False))

asyncio.run(main())

跑一下:

$ python valuation.py
ticker     pe     pb  ev_ebitda
00939    4.6    0.5        n.a.
01398    4.8    0.5        n.a.
00005    7.2    0.9        n.a.
00388   24.1    8.1       16.8
00700   18.5    3.2       11.4
09988   13.7    1.5        9.8
...

20 行核心代码就跑出来了。当然真实研究需要:

  • 币种统一(RMB / HKD / USD → 选一个)
  • 用 forward EPS / 一致预期净利润 而不是 TTM
  • 剔除一次性项目(用调整后净利润)
  • 同业相对估值(行业中位数对比)

4. 给它套个 Streamlit dashboard

另存为 app.py

import streamlit as st
import pandas as pd
# ... (复用上面的 fetch_one + main 逻辑,async 部分用 asyncio.run 包) ...

st.set_page_config(page_title="HK Valuation Dashboard", layout="wide")
st.title("港股估值看板 · HKFilings")

industries = {
    "互联网": ["00700", "09988", "03690", "01810", "09618", "01024", "09626"],
    "银行": ["00939", "01398", "00005"],
    "保险": ["01299", "02318"],
    "新能源汽车": ["01211", "09868", "09866", "02015"],
}

selected = st.multiselect("选择行业", list(industries.keys()), default=["互联网"])
tickers = [t for ind in selected for t in industries[ind]]

df = run_pipeline(tickers)  # 复用上面的逻辑

st.dataframe(df[["ticker", "pe", "pb", "ev_ebitda"]].style.background_gradient(subset=["pe"], cmap="RdYlGn_r"))

# 图表
import altair as alt
st.altair_chart(
    alt.Chart(df).mark_circle(size=200).encode(
        x="pe:Q", y="pb:Q", color="ticker:N",
        tooltip=["ticker", "pe", "pb", "ev_ebitda"]
    ).properties(height=400),
    use_container_width=True,
)

跑:

streamlit run app.py

30 分钟你就有了一个浏览器里可交互、能按行业过滤、能看散点图的港股估值看板。

5. 下一步扩展

  • 历史回测:用 client.get_company_matrix 拿历史 4 期数据,画 P/E 走势
  • Forward 估值:接 Wind / 同花顺一致预期净利润数据补 forward EPS
  • 分行业模板:银行业用 P/B / 不良率 / ROE 三维;保险业用 P/EV / NBV 增长;REIT 用 P/NAV / DPU 收益率 + Gearing
  • 催化剂叠加:用 client.get_layer2_signals 拿未来 12 个月催化剂时间线,与估值 + 价格走势对齐

常见坑

  • 币种:港股 50 家用三种币种披露(RMB / HKD / USD)。一定要先归一再算估值。HKFilings 的 facts.reporting_currency 给你币种标签。
  • 同股不同权:阿里 / 美团 / 小米 / 小鹏 / 蔚来 / 理想 / 京东 / 快手 / B 站都是。EPS 计算需要按 ADS 或股票面值统一。
  • 剥离 / 重组:去年的净利润可能包含已剥离业务。看 MD&A「持续经营业务净利润」更可比。HKFilings 校验规则会标 flag。

结语

HKFilings 不替代你的判断 —— 它替代你抄 PDF 的那段。把研究员的时间从「找数字」迁移到「想数字」上。免费试一份看完整 API 文档