yfinance 数据审计 · 从取数到回测的最后一道关

数据质量审计:这份 yfinance 数据能不能拿去回测?

这是本站认为最该先补上的一环:中文教程大多停在「怎么取数」,很少回答「取到的数能不能用」。而真正让回测结论失真的,通常不是代码 bug,而是口径问题——混用复权价与未复权价、用报告期当成公告日、用今天的存续标的回测历史。本页给出一套可执行的审计清单与三态结论,让你在把数据交给策略之前,先有一个明确的判断依据。

审计对象:yfinance 返回的行情与事件数据依据:本机双版本实测 + 官方文档边界输出:三态结论 + 检查记录
13 项检查
三态结论
七条红线
留痕归档
依据官方数据边界与本站实测基线绘制的审计流程示意;非官方流程图。
yfinance · Checklist

回测前要检查哪些项:13 项可执行审计清单

左列是检查项、中列是为什么、右列是本站实测基线(AAPL 一个月日线)。

#检查项为什么重要实测基线(AAPL, 1mo)
1是否为空表 df.empty空表会让后续所有计算静默产出空结果非空,22 行
2索引是否重复 index.duplicated()重复日期会让位置运算整体错位重复数 0
3索引是否单调递增时间序列运算的前提单调递增
4索引时区是否符合预期不同取数方式的时区不同,拼接会错位America/New_York
5OHLC 关系是否成立价格错误最直接的暴露口成立(未逐行断言外的行)
6是否存在负价格负价通常意味着数据错误无
7涨跌幅是否有极端跳点极端跳点多来自复权错误或单位错位未发现
8事件列与价格是否一致分红/拆股应与价格跳变对应97 行 actions 记录可对照
9最新一行是否为预期交易日区分「没数据」与「取数失败」2026-10-08
10跨市场表 NaN 分布NaN 常来自交易日历不同,不是停牌单市场无此问题
11dtype 是否符合预期跨市场并集会把 Volume 变 floatVolume 为 int64
12股票池是否含已退市标的否则存在幸存者偏差单标的回测不涉及
13报表是否按公告日对齐报告期 ≠ 公告日,point-in-time 红线本页仅行情,见财务页
yfinance · Verdict

数据能不能用怎么下结论:三态判断法

本站建议每次审计都输出一个明确结论,而不是「看起来还行」。

结论判定条件可以做什么不可以做什么
可用于探索性分析1–7 项全部通过;事件列与价格一致;最新日期符合预期画图、算描述性统计、做初步收益与波动观察仍不能直接作为策略绩效依据——还没处理成本、滑点与样本外
需要人工复核出现极端跳点、事件与价格不一致、跨市场 NaN 分布异常、dtype 意外变化等任一项定位具体日期,用事件列或第三方数据交叉核对不要用 ffill()/均值填充把问题掩盖掉
不建议直接用于回测复权口径混用;point-in-time 未处理;股票池含幸存者偏差;无法解释的跳变先修口径或换数据源,再回到首步不要用「回测结果看起来合理」当作口径正确的证据
为什么强调「写下来」:三态结论的用途是留痕。当半年后有人问「这个结论是怎么来的」,你能给出当时的数据状态与判断依据,而不是重新猜一遍。
yfinance · Red lines

回测前有哪些红线:七条必须确认的边界

每条都对应一个真实会发生的口径错误,且都不会让代码报错。

① 复权价与未复权价不能混用

同一段逻辑里出现 Close(未复权)与 Adj Close(复权)的混算,会让收益里混进除权跳空。实测 auto_adjust=True 时 Adj Close 列根本不存在,所以「想混用」往往是因为没意识到口径已被默认值改变。

② 财务报表不能用报告期回测

报表列是报告期结束日,公告晚数周。用报告期对齐等于提前知道未来。官方没有 point-in-time 视图,只能自建归档。

③ 股票池要防幸存者偏差

用今天存续的标的回测历史,会系统性剔除退市与衰败公司。本站实测的单标的取数不涉及这点,但做多标的组合时必然遇到。

④ 日线不等于可成交价

日线只有开高低收,没有盘口深度与成交量分布。用它回测时,成交假设(按收盘、按次日开盘、加多少滑点)必须显式写出。

⑤ 分红、拆股、成本、滑点、涨跌停、停牌要单独建模

这些在 yfinance 的原始数据里不是统一处理的:分红拆股有列可查,但交易成本、滑点模型、涨跌停与停牌语义都要你自己定义。

⑥ 数据截止日不能和策略交易日混淆

实测最新一行是最近一个交易日(如 2026-10-08),而你的策略可能在别的日期运行。没有对齐「数据截止日」的回测,会出现用了未来数据或漏了最新数据的两种相反错误。

第七条单独说明:不要把 yfinance 当作实盘行情源。官方定位是研究与教育用途,库内不含交易能力;本站也没有实测其延迟表现,故不做任何实时性承诺。
yfinance · Script

审计脚本怎么用:可直接复用的检查代码

把下面这段放进数据管道,落盘前跑一次即可得到三态结论。

import yfinance as yf
import pandas as pd

