综合

A股行情数据源“集合竞价”深度拆解:AkShare/Tushare静默,TickDB实测寒武纪全程记录

作者: TickDB Research · 发布: 2026/9/3 · 阅读: 10

标签: 知乎A002

你9:15打开自己的行情脚本,用AkShare拉A股实时数据。终端弹出一行红字:ValueError: Length mismatch。你以为是参数写错了,改了重跑,还是报错。你换成Tushare,返回倒是正常了,但价格栏全是空的,直到9:30才突然跳出第一行数据。

你以为是自己代码的问题。其实不是。

你在用个人开发者能拿到的公开接口,而这些接口在9:15到9:25这个窗口,集体选择了沉默。你的策略在等数据,而开盘价早在9:25就已经确定了。

这10分钟的静默,不只是"拿不到数据"这么简单。它让你的开盘策略信号迟到5分钟,让你的K线量混入了集合竞价的成交,让你不知道自己拿到的"第一个开盘价"究竟是哪一刻产生的。

我用TickDB从9:15跑到9:30,打了7个采集点,把集合竞价的全过程API行为记录下来。这篇文章想说两件事:第一,你不关注这10分钟,会损失什么;第二,有了集合竞价数据之后,你能做什么。

集合竞价(9:15-9:25)是A股每天开盘价的形成时间,但大多数行情接口在这10分钟是静默的——我找到了一个在这个窗口有数据的接口,把全程行为记录下来。


30秒抓住重点

>

问题:AkShare/Tushare等免费接口在9:15-9:25不返回数据,你的开盘策略信号迟到5分钟,K线量和开盘价来源存在系统性缺陷

根因:交易所底层有虚拟竞价数据,但个人开发者常用的接口普遍不转发

方案:TickDB的REST端点在集合竞价窗口内和窗口结束后均有结构化数据

效果:窗口内可看到虚拟撮合价变动和depth的对称结构;9:25后出现open、side=neutral等字段,来源清晰

适合:做A股开盘策略、需要开盘价形成过程数据的人


这10分钟的静默,你在付出什么代价

大多数人知道"接口在9:15没数据",但没有把这件事的代价算清楚。代价不只是"空了一段时间",它渗透进策略的每一个环节。

代价一:你的信号迟到了5分钟。 9:25,开盘价已经是事实——寒武纪今天开1126,就是1126,交易所主机已经算完了。但你的接口9:30才给出第一个价格。这5分钟,知道开盘价的人和不知道的人同时在市场里,信息不对等直接体现在入场成本上。高波动开盘日,5分钟可以是3到5个点的差距,这不是小数。

代价二:你的K线量从第一根bar开始就是混的。 9:30:00这根1分钟K线的volume,实测是596手——全是集合竞价的统一撮合量。9:31:00起才是连续竞价的量,第一根就有2551手。如果你的策略用"开盘首根K线成交量vs过去N日均量"来判断开盘强弱,你在拿596和几千手的连续竞价均量比较——两种完全不同机制产生的量,你混在一起做判断,信号从第一根bar就失真了。

代价三:你不知道那个"开盘价"是哪一刻产生的。 你拿到一个open=1126,但你没有验证过它的来源。是9:25统一撮合的价格,还是9:30:01连续竞价的第一笔?这两个价格的含义完全不同:前者是全市场提前博弈的均衡点,由几百上千笔申报共同决定;后者是某一笔主动单推动的瞬时成交。用来算止损、算缺口、算回测基准,含义差得远。大多数人从来没验证过。

代价四:你错过了比开盘价更早的博弈信号。 本次实测:9:20撤单截止,depth量从44手跳到133手;9:24最后1分钟,从179手急涌到324手。这个量的积累曲线,在9:30开盘之前5分钟就已经成型——大资金9:20后已经无法撤单,他们在不可撤单阶段的行为,比开盘价本身更诚实。你等9:30看K线,看到的是结果,看不到过程。


9:25那一下,才是真正的开盘

券商App显示的开盘价,是9:25产生的,不是9:30。但你接口里拿到的"第一个价格",大概率是9:30连续竞价的第一笔成交。这不是一回事。

