多源数据版图与选型
实测那天,东财系接口全挂了——这一节因此更值钱
这一节本来打算给你一张干净的数据源对比表。
结果实测那天出了状况:akshare 里所有走东方财富后端的接口,全部连不上——指数、个股、ETF,四个接口、各重试三次,清一色 RemoteDisconnected。而同一个库里走新浪后端的接口,0.11 秒返回 5960 行。
我决定不重跑到"好看"为止,而是把这次故障原样留在课里。因为它恰好演示了这一节真正要教的东西:
不要问"哪个数据源最好",要问"当我依赖的那个源今天挂了,我的项目会怎样"。
出门只带一张银行卡的人,和带两张不同银行卡的人,平时没区别。区别只在 ATM 吞卡的那一天。
数据源一样。单源项目不是"更简单",是"把故障延后到最不方便的那一天"。
一、实测结果(2026-07-31,境内可访问的服务器)
| 源 / 后端 | 结果 | 行数 | 覆盖区间 | 中位耗时 |
|---|---|---|---|---|
| akshare · 东财 · 指数 | ❌ ConnectionError | — | — | — |
| akshare · 东财 · 指数(另一接口) | ❌ ConnectionError | — | — | — |
| akshare · 东财 · 个股(后复权) | ❌ ConnectionError | — | — | — |
| akshare · 东财 · ETF | ❌ ConnectionError | — | — | — |
| akshare · 新浪 · 指数 | ✅ | 5960 | 2002-01-04 ~ 2026-07-30 | 0.11s |
| baostock · 指数 | ✅ | 5177 | 2005-04-08 ~ 2026-07-29 | 1.75s |
| yfinance · 000300.SS | ✅ | 1299 | 2021-03-11 ~ 2026-07-29 | 0.01s |
三条一眼可见的结论:
- "akshare 挂了"这句话本身就是错的。akshare 是个聚合库,后端有东财、新浪、同花顺等等。挂的是某一个后端,不是这个库。理解这一点,你才知道降级该往哪降。
- baostock 慢一个量级(1.75s vs 0.11s),但它有一个不可替代的价值:它是独立的第二来源,可以用来验证第一来源对不对。慢,是校验的成本。
- yfinance 那一行是本节最危险的一行——它没有报错。
最危险的不是报错,是"成功地少给你"。
我向 yfinance 要的是 2005-04-08 起的数据。它返回了 200 OK、1299 行、干干净净一张表——起点是 2021-03-11。
十六年数据凭空消失,没有任何警告。如果你不检查区间,直接拿去回测:
- 你的"21 年长周期验证"其实只有 5 年
- 而这 5 年恰好避开了 2008 和 2015 两次大熊市
- 于是你的策略最大回撤会显著偏小,夏普显著偏高
"能取到"和"取对了"是两件事。 取回来第一件事必须是检查区间和行数,不合格要当作失败处理。
二、选型:别选"最好的",选"能互相救的"
| 源 | 优势 | 代价 / 坑 | 本课定位 |
|---|---|---|---|
| akshare | 免费、无需注册、覆盖最广(股/基/指/期/宏观) | 聚合多个后端,单后端可能整体不可用;接口签名变动较频繁 | 主力取数 |
| baostock | 免费、无需 token、区间完整、复权口径明确 | 慢;需 login/logout;返回游标不是 DataFrame | 交叉校验源 |
| tushare | 字段规范、文档完整、社区大 | 需注册取 token,多数接口按积分限流(本课不内置任何账号,故未实测) | 可选替补 |
| yfinance | 全球标的、极快 | 境内标的覆盖不全(本次实测只给到 2021 起);数据口径与境内源不同 | 仅用于境外标的 |
⚠️ 表中 tushare 一行依据官方文档,本节未实测——因为它需要账号。凡是没实测的,本课一律标注出来,不混在实测结论里。
🔧 动手做:查一查你的取数器,单点故障和静默截断(5 分钟)
本节实测那天,akshare 东财系四个接口全部 ConnectionError(各重试 3 次)。而 yfinance 请求 2005 年起的沪深300,只返回了 2021-03-11 起的 1299 行——它没报错,悄悄把前 16 年截掉了。
回去查你(或任何教程)的取数代码两件事:① 只有一个数据源吗?那个源今天挂了,你的整条管线就死了。② 你验过每个源返回的行数和起点吗?还是拿到数据就直接用?
想明白:数据源会挂,更阴的是会静默返回错的(少给一段、给了别的口径)。所以选型不是选"最好的",是选"能互相救的";交叉校验别用"相等",用"容差"。取到数 ≠ 取对了——每一批数据都要先验行数、起点、口径,再用。
三、交叉校验:别用「相等」,用「容差」
有了两个可用源,自然要问:它们说的是同一件事吗?
我把 akshare·新浪 与 baostock 在 5177 个共同交易日上的收盘价逐日比对:
完全相等(==) 65.73% ← 这个数字没有意义
最大相对差 0.0033bp
超出 5bp 容差 0 天 ← 这个才是要看的
"完全相等只有 65.73%" 听起来像灾难,实际上毫无问题。两个源都对,只是一个存到小数点后 3 位、另一个后 4 位。最大分歧 0.0033 个基点——换算成 4600 点的指数,是 0.0015 个点。
如果你用 df_a == df_b 做校验,你会得到 34% 的"不一致",然后花一整天去查一个不存在的 bug。
数值校验永远用容差,不用相等。本课默认容差 5bp(0.05%),超过才算实质分歧。
四、交付物:带降级与血缘的取数器
把上面所有教训写成一个组件(它会成为 qlab.data 的雏形):
SOURCES = [("akshare·东财", _from_akshare_em), # 顺序 = 优先级
("akshare·新浪", _from_akshare_sina),
("baostock", _from_baostock),
("yfinance", _from_yfinance)]
def fetch_daily(symbol="000300", need_start="2005-04-08", min_rows=4000):
failed = []
for name, fn in SOURCES:
try:
df = fn()
except Exception as e:
failed.append(f"{name}({type(e).__name__})"); continue # 挂了 → 降级
start = df["date"].iloc[0]
if start > need_start: # 关键:能取到 ≠ 取对了
failed.append(f"{name}(起点晚:{start})"); continue
if len(df) < min_rows:
failed.append(f"{name}(行数少:{len(df)})"); continue
return df, Provenance(...) # 带血缘返回
raise RuntimeError(f"所有源均失败:{failed}")
实测运行效果——故障发生时它自己绕过去了:
✗ akshare·东财: ConnectionError — 降级到下一个源
✓ akshare·新浪: 5960 行 [2002-01-04~2026-07-30] 0.65s — 采用
交叉校验 akshare·新浪 vs baostock:共同交易日 5177 天
完全相等(==) 65.73% ← 这个数字没有意义
最大相对差 0.0033bp
超出 5.0bp 容差 0 天 ← 这个才是要看的
✓ 分歧全部在四舍五入位内,两源可互为验证
每份数据都要带血缘
{
"symbol": "000300",
"source": "akshare·新浪",
"fetched_at": "2026-07-31 04:56:15",
"rows": 5960,
"start": "2002-01-04",
"end": "2026-07-30",
"fallbacks": ["akshare·东财(ConnectionError)"],
"cross_checked_with": "baostock",
"max_rel_diff_bp": 0.0033439168
}
纪律:没有血缘的数据不许进下游。
半年后你会遇到这样一天:某个回测结果和上次不一样了。这时候唯一能救你的,就是这个 json——它告诉你上次的数据是谁给的、什么时候拿的、覆盖到哪、和谁校验过。没有它,你只能推倒重来。
📌 免责:本课为技术教学,文中出现的标的代码仅用于接口连通性测试,不构成任何投资建议;各数据源的可用性随时间与网络环境变化,课文数字为 2026-07-31 单次实测。
这一节的收获来自一次真实故障:akshare 东财系四个接口全挂,新浪系 0.11 秒返回。由此得到三条纪律:
- 降级链:SOURCES 顺序 = 优先级,一个源挂了自动往下走
- 能取到 ≠ 取对了:yfinance 静默少给 16 年;区间与行数不达标 = 失败
- 校验用容差不用相等:完全相等 65.73% 毫无意义,最大相对差 0.0033bp 才是结论
外加一条会救你命的纪律:没有血缘的数据不许进下游。
下一节进入 pandas,但不是入门教程——是量化专用的那几件事:时间序列对齐、resample 的陷阱,以及怎么在写法层面把未来函数堵死,让 p0.3 那道守卫永远抓不到你。
🔎 来源与核验· 4 条,点开核对
