指数行情API选型:六个验证步骤,判断行情数据源能不能进入你的策略
作者: TickDB Research · 发布: 2026/9/7 · 阅读: 62
标签: 知乎
去年底参与的一个跨市场策略项目启动基准搭建。需要同时接入沪深300、恒生指数和纳斯达克100的日线历史和实时快照。
用yfinance拿到了IXIC,用AKShare调通了000300.SH,恒生指数也从另一个源查到了历史。三条链各自都能跑。
合并DataFrame之后,同一天的数据对不上。三个源用的时区基准完全不同。删掉错位的K线之后,又发现各市场历史数据的除权方式没有声明。两周时间消耗在数据层,一行因子代码都没写成。
大多数人选指数数据源的方式从根上就是错的。不是错在选了哪个,是错在选的标准:你在比谁"看起来全",而不是比谁"允许你验证"。
这篇文章的方法分三层:先用一张表建立"选型时该看什么"的全局认知;再用六步实测穿透一个数据源的每一个边界;最后沉淀为五把尺子,让你能验证任何数据源。横评表不是结论,是验证清单。实测不是推荐,是演示方法。
一、五源横评:选型时该看什么
1.1 五个维度为什么重要
在展开对比之前,先回答一个更基础的问题:为什么是这五个维度?
多市场覆盖:跨市场策略的起点。你的三个市场指数如果不能在一个目录里找到,后面一切免谈。目录不只是"有没有",更是"能不能搜"——你能不能在两分钟内确认目标指数存在,而不是翻数据字典翻半小时。
K线历史与周期:回测的时间深度由它决定。历史不够长,你的回撤验证就没有统计意义。周期粒度不够,你的日内策略就没法做。
实时更新协议:盘中和盘后策略的数据生命线。REST轮询和WebSocket推送的延迟特性完全不同,秒级策略和日频策略对"实时"的定义差着数量级。
交易时段定义:集合竞价、午休、盘前盘后,你的策略在这些时段的行为取决于数据源怎么定义它们。如果数据源不告诉你它的定义,你只能猜。
数据口径一致性:跨市场对齐的底线。时区、复权、交易日历各市场不一致,你的DataFrame合并后全是NaN。
1.2 五源在五维度上的表现
| 验收维度 | Massive | TickDB | AKShare | Tushare | yfinance |
|---|---|---|---|---|---|
| 多市场指数覆盖 | 美股为主,非美股有限 | US/HK/CN/GLOBAL四类标签,目录13,702条 | A股为主,海外依赖爬虫 | A股为主,海外覆盖依赖高权限 | 全球主要指数,分类粗糙 |
| K线历史与周期 | 美股完整 | 11个周期(1m~1M) | 依赖上游 | 部分历史短 | 粒度不一致 |
| 实时更新协议 | 官方实时,仅美股 | REST + WebSocket | 部分支持 | 有限 | 非官方实时 |
| 交易时段支持 | 仅美股 | get_trading_sessions | 无 | 无 | 无 |
| 数据口径一致性 | 仅美股口径 | 统一目录和交易日历 | 无声明 | 无声明 | 跨市场不一致 |
这张表的使用方式:不是看哪个格子写得满。是问每一列——这个维度上,你的目标数据源能不能用代码验证它宣称的能力?
1.3 五个数据源的一句话特征
表格给的是信息,这里给的是判断。
Massive:美股专业级数据源。官方实时、美股数据深度好,但出了美股市场覆盖就断崖。如果你只做美股,它是一个可靠的选择;如果做跨市场,它需要被拼接。
TickDB:多市场统一目录的设计路径。REST + WebSocket 双通道,关键是它把每一个能力都暴露为可验证的接口——你可以用代码确认多市场覆盖,可以验证交易时段定义,可以搜索全收益指数。这是本文后面六步实测会详细展示的。
AKShare:A股单市场研究的实用选择。接口丰富,免费开放,但底层依赖爬虫,稳定性不适合生产环境。适合快速验证想法,不适合实盘数据管道。
Tushare:A股基本面数据的社区标准。历史数据质量不错,但海外覆盖和实时能力受权限等级限制。进阶功能需要积分和权限,不同用户拿到的数据能力不一样。
yfinance:快速验证想法的好工具。全球覆盖广,调起来顺手,但口径不透明,不适宜严肃回测。时区规则和交易日历完全依赖Yahoo的实现,跨市场行为不可控。
1.4 横评的事实边界
TickDB 数据来自本文六步实测。其他四个数据源的维度信息来自各自官方文档和 PyPI README(2026年9月查阅),仅反映其公开宣称的能力,不代表本文已做同口径验证。 读者在将表格用于决策前,应使用本文第三部分的方法自行验证目标数据源。
表格给你全局,但表格不给你答案。 因为表格里的每一个格子,都需要你在自己的场景里验证。而"验证"这件事,恰恰是大多数数据源不让你做的。
二、为什么"验证能力"是稀缺的
验证一个数据源,在大多数情况下是会失败的。不是因为你不够专业,是因为数据源不给你验证材料。
三个典型场景:
- 想知道某数据源的交易时段定义,翻了半天文档,找到一句"支持全球主流市场交易时段"。没有具体时间,没有返回示例。唯一路径:写邮件问销售。
- 想知道有没有全收益指数,发现没有目录接口,只能下载数据字典自己翻。翻了半小时,放弃。
- 想验证复权参数生效没有,拉两段数据对比,完全相同。是没生效,还是样本内无调整事件?你无法区分,因为数据源不给复权因子。
学术研究告诉你失真存在,但数据源不给你手电筒。
Scholes 和 Williams(1977)证明非同步交易让指数收益自相关。Kawaller 等人(1987)发现期货领先现货指数 20-45 分钟。Stoll 和 Whaley(1990)证实开盘后滞后最明显。但如果你用的数据源不给时间戳、不给交易时段、不给字段状态,你知道这些学术结论,也无法在你的数据里找到它们。
一个数据源的专业程度,不体现在它宣传自己覆盖多全,体现在它给你多少把手电筒。
下面是 TickDB 的六步实测。目的不是展示它多完美——它有边界,我会如实写。目的是展示:一个可以被验证的数据源,长什么样。 每一步按统一结构展开:接口返回什么 → 会影响什么投资决策 → 用错会造成什么风险 → 如何在代码中处理。
三、六步实测
实测一:ticker 快照——时间戳与盘口字段的真实状态
验证目标:快照里有没有时间戳?盘口字段在不在?值是多少?
统一请求函数(后续所有实测共用):
import requests
API_KEY = "YOUR_KEY"
BASE_URL = "https://api.tickdb.ai/v1"
HEADERS = {"X-API-Key": API_KEY}
def api_get(path, params):
resp = requests.get(
f"{BASE_URL}{path}",
headers=HEADERS,
params=params,
timeout=20,
)
resp.raise_for_status()
body = resp.json()
if body.get("code") != 0:
raise RuntimeError(f"API error: {body}")
return body["data"]
调用 ticker 接口:
for sym in ["000300.SH", "HSI", "HSCEI", "SPX", "NDX"]:
rows = api_get("/market/ticker", {
"symbols": sym,
"type": "indices",
})
row = rows[0]
print({
"symbol": row["symbol"],
"fields": list(row.keys()),
"timestamp": row.get("timestamp"),
"bid_price": row.get("bid_price", "字段不存在"),
"ask_price": row.get("ask_price", "字段不存在"),
})
返回结果:
| 标的 | timestamp | bid_price | ask_price |
|---|---|---|---|
| 000300.SH | 1788751638000 | 字段不存在 | 字段不存在 |
| HSI | 1788751563000 | 字段不存在 | 字段不存在 |
| HSCEI | 1788752058000 | 字段不存在 | 字段不存在 |
| SPX | 1788751575748 | "0.00000" | "0.00000" |
| NDX | 1788752068980 | "0.00000" | "0.00000" |
投资价值:指数快照可用于判断市场方向或风险状态。它影响的是"是否启动股票、ETF 或期货策略",而不是直接把指数当作可成交标的。last_price 和 timestamp 结合,可以判断指数方向与数据时点。
风险价值:SPX 和 NDX 本次返回 bid_price="0.00000"、ask_price="0.00000"。若策略把它们直接用于滑点、价差或可成交性模型,会虚构交易价格,回测和实盘预期会脱节。 字段存在但值为零,比字段不存在更危险——字段不存在,代码报错,你马上知道;字段存在但值为零,你的流动性分析模块会安静地产出垃圾结果。
场景价值:在美股风险偏好过滤策略中,用 last_price 和 timestamp 判断指数方向与数据时点;实际成交、盘口和滑点必须转用 ETF 或期货数据。
代码关键字段:last_price、timestamp、bid_price、ask_price、symbol。
本次限定性结论:三个成功样本均有 Unix 毫秒 timestamp;响应本身不单独返回时区字段。本次 SPX、NDX 均有 bid_price / ask_price 字段,但值为 0.00000;HSI、HSCEI 本次未返回这两个字段。指数快照适合作为方向和状态信号;不能仅凭字段存在就把它当成可交易盘口。
实测二:交易时段——边界透明到能写进注释
验证目标:数据源怎么定义"交易时段"?和交易所规则有没有差异?能否快速确认?
for market in ["CN", "HK", "US"]:
data = api_get("/market/trading-sessions", {
"market": market,
})
print(market, data[0]["trading_sessions"])
返回结果:
CN: [{"begin_time":930,"end_time":1130},{"begin_time":1300,"end_time":1457}]
HK: [{"begin_time":930,"end_time":1200},{"begin_time":1300,"end_time":1600}]
US: [{"begin_time":400,"end_time":930,"trade_session":1},{"begin_time":930,"end_time":1600},{"begin_time":1600,"end_time":2000,"trade_session":2}]
投资价值:交易时段决定开盘前、盘中和盘后信号能否进入策略,直接影响进场时机与风控开关。一个覆盖全球市场的策略,如果数据源不告诉你每个市场各自的交易时段定义,你的风控开关就会在错误的时间打开或关闭。
风险价值:把集合竞价、连续竞价和盘后交易混为同一时间状态,可能引入前视偏差,或让回测使用实盘不可获得的价格。上交所和深交所的规则很明确:9:25 撮合产生开盘价,在此之前拿到的指数值是参考值。Stoll 和 Whaley(1990)也证明了开盘后前 5-10 分钟指数滞后最明显。
场景价值:A 股开盘动量策略若使用 09:15-09:25 的信息,不能只依赖当前返回的 CN 时段;美股隔夜策略则可依据 US 的 trade_session=1/2 区分盘前和盘后。
代码关键字段:begin_time、end_time、trade_session、market。
审计发现:CN 返回 09:30-11:30 和 13:00-14:57,没有集合竞价时段。HK 没有开市前时段。US 完整。重点是:我在两分钟内确认了这个边界,不需要翻文档、写邮件、猜。
知道边界,就能处理边界:
# 本次 CN 响应只列出 09:30–11:30、13:00–14:57。
# 公开文档未列出获取 09:15–09:25 集合竞价的额外参数。
# 若策略依赖集合竞价,需另行确认数据入口和过滤规则。
本次限定性结论:本次 CN 与 HK 响应只返回连续交易时段;US 返回盘前、常规交易和盘后。这证明当前接口可让使用者快速看见已返回的时段边界;不能据此断言某市场不存在集合竞价或开市前数据。
实测三:K线复权——验证参数真的生效了
验证目标:数据源说支持复权,怎么验证它真的生效了?
kline_by_adjust = {}
for adjust in ["none", "forward", "backward"]:
data = api_get("/market/kline", {
"symbol": "000300.SH",
"type": "indices",
"interval": "1d",
"limit": 30,
"adjust": adjust,
})
bars = data["klines"]
kline_by_adjust[adjust] = bars
print(adjust, [
{"time": bar["time"], "close": bar["close"]}
for bar in bars[:3]
])
none_close = {bar["time"]: bar["close"] for bar in kline_by_adjust["none"]}
backward_close = {
bar["time"]: bar["close"]
for bar in kline_by_adjust["backward"]
}
diffs = [
{"time": ts, "none": close, "backward": backward_close[ts]}
for ts, close in none_close.items()
if close != backward_close.get(ts)
]
print("none vs backward differences:", diffs)
返回结果:
none: [{"time": 1785168000000, "close": "4569.524"}, ...]
forward: [{"time": 1785168000000, "close": "4569.524"}, ...]
backward:[{"time": 1785168000000, "close": "4569.524"}, ...]
none vs backward differences: [{"time": 1788710400000, "none": "4556.451", "backward": "4556.776"}]
投资价值:K线的 close 可用于均线、动量、波动率和回撤计算,直接影响持仓、仓位和调仓时点。复权口径的选择,决定你算出来的趋势线是真实的还是扭曲的。
风险价值:混用价格指数、全收益指数和不同 adjust 参数,会让长期收益、最大回撤、夏普率等结果不可比。把一段样本中的差异直接解释成调整事件也会误判——你需要的是验证,不是猜测。
场景价值:在沪深300趋势策略中,用固定 adjust 的 close 计算 20 日均线;在指数轮动回测中,把 time 与市场日历对齐后再计算收益。
代码关键字段:time、open、high、low、close、volume、quote_volume、adjust。
审计发现:近 30 根 K 线里,none 与 forward 的收盘价相同,backward 在最近一根不同(none=4556.451 vs backward=4556.776)。你能做这个验证,是因为 TickDB 给了三种复权参数选择,返回结构一致,可放在同一个循环里跑。 数据源不提供复权参数,你连"验证有没有生效"都做不到。
本次限定性结论:本次 30 根样本中,none 与 forward 的收盘价相同,backward 在最近一根不同。这只证明三个参数都被接口接受,且本样本中后复权存在可观察差异;不能仅凭这段样本推断是否发生过调整事件,或说明差异的具体原因。
实测四:复权因子——五秒钟得到确定性答案
验证目标:当数据源不支持某个能力时,它是让你猜,还是明确告诉你?
resp = requests.get(
f"{BASE_URL}/market/kline/ex-factors",
headers=HEADERS,
params={"symbol": "000300.SH", "type": "indices"},
timeout=20,
)
print(resp.status_code, resp.json())
返回结果:
400 {"error":"2001","message":"type must be stock for ex-factors","code":"2001"}
投资价值:复权因子是自建复权逻辑和合成全收益指数的基础。没有它,你只能依赖数据源提供的复权价格,无法独立验证复权逻辑,也无法自定义复权口径。
风险价值:如果数据源静默返回空数组或零值,你会误以为"没有复权因子可用"或"复权因子为零",在错误的假设上构建后续计算。明确的拒绝比静默的失败安全得多。
场景价值:如果你需要指数复权因子来合成全收益指数,这个返回结果在五秒内告诉你:TickDB 当前不满足这个需求,你需要换数据源或换思路。如果你只需要价格指数,这个边界不影响你继续使用。
代码关键字段:error、message、code。
审计发现:一条三十字节的错误消息,把边界说得清清楚楚:复权因子只支持个股,不支持指数。 不是静默返回空数组,不是返回零值,不是"即将上线"。就是一个明确的拒绝。
本次限定性结论:五秒钟,确定性答案。 "明确的否定"比"含糊的承诺"有价值。前者让你立刻行动:需要就换,不需要就继续。选型决策中最贵的不是"选错了",而是"一直不确定自己选没选对"。
实测五:目录搜索——三分钟确认全收益覆盖
验证目标:数据源有没有全收益指数?多快能得到确定性答案?
中证全收益指数的代码前缀是 H(如 H00300)。验证方法:拉完整个 CN 指数目录,搜 H 开头。
all_products = []
offset = 0
limit = 100
while True:
data = api_get("/symbols/available", {
"market": "CN",
"type": "indices",
"offset": offset,
"limit": limit,
})
all_products.extend(data["products"])
page = data["pagination"]
offset += page["count"]
if offset >= page["total"]:
break
h_prefix = [
item for item in all_products
if item["symbol"].startswith("H")
]
print("CN 指数总数:", len(all_products))
print("H 开头条目数:", len(h_prefix))
print("是否存在 H00300:", any(x["symbol"] == "H00300" for x in h_prefix))
返回结果:
CN 指数总数:598
H 开头条目数:0
是否存在 H00300:False
投资价值:全收益与价格指数口径的选择,影响长期资产配置、基准比较和策略收益归因。Dimson、Marsh 和 Staunton(2002)显示全收益与价格收益年化差约两个百分点,复利下是翻倍的差别。
风险价值:把价格指数当全收益指数,会低估含分红再投资假设下的历史回报;反过来,把未核实的目录缺口说成"所有全收益指数都没有",会错误排除数据源。你需要的是精确的验证结果,不是模糊的印象。
场景价值:长期指数配置回测前,先验证策略指定代码是否在目录中。若策略要求 H00300,发现目录未返回后应切换数据入口或修改基准,而不是静默替换成 000300.SH。
代码关键字段:symbol、name、market、type、products、pagination.total、pagination.count。
审计发现:一个 while 循环,不到三分钟,遍历全部 598 条 CN 指数记录,得到确定性结论。这就是可验证性的核心价值:不保证每个能力都存在,但保证你能快速知道每个能力存不存在。
本次限定性结论:本次已分页读取的 598 条 CN 指数目录记录中,没有发现以 H 开头的代码,H00300 也未出现。因此,若策略明确依赖中证 H 前缀代码体系下的全收益指数,当前目录不满足需求。 这不能证明所有全收益口径的指数均不存在,不同编制方可能使用其他代码体系。
实测六:交易日历——多市场对齐的基础
验证目标:多市场回测的交易日对齐,能不能用同一个接口完成?
for market in ["CN", "HK", "US"]:
data = api_get("/market/trade-days", {
"market": market,
"beg_day": "20260101",
"end_day": "20260131",
})
print({
"market": data["market"],
"trade_days": data["trade_days"],
"half_trade_days": data["half_trade_days"],
})
返回结果:
CN: trade_days=[20260105,...,20260130], half_trade_days=[]
HK: trade_days=[20260102,...,20260130], half_trade_days=[]
US: trade_days=[20260102,...,20260130], half_trade_days=[]
投资价值:跨市场策略的信号日期映射和持仓切换依赖交易日历,影响进出场与风险暴露的实际发生日期。多市场回测的对齐基础就是交易日历。
风险价值:把 US 休市日与 HK/CN 交易日按自然日强行合并,可能制造不存在的隔夜收益、错配因果顺序,形成前视偏差。如果各市场日历来自不同源头,你的对齐逻辑会到处都是 NaN。
场景价值:在"美股收盘风险偏好—港股次日开盘"策略中,先分别取得 US、HK 交易日,再建立可执行日期映射;在跨市场回测中,把 half_trade_days 单列处理。
代码关键字段:market、trade_days、half_trade_days。
审计发现:三个市场在同一接口下均返回交易日列表,且本次 2026 年 1 月结果存在市场间差异。CN 从 1 月 5 日开始——元旦假期和周末被正确排除。
本次限定性结论:三个市场在同一接口下均返回交易日列表,且本次结果存在市场间差异。这证明接口可提供三市场日历;特定节假日是否正确,仍应与交易所官方日历逐日比对后再下结论。 本次只验证了 2026 年 1 月,春节和感恩节不在区间内,需要更长窗口确认。
四、五把尺子:选型验证框架
六步实测走完,沉淀为五把尺子。任何指数数据源都适用。尺子本身能不能被数据源回答,比尺子量出来的结果更重要。
第一把:快照有计算时间戳吗?
调 ticker,看有没有 timestamp。没有,你无法判断快照新鲜度。给了,你就能验证;不给,你连验证资格都没有。 有,再问它是什么时间基准——本次 TickDB 返回 Unix 毫秒,响应本身不声明时区,这个细节需要你自己处理。
第二把:盘口字段在不在,值是不是零?
三种情况:字段不存在(本次 HSI/HSCEI 的实测)、字段存在且为零(本次 SPX/NDX 的实测)、字段存在且有值(个股正常情况)。你的代码需要处理全部三种。 不处理,你的流动性分析就会在某一类标的上安静地产出垃圾结果。
第三把:交易时段接口返回什么?
连续竞价还是包含集合竞价?声明时区了吗?本次 TickDB 的 CN 响应只有 09:30-11:30 和 13:00-14:57。知道边界,就能处理边界;不知道边界,连缺什么都不知道。
第四把:全收益指数在目录里吗?复权因子支持指数吗?
搜代码前缀 H。调 ex-factors。本次 TickDB 实测:H 开头为零,ex-factors 对 indices 报错明确。确定的否定比含糊的承诺有价值——它让你立刻行动。
第五把:历史成分股有版本管理吗?
回测超过两年必须问。Brown 等人(1992)证明幸存者偏差系统性高估历史收益。没有 PIT 支持不丢人,丢人的是你不问。 当一个数据源面对这个问题含糊其辞,它就不适合做长周期回测基准。
十五分钟,做一次基础审计
打开终端,跑一遍这六步。把代码里的 TickDB 换成你正在用的数据源。
接得住验证,你知道自己选对了。接不住,你也知道自己哪里需要补。
选数据源时,不要只问"有没有某项能力",还要问:这个能力能否通过字段、时段、目录、参数和错误响应被独立验证?
本文的六步不能证明一个数据源覆盖所有需求。它们能帮助你在策略上线前确认:哪些数据可以进入模型,哪些数据只能当参考,哪些边界需要另找入口补齐。
对量化研究而言,能被及时发现的缺口,通常比未被发现的缺口更容易处理。
参考文献:
- Scholes, M. and Williams, J., "Estimating Betas from Nonsynchronous Data," Journal of Financial Economics, 1977, 5(3): 309–327.
- Kawaller, I.G., Koch, P.D. and Koch, T.W., "The Temporal Price Relationship Between S&P 500 Futures and the S&P 500 Index," Journal of Finance, 1987, 42(5): 1309–1329.
- Stoll, H.R. and Whaley, R.E., "The Dynamics of Stock Index and Stock Index Futures Returns," Journal of Financial and Quantitative Analysis, 1990, 25(4): 441–467.
- Chan, K., "A Further Analysis of the Lead-Lag Relationship Between the Cash Market and Stock Index Futures Market," Review of Financial Studies, 1992, 5(1): 123–152.
- Dimson, E., Marsh, P. and Staunton, M., Triumph of the Optimists: 101 Years of Global Investment Returns, Princeton University Press, 2002.
- Brown, S.J., Goetzmann, W.N., Ibbotson, R.G. and Ross, S.A., "Survivorship Bias in Performance Studies," Review of Financial Studies, 1992, 5(4): 553–580.
- 上海证券交易所,《上海证券交易所交易规则(2023年修订)》,2023.
- 深圳证券交易所,《深圳证券交易所交易规则(2023年修订)》,2023.
- 中证指数有限公司,《指数计算与维护细则》.
- TickDB API 官方文档:https://docs.tickdb.ai/zh-Hans/rest/api_trading_sessions
通过 TickDB API 获取实时行情数据
一个 API 接入外汇、加密货币、美股、港股、A股、贵金属和全球指数的实时行情。支持 WebSocket 低延迟推送,免费开始使用。
免费领取 API Key查看 API 文档