9:15到9:25这10分钟,A股在做一件事:统一价格撮合。9:15到9:20,买卖双方可以报单,也可以撤单。9:20到9:25,只许报单,不许撤单。9:25,交易所主机把所有申报汇集到一起,按一个算法撮合,得到一个价格——这个价格就是开盘价。9:30开始的连续竞价,才是我们熟悉的逐笔撮合。

这两个阶段的撮合逻辑完全不同。连续竞价是"一笔订单冲进来,找到对手方就成交",价格随订单流不断变动。集合竞价是"所有人先报完,再选一个能让成交量最大的价格"。前者解决流动性,后者解决价格发现。

一个细节说透两者的区别:上交所的数据显示,在9:20到9:25不可撤单阶段,每天仍有约1838笔撤单尝试被系统作废。在撮合前的最后一刻,还有人试图撤回自己的申报,改变供需结构。这10分钟里的博弈强度,远超你从9:30的K线上看到的。


你的行情数据源,在这10分钟做了什么

交易所底层Level-1行情,在9:15到9:25其实并不静默。上交所LDDS Level-1接口在这个阶段持续播发虚拟匹配价、虚拟匹配量和未匹配量。深交所STEP接口用状态码区分9:15-9:20"可撤单竞价"和9:20-9:25"不可撤单竞价"。这些数据就在交易所的主机里流淌。

问题出在个人开发者能接触到的层面。集合竞价数据不是不存在,是被一层层接口封装给滤掉了。从交易所到券商,再到开源库,每一次封装都丢一点竞价期间的状态信息,最后到个人开发者手里,只剩空值或报错。

数据源集合竞价窗口行为原因
TickDB窗口内有虚拟撮合数据;9:25后返回竞价结果REST端点对集合竞价阶段有完整支持
AkShareValueError: Length mismatch非连续时段无兼容处理
Tushare返回为空,9:30才有数据分钟线从9:30起记
Baostock无实时路径定位是历史回测
聚宽/米筐分钟线从9:30开始回测引擎的通用处理
券商Level-1封装过滤竞价虚拟字段只保留9:30后标准快照
机构Level-2有完整数据权限和成本限制个人接入

这张表里最值得关注的是第一行。TickDB不是通过Level-2权限拿数据,它走的是REST端点。不需要机构资质,只需要一个API密钥。


9:15到9:25,TickDB窗口内全程实测

2026年9月3日,我从9:15整点到9:30,每隔约2分钟调一次接口,共7个采集点。标的是688256.SH(寒武纪)。

9:15 | 集合竞价开窗(撤单允许阶段)

▼ 实测输出 | 2026-09-03 09:15 BJ | 688256.SH 寒武纪
GET https://api.tickdb.ai/v1/market/ticker?symbols=688256.SH
────────────────────────────────────────────────────────
{
  "last_price": "1120",       ← 当前虚拟撮合价(非昨收)
  "prev_close": "1108",
  "volume_24h": "0",          ← 当日成交量归零
  "amount_24h": "0",
  "high_24h":   "0",
  "low_24h":    "0",
  "timestamp":  1788398124000
  ── open 字段:不存在 ──      ← 开盘价未确定,字段本身缺失
}

GET https://api.tickdb.ai/v1/market/depth?symbol=688256.SH
────────────────────────────────────────────────────────
{
  "bids": [["1126","35"], ["0","0"], ...],
  "asks": [["1126","35"], ["0","1"], ...]  ← bid = ask,虚拟撮合特征
}

GET https://api.tickdb.ai/v1/market/trades?symbol=688256.SH
────────────────────────────────────────────────────────
{
  "trades": []    ← 集合竞价期间无成交记录
}

三个接口同时说了一件事:open字段缺失,volume_24h归零,depth里买一等于卖一,trades为空。 这是集合竞价进行中的API状态,和9:30连续竞价是两套完全不同的字段结构。

9:20 | 撤单截止——"只进不出"开始

▼ 实测输出 | 2026-09-03 09:20 BJ | 688256.SH 寒武纪
GET https://api.tickdb.ai/v1/market/depth?symbol=688256.SH
────────────────────────────────────────────────────────
{
  "bids": [["1126","133"], ["0","13"], ...],   ← 量 44 → 133,急增+89手
  "asks": [["1126","133"], ["0","0"],  ...]
}

撤单截止那一刻,bid[0]的量从44手跳到133手。撤单通道关闭后,只有新单能进来、没有旧单能出去,深度的量开始单调递增。这是9:20这道关卡在API里的直接体现。

