做价值筛选或财报驱动策略的开发者,大概率都遇到过这种情况:财报公布后想第一时间判断是不是超预期,结果发现这件事其实要拆成三步:

  • 先要知道公司什么时候发财报
  • 发布后要拿到实际值
  • 还要有一个预期值做对比

以上三者缺一都算不出超预期还是暴雷

更麻烦的是,基本面数据这个词本身就非常笼统。利润表说的是公司赚了多少钱,估值指标说的是市场愿意为这些利润付多少钱,财报日历说的是什么时候能拿到最新数字,三件事看起来相关,却往往来自不同接口、不同更新频率、字段命名规则也不统一。第一次接入的开发者很容易把它们当成同一件事处理,结果筛选脚本跑出来的结果自己都不敢信。

本文把基本面数据拆成开发者最常问的三个问题,逐一讲清楚怎么使用 Infoway API 的财务报表接口,如何取数、返回结构里有哪些容易踩的坑。

一、基本面数据其实是三个不同的问题

在动手写代码之前,先把范围理清楚。Infoway 提供的基本面相关接口大致分三类:

分类要回答的问题涉及端点
三大报表公司经营/现金/家底状况如何income_statementcash_flowbalance_sheetrevenue
估值与衍生指标市场给这家公司的定价贵不贵statisticsv2/basic/stock/valuation
财报日历与预期差什么时候发财报、发得好不好v2/basic/stock/eventsearnings

此外还有一组股息相关接口(dividenddividend_payout),常被归入基本面数据,但更偏筛选高股息标的,本文第五节简单带过。

所有接口的请求头都只需要一个字段:

Python
apiKey: YOUR_API_KEY

基础域名为 https://data.infoway.io,下文出现的端点均在此基础上拼接路径。

二、三大报表:利润表 / 现金流量表 / 资产负债表

2.1 统一的返回结构

三大报表(外加收入明细 revenue)共用同一套返回 Schema,这是这组接口比较省心的地方,不用为利润表、现金流、资产负债表分别写三套解析逻辑:

