QVeris

金融与市场数据

FMP 财务报表 API: 代码示例、三表对齐与数据校验

使用 FMP 财务报表 API 获取利润表、资产负债表和现金流量表数据,并实现期间对齐、校验与错误控制。

发布日期:2026年4月13日 · QVeris团队制作

结合财务报告、计算记录和市场证据开展分析
结合财务报告、计算记录和市场证据开展分析
核心结论

最短的正确三表工作流

三张报表使用相同的代码、期间和条数请求,再按财务期间身份连接,不能按数组下标拼接。

FMP Key 保存在服务端。统一 symbol + fiscalYear + period,保留报表日期与申报日期,不擅自把空值变成 0,校验币种和单位,再计算比率或交给 Agent。

适合谁

开发基本面研究、股票筛选器、估值模型、组合分析或财务研究 Agent 的团队。

准备条件

FMP API Key、可信服务端、明确的年度/季度/TTM 策略,以及空值、币种和修订处理规则。

端点选择

选择标准化、TTM 或原始披露数据

FMP 提供多类财务报表端点,是因为跨公司比较、最新经营表现和申报级审计是不同任务。先确定应用需要回答的问题。

需求Stable endpoint适用场景主要注意点
盈利能力/income-statement收入、利润率、营业利润、净利润和 EPS。财务期间与基本/稀释股数口径。
财务状况/balance-sheet-statement现金、营运资本、债务、资产、负债与权益。它是时点报表,而非期间流量。
现金创造/cash-flow-statement经营、投资和融资现金流,以及资本开支和自由现金流。资本开支符号与汇率影响。
滚动最近一年/income-statement-ttm
/balance-sheet-statement-ttm
/cash-flow-statement-ttm
无需等待年报即可使用最近一年的趋势或估值输入。不要把 TTM 与年度或季度数据相加。
可比指标/key-metrics · /ratios
/key-metrics-ttm · /ratios-ttm
利润率、回报率、流动性、杠杆和估值筛选。必须知道公式与分母期间。
申报级审计/financial-statement-full-as-reported需要原始申报分类或自行建立映射层。字段会随公司与分类标准变化。
标准化数据用于可比性,原始披露数据用于追溯性。

不能笼统说其中一种“更准确”。两者解决的问题不同,应在数据集元数据中保存所选口径。

期间对齐

年度、季度与 TTM 是不同的数据契约

年度

适合经审计的多年趋势和完整财年模型。公司的财年不一定等于自然年。

季度

适合观察近期经营变化。财务 Q1 所在自然年可能与你直觉不同。

TTM

滚动最近十二个月视图,适合当前比率,但不是可以再加进历史序列的新期间。

原始披露

保留发行人的申报分类与细节,但字段映射一致性需要由你负责。

以财务期间身份作为主键

以 symbol + fiscalYear + period 为起点,再核验 date、calendarYear、filingDate 和可用时的 acceptedDate。

不要按数组下标拼接

某个端点可能缺期、重复或出现修订版本。应按键建立映射,并报告无法匹配的记录。

区分期间流量与时点存量

收入与现金流覆盖一段期间;现金、债务和权益是在某个日期的存量。应使用结束于同一财务期间的资产负债表。

分步实现

安全获取 FMP 三张财务报表

FMP 支持通过请求头或查询参数鉴权。服务端请求头可避免密钥进入 URL、浏览器历史和常见访问日志。

cURL · annual income statement
curl --fail --silent --show-error \
  --get "https://financialmodelingprep.com/stable/income-statement" \
  --data-urlencode "symbol=AAPL" \
  --data-urlencode "period=annual" \
  --data-urlencode "limit=5" \
  --header "apikey: $FMP_API_KEY"
Python · parallel fetch + fiscal-key join
import os
from concurrent.futures import ThreadPoolExecutor
import requests

BASE = "https://financialmodelingprep.com/stable"
ENDPOINTS = {
    "income": "income-statement",
    "balance": "balance-sheet-statement",
    "cashflow": "cash-flow-statement",
}

def fetch(name: str, symbol: str, period="annual", limit=5):
    response = requests.get(
        f"{BASE}/{ENDPOINTS[name]}",
        params={"symbol": symbol.upper(), "period": period, "limit": limit},
        headers={"apikey": os.environ["FMP_API_KEY"]},
        timeout=10,
    )
    response.raise_for_status()
    rows = response.json()
    if not isinstance(rows, list) or not rows:
        raise ValueError(f"No {name} rows for {symbol}")
    return rows