9:24 | 清算前最后1分钟

▼ 实测输出 | 2026-09-03 09:24 BJ | 688256.SH 寒武纪
GET https://api.tickdb.ai/v1/market/depth?symbol=688256.SH
────────────────────────────────────────────────────────
{
  "bids": [["1126","324"], ["0","85"], ...],   ← 量 179 → 324,最后急涌+145手
  "asks": [["1126","324"], ["0","0"],  ...]
}

集合竞价窗口内depth量的完整变化轨迹:

时间bid[0]量增量阶段
9:1535手撤单允许,缓慢积累
9:1744手+9撤单允许
9:20133手+89撤单截止后急增
9:22179手+46只进不出,持续积累
9:24324手+145最后1分钟急涌

这条曲线在9:30之前5分钟就成型了,是比开盘价更早的博弈强度信号。

9:25 | 统一撮合完成——三个字段同时到位

▼ 实测输出 | 2026-09-03 09:25:03 BJ | 688256.SH 寒武纪
GET https://api.tickdb.ai/v1/market/ticker?symbols=688256.SH
────────────────────────────────────────────────────────
{
  "open":        "1126",       ← 字段首次出现 = 今日开盘价(集合竞价确定)
  "last_price":  "1126",
  "prev_close":  "1108",
  "volume_24h":  "596",        ← 0 → 596手(集合竞价总成交量)
  "amount_24h":  "67097200",
  "high_24h":    "1126",       ← 只有一个成交价,高 = 低 = 开
  "low_24h":     "1126",
  "timestamp":   1788398703000
}

GET https://api.tickdb.ai/v1/market/trades?symbol=688256.SH
────────────────────────────────────────────────────────
{
  "trades": [{
    "id":        "1788398703000_0",
    "price":     "1126",
    "quantity":  "596",        ← 全部集合竞价成交量,一条记录
    "side":      "neutral",    ← 集合竞价统一撮合专属标记
    "timestamp": 1788398703000
  }]
}

GET https://api.tickdb.ai/v1/market/depth?symbol=688256.SH
────────────────────────────────────────────────────────
{
  "bids": [["1126","65"], ["1125","2"], ["1122.76","8"], ["1120","6"], ...],
  "asks": [["1126.99","2"], ["1128","16"], ["1128.8","2"], ["1129","18"], ...]
  ← bid ≠ ask,正常双侧盘口恢复
}

9:25之后,三件事同时发生:ticker里open字段出现;trades里出现side="neutral"的统一撮合记录;depth里bid和ask不再对称,正常双侧盘口恢复。这三件事对应的就是9:25统一撮合完成那一刻,时间戳精确到秒:09:25:03。

9:30 | 连续竞价开始——K线真相

▼ 实测K线 | 2026-09-03 | 688256.SH 寒武纪 | 1m
────────────────────────────────────────────────
bar① time = 09:30:00 BJ   ← 集合竞价独立bar
  open=1126, high=1126, low=1126, close=1126
  volume = 596             ← 四价合一,全是集合竞价量

bar② time = 09:31:00 BJ   ← 连续竞价第一根bar
  open=1124.68, high=1129.86, low=1111, close=1112
  volume = 2551            ← 连续竞价才是这个量级

这里有一个必须说清的坑:集合竞价数据独立占据09:30:00这根K线bar,连续竞价从09:31:00才开始。 09:30:00这根bar的四价合一(open=high=low=close)是集合竞价的专属特征,volume是596手,不能和09:31:00之后的连续竞价量级放在一起做比较。


有了集合竞价数据,你能做什么

实测验证了TickDB在这个窗口确实有数据之后,更重要的问题是:这些数据值多少钱?

价值一:把信号提前5分钟。 9:25统一撮合完成后,开盘价就确定了。你可以立刻知道今天是高开还是低开、开盘量是大是小,而不需要等9:30连续竞价启动。5分钟的信息优势,意味着你在9:25到9:30之间就能准备好开盘策略的执行逻辑,而不是在9:30价格已经开始跑动之后才追。

