错误写法
把 income_stmt 的列当成时间轴直接对齐到财年结束日,然后写「在 2025-09-30 用 2025 财年数据做决策」。实际公告通常在此之后数周,等于提前知道了未来。
yfinance 基本面与衍生品 · 财务报表与期权链
财务数据与行情数据的差异不只是「内容不同」:报表的列是报告期,不是公告日,这在回测里会造成「用了当时还不知道的信息」;实测中 balance_sheet 的列 dtype 是 object 而不是数值型,个别报表还会在超时后返回空表;期权链虽然有 14 个字段,但不少合约的成交量是空的。本页把「字段是什么、日期代表什么、缺了怎么办」逐条写清楚。
每一项都是本站对 AAPL 的真实调用结果,行数会随公司不同而变化。
| 报表 | 实测形状 | 列是什么 | 注意点 |
|---|---|---|---|
income_stmt(年) | 本次实测返回空表 (0, 0);quarterly_income_stmt 正常返回 33 行 × 5 列 | 列 = 报告期结束日(如 2026-06-30) | 单接口空表要按「取数失败」处理并重试,不要当成公司无数据 |
balance_sheet | 69 行 × 4 列(2025-09-30 → 2022-09-30) | 列 = 财年结束日 | 实测列 dtype 为 object,参与计算前先转数值 |
cashflow | 51 行 × 4 列 | 列 = 财年结束日 | 首行为 Free Cash Flow,可直接用于自由现金流口径核对 |
get_income_stmt(freq="yearly") | 官方签名:as_dict=False, pretty=False, freq='yearly' | freq 可为 yearly/quarterly/trailing | 同一份数据的不同口径入口,比属性访问更可控 |
earnings_dates | 实测索引时区 America/New_York,时间 15:00 或 16:30 | 列 = EPS Estimate / Reported EPS / Surprise(%) | 默认顺序不是时间递增,用之前先 sort_index() |
history_metadata(对照项) | 含 currency 等元信息 | —— | 核对真货币用它,不要用 info |
这类错误不会报错,只会让回测结果虚高,所以必须靠流程而不是靠代码检查来防。
把 income_stmt 的列当成时间轴直接对齐到财年结束日,然后写「在 2025-09-30 用 2025 财年数据做决策」。实际公告通常在此之后数周,等于提前知道了未来。
把「报告期」与「公告日」建两张表:报告期来自报表列,公告日来自 earnings_dates 或公司公告。回测时按公告日做 as-of join,并留一个缓冲(例如公告次日开盘才可用)。
在数据管道里记录每个报表快照的抓取时间。只要你有「某天抓到的报表长什么样」的历史,就能重放 point-in-time 视图;否则事后无法还原。这一点比任何公式都重要。
income_stmt 返回的是「你抓取那一刻的 Yale 数据库」视图。因此这项风险只能由使用方自己用归档流程解决,不能指望库来解决。官方没有给出字段级文档,下表按本站实测列名与常见语义整理,标注了本站未验证的部分。
| 字段 | 含义(按名称与官方数据惯例) | 本站实测观察 |
|---|---|---|
contractSymbol | 合约代码 | 每行唯一,可用于去重 |
lastTradeDate | 最后成交时间 | 未成交合约可能为空或很旧,用它判断新旧 |
strike | 行权价 | 与 inTheMoney 一起用于筛选 |
lastPrice / bid / ask | 最新价 / 买价 / 卖价 | 同一到期日实测 lastPrice 无缺失,但 bid/ask 可能为 0(无报价) |
change / percentChange | 相对前收的变化 | 与股票行情字段同名但口径为合约价 |
volume / openInterest | 当日成交量 / 持仓量 | 实测 0.2.58 一次 71 行 calls 中 11 行 volume 为 0(未成交) |
impliedVolatility | 隐含波动率(小数形式) | 做曲面/期限结构前先按流动性过滤,否则噪声极大 |
inTheMoney | 是否价内 | 布尔型,可直接用于分组统计 |
contractSize / currency | 合约乘数 / 计价货币 | 跨市场比较前必须核对货币与乘数 |
把重试、类型转换、排序、流动性过滤一次性写进去,避免每次调用都要重想一遍。
import yfinance as yf
import pandas as pd
t = yf.Ticker("AAPL")
# 1) 报表:空表按失败处理,转数值,按公告日另表管理
def get_stmt(name, tries=2):
for _ in range(tries):
df = getattr(t, name)
if df is not None and not df.empty:
return df.apply(pd.to_numeric, errors="coerce")
print("[warn] empty after retry:", name)
return pd.DataFrame()
bs = get_stmt("balance_sheet")
cf = get_stmt("cashflow")
qis = get_stmt("quarterly_income_stmt")
print("shapes:", bs.shape, cf.shape, qis.shape)
# 2) 财报公告时间:默认顺序不是时间递增,先排序
ed = t.get_earnings_dates(limit=12)
if ed is not None and not ed.empty:
ed = ed.sort_index()
print(ed.head(3)[["EPS Estimate", "Reported EPS"]])
# 3) 期权链:按流动性过滤后再看隐含波动率
if t.options:
chain = t.option_chain(t.options[0])
calls = chain.calls
liq = calls[(calls["volume"].fillna(0) > 0) & (calls["bid"] > 0)]
print("calls:", len(calls), "liquid:", len(liq))
print("moneyness:", calls["inTheMoney"].value_counts().to_dict())
把「能取到」和「能直接用」分开写。
| 你想做的事 | yfinance 能提供什么 | 缺口在哪 | 本站态度 |
|---|---|---|---|
| 对比两家公司财务 | 报表字段齐全(实测资产负债表单表 69 行) | 会计口径差异、并表范围不在库的处理范围内 | 可以取数,口径自己对齐 |
| 做估值模型 | 拿到三大报表的原始数字 | 没有单位统一、没有 TTM 自动计算(需用 trailing 口径自行核对) | 可作为数据源之一 |
| 回测里用财务因子 | 提供报表数值 | 没有 point-in-time 视图 | 必须自建归档流程 |
| 分析期权曲面 | 当前快照 14 个字段 | 无历史期权数据;流动性差的合约字段为空 | 可做当日研究,历史研究需自采 |
| 判断公司是否「没有某指标」 | info 字段以空值表示 | 空值既可能是「没有」也可能是「本次未取到」 | 需要二次来源核对,不能只看空值 |
下面的回答都指向可核验的官方文件或本站实测;与官方表述冲突时,以官方仓库与 docs 为准。
不是公告日,是报告期结束日。本站实测 AAPL 的年报列是 2025-09-30 / 2024-09-30 / 2023-09-30 / 2022-09-30,季报列是季度结束日(如 2026-06-30)。做回测时,如果直接把某财年的数据用在财年结束当天,就用了当时还没公告的信息——这是典型的 point-in-time 错误。财报的实际公告时间要另外用 earnings_dates 或 calendar 获取。
因为实测列的 dtype 是 object 而不是 float。直接做算术或比较可能得到意外结果,建议先 df.astype(float) 或 pd.to_numeric(errors="coerce")。另外要注意单位:不同项目可能以「元」或「百万」为单位,官方不做口径换算。
会。本站实测在 0.2.58 环境下 income_stmt 返回过 (0, 0) 的空表(耗时约 21 秒后返回),而同一轮里 balance_sheet(69 行 × 4 列)、cashflow(51 行 × 4 列)、quarterly_income_stmt(33 行 × 5 列)都正常。处理方式:把空表当成「本次取数失败」而不是「这家公司没有财报」,做重试并记录,必要时用 get_income_stmt(freq="quarterly") 换口径再试。
本站实测 calls 与 puts 各 14 列:contractSymbol, lastTradeDate, strike, lastPrice, bid, ask, change, percentChange, volume, openInterest, impliedVolatility, inTheMoney, contractSize, currency。流动性差的合约当天没有成交,volume 就会是空值(本站 0.2.58 一次实测中 71 行 calls 里有 11 行 volume 为 0)。用 impliedVolatility 做曲面之前,先按 volume/openInterest 过滤。
本页覆盖的 option_chain(expiry) 返回的是当前快照,不是历史序列。要做历史期权分析,必须自己按日抓取并落盘归档——这一点官方文档没有承诺任何历史期权服务。本站没有实测历史归档方案,因此不对「用什么方式归档最合适」下结论。
fast_info 和 info 有什么区别?fast_info 是一个轻量对象,本站实测暴露 20 个键(currency、dayHigh、dayLow、exchange、fiftyDayAverage、lastPrice、lastVolume、marketCap、open、previousClose、quoteType、regularMarketPreviousClose、shares、tenDayAverageVolume、threeMonthAverageVolume、timezone、twoHundredDayAverage、yearChange、yearHigh、yearLow);info 返回完整字典(字段数随标的与时间变化)。注意 info 在部分网络环境下会超时——本站实测就遇到过,见故障排查页。