def fiscal_key(row: dict):
    year = row.get("fiscalYear") or row.get("calendarYear")
    return row.get("symbol"), str(year), row.get("period")

def get_three_statements(symbol: str):
    with ThreadPoolExecutor(max_workers=3) as pool:
        futures = {name: pool.submit(fetch, name, symbol) for name in ENDPOINTS}
        data = {name: future.result() for name, future in futures.items()}

    indexed = {
        name: {fiscal_key(row): row for row in rows}
        for name, rows in data.items()
    }
    common = set.intersection(*(set(rows) for rows in indexed.values()))
    if not common:
        raise ValueError("The three statements share no fiscal periods")

    return [
        {"key": key, **{name: indexed[name][key] for name in ENDPOINTS}}
        for key in sorted(common, reverse=True)
    ]
TypeScript · bounded Promise.all
const paths = {
  income: "income-statement",
  balance: "balance-sheet-statement",
  cashflow: "cash-flow-statement",
} as const;

async function getStatements(symbol: string, period = "annual") {
  const key = process.env.FMP_API_KEY;
  if (!key) throw new Error("FMP_API_KEY is missing");

  const entries = await Promise.all(
    Object.entries(paths).map(async ([name, path]) => {
      const url = new URL(\`https://financialmodelingprep.com/stable/\${path}\`);
      url.searchParams.set("symbol", symbol.toUpperCase());
      url.searchParams.set("period", period);
      url.searchParams.set("limit", "5");

      const response = await fetch(url, {
        headers: { apikey: key },
        signal: AbortSignal.timeout(10_000),
      });
      if (!response.ok) throw new Error(\`\${name}: HTTP \${response.status}\`);
      const rows: unknown = await response.json();
      if (!Array.isArray(rows) || rows.length === 0) {
        throw new Error(\`\${name}: empty statement response\`);
      }
      return [name, rows] as const;
    }),
  );
  return Object.fromEntries(entries);
}
三表数据契约

先统一身份字段,再处理财务数值

供应商响应结构可能变化。应围绕产品实际使用的字段建立稳定内部封装,并在政策允许时保留原始响应或内容哈希。

字段组建议字段规则
标的身份symbol, cik在报表记录之外处理代码变更与交易所变体。
财务期间fiscalYear, period, date作为连接身份,不能依赖数组顺序。
申报来源filingDate, acceptedDate, finalLink用于解释数据何时、从哪里进入数据集。
计量口径reportedCurrency, unit, scale比较前统一币种与数量级。
数据形态standardized or as_reported没有明确映射时不要混用。
修订信息retrievedAt, payload hash, version支持修订检测和可复现研究。
空值不等于 0。

字段缺失可能表示不适用、未披露、未映射或暂时不可用。在计算比率或排名前,应保留空值并附上质量原因。

校验层

把会计勾稽关系变成测试

校验应输出标记和容差,而不是静默修改源数据。舍入、汇率影响、分类映射或发行人列报方式都可能造成合理差异。

检查项测试方式失败含义
资产负债表恒等式assets ≈ liabilities + equity映射、单位、舍入或报表不完整。
期间一致性相同代码、财年、期间和兼容日期。混入季度/年度、缺报表或发生修订。
币种与数量级相同报告币种且单位明确。跨市场上市、换算或百万/个位数混用。
现金变化期初到期末现金与净变化一致,并显式考虑汇率影响。符号、汇率、收购分类或记录缺失。
净利润衔接现金流起始利润与利润表口径兼容。持续经营、少数股东损益或映射差异。
重复期间身份每种数据形态与财务期间键最多一个有效版本。发生修订或重复采集,应按规则选择版本。
Python · tolerance-based balance check
def close_enough(left, right, rel_tol=0.001, abs_tol=1.0):
    if left is None or right is None:
        return None  # unknown, not pass
    return abs(left - right) <= max(abs_tol, rel_tol * max(abs(left), abs(right)))

def validate_balance_sheet(row):
    assets = row.get("totalAssets")
    liabilities = row.get("totalLiabilities")
    equity = row.get("totalStockholdersEquity")
    return {
        "balanceEquation": close_enough(assets, liabilities + equity)
        if None not in (assets, liabilities, equity) else None,
        "currencyPresent": bool(row.get("reportedCurrency")),
        "periodPresent": bool(row.get("period")),
    }
  • 加载完整历史前,先测试一个已知年度和一个已知季度。
  • 计算自由现金流前,确认供应商的资本开支符号。
  • 验收测试时,将一个重要数值与链接的申报文件比对。
  • 明确区分空值、零和不适用。
  • 保存获取时间与响应指纹,用于检测修订。
  • 身份、币种或期间检查失败时,阻止下游比率计算。
衍生指标

源报表通过校验后再计算指标

利润率

分子与分母必须来自同一期间、同一数据形态的利润表。

杠杆

明确债务是总债务、净债务还是有息债务,并说明 EBITDA 是年度还是 TTM。

自由现金流

优先使用定义明确的供应商字段,或在用经营现金流减资本开支前统一资本开支符号。

回报率

期间流量作为分子时,分母通常应使用期初与期末余额平均值,而不是只用期末值。

安全、成本与血缘

财务报表管线的生产控制

按申报生命周期缓存

财务报表的变化远少于价格。可以较长缓存,并在预计申报和供应商更新窗口刷新。

只重试瞬时故障

对 429 和 5xx 使用指数退避与随机抖动;无效代码、错误密钥和套餐不支持不要重试。

为每次刷新保存版本

保存供应商、端点、参数、获取时间和响应指纹,确保研究结果可复现。

衡量数据质量

监控空结果、缺期率、勾稽失败率、修订率,以及从申报到数据可用的时间。

故障排查

常见财务报表失败模式

现象可能原因处理方式
HTTP 200,但数组为空代码、期间或覆盖不受支持。按未找到处理,并核验代码与报表覆盖。
403密钥缺失、无效或套餐权限不足。停止重试,检查密钥注入和权限。
429超过速率限制。退避、降低并发,并缓存重复历史请求。
三张报表数组长度不同财务期间缺失、重复或被修订。按财务期间键连接,并暴露未匹配记录。
比率突然放大 1,000 倍单位、币种或数量级不一致。重新计算前检查报告币种与缩放。
季度合计与年度不一致修订、财年、舍入或单季度/年初至今口径差异。检查期间语义与申报版本,不要强行对齐。
历史已存数值发生变化公司修订或供应商标准化更新。生成新版本、记录差异并保留旧版本来源。
QVeris 接入模式

当工作流需要选择能力时,再引入能力层

在该模式下,FMP 仍然是数据供应商。Agent 需要发现报表能力、检查当前 Schema、统一调用、保存证据或切换供应商时,QVeris 才发挥作用。固定的 FMP 集成可以继续直接调用。

Discover

根据报表类型、市场覆盖、期间和来源要求搜索能力。

Inspect

调用前读取实时输入输出契约,确认支持的标识符和期间。

Call

使用校验后的参数执行,再应用同样的财务期间对齐与勾稽检查。

查看 QVeris 文档 →

FAQ

FMP 财务报表 API 常见问题

哪些端点返回三张财务报表?

在 Stable 基础 URL 下使用 income-statement、balance-sheet-statement 和 cash-flow-statement,三次请求保持代码、期间和条数一致。

应该使用年度、季度还是 TTM 数据?

多年经审计比较使用年度;近期变化使用季度;滚动最近一年使用 TTM。没有显式转换时不要混用。

标准化与原始披露有什么区别?

标准化报表提高通用字段的可比性;原始披露端点保留申报分类,适合审计和自定义映射。

三张报表应该怎样连接?

按代码、财年和财务期间连接,再核验报表日期与申报元数据。申报日期和数组下标都不是安全连接键。

如何验证财务报表数据?

检查期间与币种、资产负债表恒等式、现金变化、重复期间、缺失值、申报时间和修订行为。

缺失值可以替换成 0 吗?

不能自动替换。空值可能表示未披露、不适用、不可用或未映射。计算比率前应保留空值并附上质量原因。

可以在前端代码中暴露 FMP Key 吗?

不可以。应保存到服务端环境变量或密钥管理器,由可信后端请求 FMP。

什么时候应该将 QVeris 与 FMP 一起使用?

需要能力发现、实时 Schema 检查、统一调用或供应商降级时使用 QVeris;固定的单供应商集成可以直接调用 FMP。

官方资料

以最新官方文档核验端点与字段

让每一个财务结论都可追溯

先正确对齐 FMP 财务报表;当工作流需要能力发现、Schema 检查或供应商感知路由时,再接入 QVeris。

档案链接有效不代表已获访问权限、字段可用、价格确定或当前调用成功;执行前须检查当前契约。

FMP 财务报表 API: 代码示例、三表对齐与数据校验 | QVeris Guides