价值二:读懂集合竞价的量积累过程。 depth量的变化轨迹不只是一个数字,它告诉你两件事:9:20撤单截止前后的量跳变幅度,反映了"不可撤单阶段有多少坚定买入";最后1分钟的急涌幅度,反映了"临近清算时市场情绪是否在加速集中"。本次实测,9:20后量跳+89手,最后1分钟急涌+145手,两个节点都有明显信号。这个曲线在9:30开盘之前就能读,等9:30看K线你已经看不到过程了。

价值三:识别重大事件后的市场态度。 财报、监管公告、隔夜美股大跌之后,集合竞价是市场第一次集中表态的时间窗口。价格飘移的速度、撮合价在9:20前后的稳定性、不可撤单阶段量的急增幅度——这些过程数据比最终开盘价本身,更能说明市场"消化了多少"、"分歧有多大"。一个开盘价涨3%可能是信息完全消化,也可能是分歧极大时的脆弱均衡;过程数据能区分这两种情形,开盘价本身区分不了。

价值四:让止损和风控有可靠的价格基准。 止损单挂在"开盘价基础上",这个开盘价必须是9:25确定的那个。如果你的风控系统在9:25需要计算持仓市值(触发预警或强平),用9:30的连续竞价价格会有几分钟的延迟,高波动日延迟等于多余的损失。能在9:25拿到经过side=neutral验证的开盘价,止损和风控的价格基准才是准的。

价值五:回测数据质量的基础保障。 如果你的开盘策略历史回测用了含集合竞价量的09:30:00 bar做"开盘强度"指标,你的回测曲线存在系统性偏差——你在拿几百手的集合竞价量和几千手的连续竞价均量做比较,信号的方向可能是对的,但幅度是失真的。用trades原始记录区分集合竞价和连续竞价成交,是修正这个偏差的唯一办法。


三个信号,判断数据来自集合竞价还是连续竞价

实测之后,判断逻辑很清楚了。

信号一:open字段是否存在。 集合竞价进行中(9:15-9:25),ticker响应体里不含open字段——不是null,是字段本身缺失。9:25统一撮合完成后,open字段出现,且在当天剩余时间里保持不变。用字段存在性做状态判断,比对比本地时钟更可靠。

信号二:side是否为neutral。 9:25之后、trades接口里最新一条记录的side是neutral。连续竞价开始后,side只有buy或sell,neutral不再出现。出现neutral,意味着你拿到的是统一撮合成交,不是连续竞价。这个字段的含义来自FIX协议——集合竞价没有主动方,Deutsche Börse、Eurex的做法也是把aggressor side标为N/A,TickDB用neutral表达同一件事。

信号三:high_24h是否等于low_24h。 统一撮合完成后、连续竞价还没开始这段时间,high_24h和low_24h都等于open(集合竞价只有一个成交价)。三价合一是集合竞价数据的专属特征,连续竞价开始后high和low立刻开始分叉。

三个信号同时满足,你拿到的是9:25那次价格发现的产物,不是9:30之后的价格。


开盘价是结果,过程才是信息

有了工具之后,更重要的认知是:你应该用这些数据看什么。

开盘价是结果。9:15到9:25的申报和撮合过程才是信息。大多数接口帮你省略了过程,但省略本身是有代价的。

学术上对集合竞价的价格发现效率并没有一致的"更好"结论。部分基于A股2006年开放式集合竞价改革的研究发现,提高透明度后定价效率有改善。也有早期研究显示,集合竞价产生的开盘价方差显著大于收盘价,存在对隔夜信息的过度反应。最有解释力的调和结论是:集合竞价不保证更准。但它承载了隔夜信息在开盘前的集中释放——而这个过程数据,比最终那个开盘价本身,更接近市场参与者的真实博弈。

这也解释了为什么只有"结果"是不够的:你需要知道这个结果是怎么形成的,才能判断它是否可靠、它背后的力量是否持续。


完整接入代码

下面的代码处理两个场景:集合竞价窗口内(9:15-9:25)和窗口结束后(9:25+)。替换YOUR_API_KEY即可:

import requests

BASE_URL = "https://api.tickdb.ai"
HEADERS = {"X-API-Key": "YOUR_API_KEY"}

# 查ticker快照
ticker_resp = requests.get(
    f"{BASE_URL}/v1/market/ticker",
    headers=HEADERS,
    params={"symbols": "688256.SH"}
)
ticker_data = ticker_resp.json()["data"][0]

