金融与市场数据
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 所在自然年可能与你直觉不同。
滚动最近十二个月视图,适合当前比率,但不是可以再加进历史序列的新期间。
保留发行人的申报分类与细节,但字段映射一致性需要由你负责。
以 symbol + fiscalYear + period 为起点,再核验 date、calendarYear、filingDate 和可用时的 acceptedDate。
某个端点可能缺期、重复或出现修订版本。应按键建立映射,并报告无法匹配的记录。
收入与现金流覆盖一段期间;现金、债务和权益是在某个日期的存量。应使用结束于同一财务期间的资产负债表。
安全获取 FMP 三张财务报表
FMP 支持通过请求头或查询参数鉴权。服务端请求头可避免密钥进入 URL、浏览器历史和常见访问日志。
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"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)
]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 | 支持修订检测和可复现研究。 |
字段缺失可能表示不适用、未披露、未映射或暂时不可用。在计算比率或排名前,应保留空值并附上质量原因。
把会计勾稽关系变成测试
校验应输出标记和容差,而不是静默修改源数据。舍入、汇率影响、分类映射或发行人列报方式都可能造成合理差异。
| 检查项 | 测试方式 | 失败含义 |
|---|---|---|
| 资产负债表恒等式 | assets ≈ liabilities + equity | 映射、单位、舍入或报表不完整。 |
| 期间一致性 | 相同代码、财年、期间和兼容日期。 | 混入季度/年度、缺报表或发生修订。 |
| 币种与数量级 | 相同报告币种且单位明确。 | 跨市场上市、换算或百万/个位数混用。 |
| 现金变化 | 期初到期末现金与净变化一致,并显式考虑汇率影响。 | 符号、汇率、收购分类或记录缺失。 |
| 净利润衔接 | 现金流起始利润与利润表口径兼容。 | 持续经营、少数股东损益或映射差异。 |
| 重复期间身份 | 每种数据形态与财务期间键最多一个有效版本。 | 发生修订或重复采集,应按规则选择版本。 |
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 倍 | 单位、币种或数量级不一致。 | 重新计算前检查报告币种与缩放。 |
| 季度合计与年度不一致 | 修订、财年、舍入或单季度/年初至今口径差异。 | 检查期间语义与申报版本,不要强行对齐。 |
| 历史已存数值发生变化 | 公司修订或供应商标准化更新。 | 生成新版本、记录差异并保留旧版本来源。 |
当工作流需要选择能力时,再引入能力层
在该模式下,FMP 仍然是数据供应商。Agent 需要发现报表能力、检查当前 Schema、统一调用、保存证据或切换供应商时,QVeris 才发挥作用。固定的 FMP 集成可以继续直接调用。
根据报表类型、市场覆盖、期间和来源要求搜索能力。
调用前读取实时输入输出契约,确认支持的标识符和期间。
使用校验后的参数执行,再应用同样的财务期间对齐与勾稽检查。
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。
档案链接有效不代表已获访问权限、字段可用、价格确定或当前调用成功;执行前须检查当前契约。