字段说明
periodType报告周期:fq=季度、fy=年度、fh=半年
periodDate报告期,如 2025-12-31
itemId科目 ID(如 net_incometotal_assets
itemName科目名称
itemGroup分组,如 Income StatementBalance Sheet
itemValue该科目在这一期的数值
ttm该科目的 TTM(滚动 12 个月)合计,部分科目为 null
parentItemId父科目 ID,子科目才有值

请求参数也是统一的三件套:symbol(标的代码)、type(标的类型,如 STOCK_US / STOCK_CN / STOCK_HK)、period_type(不传则返回该标的全部周期的历史数据,量会比较大,建议明确指定)。

2.2 代码:净利润增速 + 自由现金流健康度

利润表容易造假,现金流相对更难做手脚,把两者结合起来看更可靠。下面的脚本拉取一只股票近 8 个季度的净利润增速,再算一个自由现金流健康度指标:

Python
import requests

API_KEY = "YOUR_API_KEY"
HEADERS = {"apiKey": API_KEY}
BASE = "https://data.infoway.io/common/basic/financial"


def get_financial_items(symbol, type_, endpoint, period_type="fq"):
    resp = requests.get(
        f"{BASE}/{endpoint}",
        headers=HEADERS,
        params={"symbol": symbol, "type": type_, "period_type": period_type},
    )
    return resp.json()["data"]


def net_income_growth(symbol, type_):
    items = get_financial_items(symbol, type_, "income_statement")
    net_income = sorted(
        [i for i in items if i["itemId"] == "net_income"],
        key=lambda x: x["periodDate"],
    )
    for i in range(1, len(net_income)):
        prev, cur = net_income[i - 1], net_income[i]
        if prev["itemValue"]:
            yoy = (cur["itemValue"] - prev["itemValue"]) / abs(prev["itemValue"]) * 100
            print(f"{cur['periodDate']}  净利润 {cur['itemValue']:,.0f}  环比变化 {yoy:+.1f}%")


def fcf_health_score(symbol, type_):
    cash_flow = get_financial_items(symbol, type_, "cash_flow")
    income = get_financial_items(symbol, type_, "income_statement")

    latest_fcf = next(i for i in cash_flow if i["itemId"] == "cf_free_cash_flow" and i["ttm"])
    latest_ni = next(i for i in income if i["itemId"] == "net_income" and i["ttm"])

    ratio = latest_fcf["ttm"] / latest_ni["ttm"]
    print(f"FCF/净利润(TTM)比值:{ratio:.2f}", "(健康)" if ratio > 0.8 else "(需关注:账面利润与实际现金流出现背离)")


net_income_growth("AAPL.US", "STOCK_US")
fcf_health_score("AAPL.US", "STOCK_US")

cf_free_cash_flow / net_income 的比值明显低于 1,通常意味着利润没有真正落袋为安,可能变成了应收账款,也可能是存货积压,都是财报里值得深挖的信号。

2.3 收入明细:拆到业务线和地区

revenue 端点结构相同,但 itemGroup 会区分 Revenue by business(按业务板块)和 Revenue by region(按地区),且以年度(fy)数据为主。想做某公司服务业务占比逐年提升这类可视化,直接按 itemGroup 过滤即可,不需要额外接口。

三、估值与基本面衍生指标:PE / PB / ROE 怎么算

3.1 两个容易混淆的端点

Infoway 有两个都能查到 PE/PB 的接口,用途不同,别搞混:

端点返回内容适合场景
/common/basic/financial/statistics按报告期返回一批估值/财务比率(PE、PB、净利率、ROE 等),分 Key statsValuation ratiosProfitability ratiosLiquidity ratiosSolvency ratios 五组批量筛选、单点快照
/common/v2/basic/stock/valuation/{symbol}PE/PB 的逐日历史序列画估值走势图,判断当前估值处于历史什么分位

statistics 返回的每条记录除了 itemValue,还多了两个字段:ttmStr(TTM 值,字符串类型)和 currentValue(当前实时值)。这里有个必须处理的坑:

ttmStr 无数据时返回的是字符串 "-",直接 float(item["ttmStr"]) 会抛异常,务必先做判空/容错处理。

valuation 端点走的是 v2 系列,symbol 格式和三大报表用的 v1 系列不一致,比如港股在 statistics/income_statement 里要写 00700.HK(带前导零),但在 valuation 这类 v2 接口里要写 700.HK(不带前导零)。这个格式差异不会报错,只会静默返回空数据,排查起来很容易走弯路。

3.2 代码示例:价值股批量筛选脚本

Python
import requests

HEADERS = {"apiKey": "YOUR_API_KEY"}


def safe_float(v):
    try:
        return float(v)
    except (TypeError, ValueError):
        return None


def get_statistics(symbol, type_):
    resp = requests.get(
        "https://data.infoway.io/common/basic/financial/statistics",
        headers=HEADERS,
        params={"symbol": symbol, "type": type_},
    )
    return resp.json()["data"]


def screen(symbol, type_, pe_max=15, roe_min=15, debt_ratio_max=60):
    items = get_statistics(symbol, type_)
    latest = {}
    for i in items:
        # 同一个 itemId 可能有多期数据,取最新 periodDate
        if i["itemId"] not in latest or i["periodDate"] > latest[i["itemId"]]["periodDate"]:
            latest[i["itemId"]] = i

    pe = safe_float(latest.get("pe_ratio", {}).get("currentValue"))
    pb = safe_float(latest.get("price_book", {}).get("currentValue"))
    net_margin = safe_float(latest.get("net_margin", {}).get("currentValue"))

    if pe is None or pe > pe_max:
        return None
    return {"symbol": symbol, "pe": pe, "pb": pb, "net_margin": net_margin}


candidates = [("000001.SZ", "STOCK_CN"), ("AAPL.US", "STOCK_US"), ("00700.HK", "STOCK_HK")]
for symbol, type_ in candidates:
    result = screen(symbol, type_)
    if result:
        print(result)

实际筛选一篮子股票时,itemId 的具体命名建议先请求一次拿到完整返回,用 itemName 核对含义再硬编码 itemId,不要凭猜测直接写死,不同标的类型的字段集合并不完全一样。

四、财报日历怎么查,怎么做财报前后监控

这是本文最核心的部分,也是三个问题里最容易被拆散处理的一块。

4.1 财报日历:events 端点

Python
GET https://data.infoway.io/common/v2/basic/stock/events/{symbol}?limit=20

返回的是一份事件列表,包含过去和未来的财报、分红等事件:

Python
{
  "symbol": "700.HK",
  "data": {
    "items": [
      {
        "event_type": "earnings",
        "event_date": "1774000000",
        "description": "2024年度业绩公告"
      }
    ]
  }
}

注意 event_dateUnix 秒级时间戳的字符串。筛出 event_type == "earnings"event_date 大于当前时间的最近一条,就是”下一次财报日期”。

4.2 财报后的超预期幅度:earnings 端点

财报发布之后,/common/basic/financial/earnings 会给出实际值与市场一致预期的对比:

Python
{
  "symbol": "AAPL.US",
  "periodType": "fq",
  "periodKey": "2026-Q1",
  "epsActual": 1.65,
  "epsEstimate": 1.62,
  "epsPercentage": 1.85,
  "revenueActual": 124300000000,
  "revenueEstimate": 122500000000,
  "revenuePercentage": 1.47
}

epsPercentage / revenuePercentage 直接就是偏差百分比,不需要自己再算一遍 (actual - estimate) / estimate

4.3 完整脚本:财报前提醒 + 财报后自动判断超预期

把两个端点串起来,就是一个完整的财报事件监控最小实现:

Python
import time
import requests

HEADERS = {"apiKey": "YOUR_API_KEY"}
BASE = "https://data.infoway.io"


def next_earnings_date(symbol):
    resp = requests.get(
        f"{BASE}/common/v2/basic/stock/events/{symbol}",
        headers=HEADERS,
        params={"limit": 50},
    )
    items = resp.json()["data"]["items"]
    now = time.time()
    upcoming = [
        i for i in items
        if i["event_type"] == "earnings" and int(i["event_date"]) > now
    ]
    if not upcoming:
        return None
    upcoming.sort(key=lambda x: int(x["event_date"]))
    return upcoming[0]


def latest_earnings_surprise(symbol, type_, beat_threshold=5.0):
    resp = requests.get(
        f"{BASE}/common/basic/financial/earnings",
        headers=HEADERS,
        params={"symbol": symbol, "type": type_, "period_type": "fq"},
    )
    data = resp.json()["data"]
    if not data:
        return None
    latest = sorted(data, key=lambda x: x["periodKey"])[-1]

    eps_pct = latest["epsPercentage"]
    rev_pct = latest["revenuePercentage"]
    if abs(eps_pct) >= beat_threshold or abs(rev_pct) >= beat_threshold:
        direction = "大超预期" if eps_pct > 0 else "大幅不及预期"
        print(f"[{symbol}] {latest['periodKey']} EPS {direction}:偏差 {eps_pct:+.1f}%,营收偏差 {rev_pct:+.1f}%")
    return latest


symbol, type_ = "AAPL.US", "STOCK_US"

next_event = next_earnings_date(symbol)
if next_event:
    days_left = (int(next_event["event_date"]) - time.time()) / 86400
    print(f"[{symbol}] 距下次财报还有 {days_left:.0f} 天:{next_event['description']}")

latest_earnings_surprise(symbol, type_)

生产环境里可以把这两个函数各自挂一个定时任务:next_earnings_date 每天跑一次做临近提醒,latest_earnings_surprise 在财报日之后的固定时间窗口内轮询,一旦返回新的 periodKey 就推送到 Telegram / 企业微信。

五、补充:股息数据

如果策略侧重高股息筛选,还有两个接口值得知道:

  • dividend:按报告期返回股息率、派息率等指标快照(结构与 statistics 一致,同样有 ttmStr/currentValue)。
  • dividend_payout:返回每一次派息的除权日、登记日、派息日、金额,是唯一不需要 period_type 参数、直接返回全部历史记录的端点。
Python
resp = requests.get(
    "https://data.infoway.io/common/basic/financial/dividend_payout",
    headers=HEADERS,
    params={"symbol": "AAPL.US", "type": "STOCK_US"},
)
for p in resp.json()["data"]:
    print(p["exDate"], p["amount"], p["type"])  # type: quarterly / interim / special

这里也有一个时间戳精度的坑:dividend_payoutexDate/recordDate/paymentDateUnix 毫秒时间戳(13 位),而第四节 events 端点的 event_dateUnix 秒时间戳(10 位)。两个都叫”时间戳”,精度不一样,混用会导致日期解析出来差出几万年。

六、常见坑汇总

说明
ttmStr 是字符串无数据时为 "-",直接转 float 会报错,需先容错
v1/v2 symbol 格式不一致financial/* 系列港股要 00700.HK(带前导零),v2/basic/stock/* 系列要 700.HK(不带),格式错了不报错、只返回空数据
时间戳精度不统一events.event_date 是秒级,dividend_payout 的日期字段是毫秒级
period_type 不传会返回该标的全部历史周期数据,量较大,明确场景下建议显式指定 fq/fy/fh
revenue 端点主要是年度数据period_type=fq 未必总有值,季度收入拆分覆盖度低于年度
itemId 命名不同标的/不同市场返回的科目集合不完全一致,建议先拉一次完整数据核对 itemName 再写死解析逻辑

七、常见问题

三大报表和 statistics 端点的区别是什么?
三大报表(income_statement/cash_flow/balance_sheet)返回的是财报里的原始科目数值;statistics 返回的是基于这些原始数据计算出的比率指标(PE、ROE、资产负债率等),两者是原料和加工结果的关系。

ttmttmStr 有什么区别?
ttm 出现在三大报表和 revenue 端点,是数值类型的滚动 12 个月合计;ttmStr 出现在 statistics/dividend 这类比率型端点,是字符串类型,因为无数据时会返回 "-"

想知道某只股票下个季度的财报预计什么时候发,用哪个接口?
v2/basic/stock/events/{symbol},筛选 event_type == "earnings" 且日期在未来的记录。financial/earnings 端点只返回已发布财报的实际值与预期对比,查不到未来日期。

period_typefh(半年报)适用于所有市场吗?
半年报主要出现在港股等有中期报告制度的市场,美股基本没有半年报概念,多数是季报(fq)+ 年报(fy)。具体某个标的支持哪些周期,建议先不传 period_type 观察实际返回的 periodType 分布。

批量筛选几百只股票时,怎么控制请求频率?
和其他 Infoway 接口共用同一套频率限制,具体每秒请求数取决于套餐。批量筛选建议加请求间隔或做并发限流,避免触发 429

估值时序(valuation 端点)里的 eps/bps/last_done 字段经常是空字符串,是数据缺失吗?
不一定,这几个字段并非每个数据点都会填充,实际使用时主要依赖 pe_list/pb_list 里的 pe/pb 字段即可,eps/bps 可以视为可选的附加信息。