# 查最近成交
trades_resp = requests.get(
    f"{BASE_URL}/v1/market/trades",
    headers=HEADERS,
    params={"symbol": "688256.SH"}
)
trades = trades_resp.json()["data"]["trades"]

# 用open字段存在性判断当前阶段
if "open" not in ticker_data:
    # 集合竞价窗口内(9:15-9:25)
    print("[集合竞价进行中]")
    print("虚拟撮合价(last_price):", ticker_data["last_price"])
    print("当日成交量:", ticker_data["volume_24h"])  # 应为 "0"
    print("trades:", trades if trades else "空(集合竞价期间无成交记录)")

else:
    # 统一撮合完成后(9:25+)
    print("[集合竞价已完成]")
    print("open(开盘价):", ticker_data["open"])
    print("last_price:", ticker_data["last_price"])
    print("volume_24h:", ticker_data["volume_24h"])

    if trades and trades[0]["side"] == "neutral":
        t = trades[0]
        print("side:", t["side"], "← 确认来自集合竞价统一撮合")
        print("集合竞价成交价:", t["price"])
        print("集合竞价成交量:", t["quantity"])
    elif trades:
        print("连续竞价已开始,side =", trades[0]["side"])

核心逻辑是if "open" not in ticker_data:集合竞价进行中时,open字段不在响应里;统一撮合完成后,字段出现。用字段存在性做阶段判断,不依赖本地时钟和交易所的时间同步。


边界诚实

限制一:REST快照,不是流式推送。 本文的实测走REST端点,每次调用拿一个快照,每隔约2分钟采集一次。TickDB的WebSocket在集合竞价窗口内的推送频率和连续性我没有专门测试。如果你的策略需要毫秒级连续数据流,需要自己验证WebSocket在这个窗口的行为。

限制二:K线历史无法回溯集合竞价过程。 09:30:00这根bar是集合竞价的汇总结果(四价合一、volume=集合竞价总量),没有9:15-9:25逐分钟的虚拟撮合价变化历史。要复盘竞价过程,需要在窗口内实时采集ticker或depth快照自行存档,不能靠事后拉K线。

限制三:行情数据不是投资结论。 这些字段能告诉你"数据来自集合竞价"、"开盘价是多少"、"集合竞价量有多大",但不会告诉你"该买还是该卖"。基于集合竞价数据的策略逻辑,需要你自己建立和验证。

如果你也在做A股开盘策略,建议你在下一个交易日的9:15到9:30之间,用TickDB自己跑一遍ticker和trades的快照序列,观察open字段的出现时刻和side字段的切换点。数据验证这一步,永远值得比策略开发花更多时间。因为策略再精巧,也跑不出一个有缺陷的数据基础。

集合竞价(9:15-9:25)是A股每天开盘价的形成时间,但大多数行情接口在这10分钟是静默的——我找到了一个在这个窗口有数据的接口,把全程行为记录下来。


参考资料与文献

  1. AkShare接口非交易时段报错记录:ValueError: Length mismatch(开源社区issues与使用讨论)
  2. 上交所LDDS Level-1接口规范:集合竞价期间虚拟匹配价、虚拟匹配量、未匹配量字段定义与播发机制
  3. 深交所STEP接口规范:开盘集合竞价状态码130(可撤单)、150(不可撤单)
  4. 刘遂、叶武等(2006):沪市开放式集合竞价改革对流动性和定价效率的影响
  5. 王志刚等(2005):A股开盘价与收盘价价格发现效率对比
  6. 孔爱国等(2002):上海股市开盘收益方差与过度反应研究
  7. Deutsche Börse官方文档:竞价交易Aggressor-Side字段处理规范
  8. Eurex T7参考材料:竞价交易aggressor side不适用声明
  9. A股集合竞价虚假申报操纵典型案例:马永威、徐国新、苏颜翔、唐汉博案(2011-2018);徐阳案(2025)
  10. 上交所2006年集合竞价撤单行为统计:9:20-9:25不可撤单阶段日均1838笔作废撤单

通过 TickDB API 获取实时行情数据

一个 API 接入外汇、加密货币、美股、港股、A股、贵金属和全球指数的实时行情。支持 WebSocket 低延迟推送,免费开始使用。

免费领取 API Key查看 API 文档

相关文章