def audit(df, expect_tz=None):
    issues = []
    price_cols = [c for c in ("Open", "High", "Low", "Close") if c in df.columns]
    if df.empty:
        return "不建议直接用于回测", ["empty dataframe"]
    if df.index.duplicated().any():
        issues.append("duplicated index: %d" % int(df.index.duplicated().sum()))
    if not df.index.is_monotonic_increasing:
        issues.append("index not sorted")
    if expect_tz and str(getattr(df.index, "tz", None)) != expect_tz:
        issues.append("tz mismatch: %s" % getattr(df.index, "tz", None))
    if price_cols:
        p = df[price_cols]
        bad_ohlc = (p["High"] < p[["Open", "Close"]].max(axis=1)) | \
                   (p["Low"]  > p[["Open", "Close"]].min(axis=1))
        if bad_ohlc.any():
            issues.append("OHLC relation broken: %d rows" % int(bad_ohlc.sum()))
        if (p < 0).any().any():
            issues.append("negative price")
        jump = (p["Close"].pct_change().abs() > 0.5).sum()
        if jump:
            issues.append("extreme moves: %d" % int(jump))
    if "Adj Close" in df.columns and ("Close" in df.columns):
        ratio = (df["Adj Close"] / df["Close"]).round(6)
        if ratio.diff().abs().max() > 0:
            pass  # 正常的复权跳变处,需人工确认对应事件
    verdict = ("可用于探索性分析" if not issues
               else ("需要人工复核" if len(issues) <= 2 else "不建议直接用于回测"))
    return verdict, issues

df = yf.Ticker("AAPL").history(period="1y", auto_adjust=False)
print(audit(df, expect_tz="America/New_York"))
yfinance · Boundary

口径差异到底会改变什么结论

下面两条是本站可以明确说的差异来源;具体数值取决于标的与区间,本站不发布绩效数字。

你换了什么会改变什么为什么本站是否实测
auto_adjust True ↔ False收益序列里是否包含分红再投资复权价把分红连续地体现在价格里,未复权价则把除权当成一次下跌列集合变化已实测;收益数值差异未做量化断言
指数(^GSPC)vs 成分股是否存在成分股变动的幸存者偏差指数历史上会调整成分,用今天的成分股回测过去等于用了未来名单本页只作方法说明,未做实测
跨市场并集表 vs 分市场处理缺失值处理方式与 dtype交易日历不同会产生 NaN,直接算收益会把 NaN 当 0 或报错并集行数与 dtype 变化已实测
repair=True ↔ False个别日期上的价格是否被改写repair 会修正 Yahoo 侧的价格错误,但存在假阳性风险本站样本未触发修复,未做效果断言
FAQ

yfinance 常见问题

下面的回答都指向可核验的官方文件或本站实测;与官方表述冲突时,以官方仓库与 docs 为准。

取到数据之后,最少要检查哪几项?

本站建议的顺序是:①是否为空表;②索引是否重复;③索引是否单调递增;④索引时区是否符合预期;⑤OHLC 是否满足 High ≥ max(Open,Close) ≥ min(Open,Close) ≥ Low;⑥是否存在负价格;⑦涨跌幅是否有极端跳点;⑧分红拆股事件与价格是否一致;⑨最近一行日期是否为预期的最新交易日;⑩跨市场表里 NaN 的分布。前四项能筛掉绝大多数「看起来有数据、其实不能用」的情况。

为什么要检查「索引是否重复」?

因为下游所有基于位置的运算都会因为重复日期而错位:pct_change()、shift()、按日重采样都会给出错误结果,而且往往不报错。本站实测的干净样本(AAPL 一个月日线)重复数为 0,但这不代表所有标的都如此,尤其是你自己拼接多个来源之后。

怎么判断「这份数据不能回测」?

本站给三个硬性判据:①存在无法解释的价格跳变且无法定位到分红/拆股事件;②复权口径混杂(同一段代码里同时用了 Close 与 Adj Close);③依赖了当时不可知的信息(报告期当成公告日、用了未来的成分股名单)。命中任意一条,就应停下修口径,而不是继续调策略参数。

最常被忽略的三个回测陷阱是什么?

①复权混用:用未复权价算收益,把除权当成暴跌;②point-in-time:财务报表的列是报告期,公告要晚数周,回测若按报告期对齐就是用了未来信息;③幸存者偏差:用今天还在的标的列表回测十年前,等于剔除了所有退市与衰败的公司。这三条都不会让代码报错。

yfinance 适合做实盘行情源吗?

本站不建议。官方 README 把定位写成 research and educational purposes,并写明与 Yahoo 无隶属关系、数据受 Yahoo Terms of Use 约束;库本身不含交易执行能力,也没有延迟与准确率承诺。本站没有实测其延迟与稳定性,因此不发布「实时」「低延迟」类结论。实盘请使用交易所或券商级行情源,并自行完成合规确认。

审计要不要写成脚本?

要。审计的价值在于「每次都跑」而不是「想起来才跑」。本页给出的检查函数可以直接放进数据管道,在落盘前执行并把结果写进研究记录。本站也建议把审计结果与数据一起归档——半年后回看时,你会需要知道当初用的数据是什么状态。

下一步:把故障排除掉

审计中最常见的阻塞是「取不到数」或「拿到空表」。下一站按现象、复现、原因、解决、不可解五段式逐条排查。