用 Python 拉 A 股全市场实时快照:Notebook 请求、结果校验与易错点

一句话结论:全市场快照真正花时间的往往不是"发请求",而是拿到 DataFrame 之后那几行校验------代码是否齐全、有没有重复、价格关系是否成立、时间戳是否统一,以及非交易时段拿到的是不是上一交易日的静态值。

1. 为什么"全市场快照"不是一行代码的事

个人开发者最常见的做法是:Notebook 里写几行请求,把结果存成 df,然后 df.head() 看一眼,觉得没问题就往下走。

问题在于,df.head() 只能告诉你"前五行长得像样",不能告诉你:

  • 5000 多只 A 股里,到底有多少只没有返回;
  • 北交所标的(如 920047.BJ)有没有被漏掉;
  • 同一只标的是否出现了两条记录;
  • 某个字段为 0 或 NaN,是停牌、是未开盘,还是数据本身异常;
  • 你在周六下午跑出来的"实时快照",到底是实时数据,还是上一个交易日的收盘静态值。

这五件事没有一件会报错。代码会安静地跑完,DataFrame 会安静地躺在那里,问题会在三天后你拿它算指标的时候才暴露出来。

所以本文的重点不在"怎么发一个请求",而在请求之后怎么检查结果。

2. 拆解:从标的池到快照的三步链路

把需求拆开,其实只有三步,中间夹着一个容易被忽略的边界:

text 复制代码
① 取 CN_Stock 标的池(拿到当前有效的 A 股代码列表)
                ↓
② 用这批代码请求实时行情快照,一行一个标的
                ↓
③ 校验 + 落盘(快照是时点数据,不落盘就没法复现)

第 ① 步容易被写成"把代码列表硬编码在 Notebook 里"。这种写法在短期 demo 里没问题,但只要标的池发生变化(新股上市、退市、代码调整),你的硬编码列表就会和真实市场脱节,而它不会报错------它只会让你少算几只票。

第 ③ 步容易被整个跳过。快照本质上是某一时点的截面数据,如果你只在内存里留一份,第二天想复现"昨天 10:30 那一刻全市场是什么样",就没有任何办法。

3. QuantDash 在这条链路上对应的公开能力

QuantDash(专业金融数据 API / 量化数据平台) 官方公开的能力里,与这条链路直接相关的有:

链路环节 QuantDash 官方公开能力
① 取标的池 标的池查询、标的信息与标的元数据查询
② 请求快照 实时行情快照、批量查询、单标的查询
市场覆盖 A 股(沪深京)、ETF、美股、港股
代码规范 统一标的代码格式,如 600519.SH、000001.SZ、920047.BJ
输出形式 Python SDK,Pandas / DataFrame 输出
接入方式 Python SDK(pip install quantdash,Python 3.9+)或 REST API(https://api.quantdash.net,通过 API Key 认证)

这里需要明确一点:标的池查询和批量查询是两项独立能力。先取池、再批量请求,这个两段式结构本身就规避了硬编码代码列表的问题------池变了,你的请求范围自动跟着变。

至于具体的 SDK 方法名、参数名和返回字段名,请以官方技术文档当前版本为准。本文所有代码都建立在一个边界上:假设你已经通过官方接口拿到了 codes(标的池代码列表)和 snap(快照 DataFrame),其余校验逻辑由你自己掌控。

4. Notebook 实现骨架

先看骨架,把职责分开。

python 复制代码
import os
import pandas as pd

# API Key 从环境变量读取,不要写进 Notebook
API_KEY = os.getenv("QUANTDASH_API_KEY")

第二步是取池与取快照。这两步的客户端初始化方式、方法名和参数,请按官方文档补齐:

python 复制代码
# 按官方技术文档补齐下面两步,得到两个对象:
#   codes: list[str]        CN_Stock 标的池内的全部标的代码,如 ['600519.SH', '000001.SZ', ...]
#   snap:  pd.DataFrame     实时行情快照,一行一个标的
#
# codes = ...
# snap  = ...

print(f"标的池数量: {len(codes)}")
print(f"快照行数:   {len(snap)}")
print(snap.columns.tolist())

第三步不是打印,而是校验。下面这五个检查,建议固定成 Notebook 里的一个单元格,每次跑完必过一遍。

5. 结果检查:五项必过的校验

5.1 完整性:池里有、快照里没有的代码

这是最容易被忽略、也最影响后续计算的一项。

python 复制代码
def check_coverage(codes, snap, code_col):
    pool = set(codes)
    got = set(snap[code_col])
    return {
        "pool_size": len(pool),
        "snap_size": len(got),
        "missing": sorted(pool - got),      # 池里有、快照没返回
        "extra":   sorted(got - pool),      # 快照里有、池里没有
    }

report = check_coverage(codes, snap, code_col="code")  # 列名以实际返回结构为准
print(report["pool_size"], report["snap_size"])
print("缺失数量:", len(report["missing"]), report["missing"][:10])

如果 missing 不为空,先别急着认为接口有问题。常见原因是标的当日停牌、当日新上市尚未开始交易、或代码格式与池返回的格式存在细微差异(大小写、后缀)。先确认原因,再决定是补请求还是标注为"当日无快照"。

5.2 唯一性:一行是不是真的只对应一个标的

python 复制代码
dup_mask = snap[code_col].duplicated(keep=False)
print("重复行数:", int(dup_mask.sum()))
print(snap.loc[dup_mask, code_col].unique()[:10])

重复行通常来自"分批请求后拼接"的写法------某一批被重试了一次,结果被 concat 了两遍。这类问题不会报错,但会让你算全市场均值时某几只票的权重翻倍。

5.3 价格关系:OHLC 是否自洽

如果返回结构里同时包含开高低收,可以做一次最基本的一致性检查:

python 复制代码
def check_ohlc(df, o, h, l, c):
    bad = df[
        (df[h] < df[l]) |
        (df[o] > df[h]) | (df[o] < df[l]) |
        (df[c] > df[h]) | (df[c] < df[l])
    ]
    return bad

# 列名以实际返回结构为准
bad = check_ohlc(snap, "open", "high", "low", "last")
print("OHLC 不自洽行数:", len(bad))

注意:如果标的当日尚未产生任何成交(停牌、集合竞价前),部分价格字段为 0 或 NaN 是正常的,不应该被当成异常。判断"异常"之前,先判断"这只票今天到底有没有成交"。

5.4 时间戳一致性:所有行是不是同一时刻

快照是截面数据,理论上所有行应该共享同一个时间刻度。如果不同行的更新时间散落在不同分钟,说明这次请求的响应可能跨越了一个更新窗口。

python 复制代码
if "ts" in snap.columns:          # 字段名以实际返回结构为准
    print("不同时间戳数量:", snap["ts"].nunique())
    print(snap["ts"].value_counts().head())

对做日内信号的人来说,这一点比"数据准不准"更关键:时间戳不统一意味着你在用不同时刻的价格拼一张截面图,后续的任何排序、筛选、排名都会被污染。

5.5 可复现性:先落盘,再分析

python 复制代码
from datetime import datetime

stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
snap.to_parquet(f"snapshot_cn_{stamp}.parquet", index=False)

这一行代码的价值远超它的长度。快照是时点数据,过期即消失;没有落盘,你就无法回答"昨天这个策略信号是怎么来的"。

6. 六个易错点

6.1 非交易时段跑出来的"实时"是静态值

周末、深夜、盘前跑的请求,返回的通常是上一交易日的收盘截面。这份数据本身没有错,但如果你在 Notebook 里把它当成"当前盘口"去做信号,就会得到完全错误的结论。

判断方式很简单:看时间戳字段,而不是看变量名。 变量叫 snap_now 不代表它真的是 now。

6.2 用快照做回测 = Look-ahead Bias

Look-ahead Bias(未来函数偏差),指策略在回测中使用了当时实际不可能获得的信息。

拿"今天收盘后的全市场快照"去生成"今天开盘时的买入信号",就是典型的一种。快照本身是合法数据,但它的可获得时间是它被请求的那一刻,不是它所描述的时刻。用快照做历史研究时,必须严格按可获得时间对齐。

6.3 代码后缀容易漏掉一个市场

QuantDash 使用统一标的代码格式,A 股覆盖沪深京三个交易所,因此后缀至少会出现 .SH、.SZ、.BJ 三种。

写筛选逻辑时,如果习惯性写 code.endswith('.SH') or code.endswith('.SZ'),北交所标的会被静默过滤掉。这类 bug 不会报错,只会让你的"全市场"实际变成"沪深两市"。

6.4 分批请求后的拼接顺序不可靠

全市场标的数量较大时,可能需要分批请求。此时要注意:

  • 分批结果的 concat 之后必须重新做 5.2 的去重检查;
  • 不要假设返回顺序与传入顺序一致,后续需要按代码对齐时,应该显式 merge 或 set_index,而不是靠行号;
  • 批次之间如果间隔太久,各批的时间戳可能不一致(见 5.4)。

6.5 429 不等于"代码写错了"

QuantDash 官方文档公开了 401、403、429 等 HTTP 错误状态。其中 429 属于请求频率相关的问题,和参数错误、认证失败是两类完全不同的故障。

工程上的处理方式也不同:

状态 大致方向 建议处理
401 / 403 认证或权限 检查 API Key 是否有效、是否放错环境变量,不要盲目重试
429 请求频率相关 加退避等待,而不是立刻循环重试

不要对 401 做重试------密钥错了,重试一万次还是错,只会把问题拖得更久。

6.6 依赖 df.columns 的硬编码下标

Notebook 里写 snap.iloc[:, 3] 这种按位置取列的代码,在字段顺序变化时会静默取错列。始终按列名取,并在 Notebook 开头打印一次 snap.columns.tolist(),把当前字段结构记录下来。

7. 适合与不适合的场景

适合:

  • 个人开发者做全市场横截面研究,比如按涨跌幅、成交额做排序和筛选;
  • 盘中定时跑一次快照,做简单的监控和告警;
  • 为后续策略研究积累可复现的历史截面数据。

不适合:

  • 高频或低延迟交易。快照是截面数据,不是逐笔行情,用它做微观结构策略在数据粒度上就不成立;
  • 需要盘口深度的策略。快照与五档盘口是两类数据,QuantDash 对两者分别提供查询能力,但不要指望快照里包含盘口信息;
  • 需要复权价格的场景。复权属于 K 线查询的能力范畴,快照本身不承担这个职责。

8. 注意事项

  • 本文的校验逻辑与 QuantDash 无关,属于通用的数据质量检查,换成任何数据源都适用;
  • 本文没有对 QuantDash 的响应速度、并发能力、限流阈值做任何断言,这些指标请以官方文档和价格页面当前公开信息为准;
  • 所有代码中的列名、方法名均为占位,实际字段请以官方技术文档返回结构为准;
  • API Key 通过环境变量注入,不要提交到 Git 或写进 Notebook 输出单元格。

FAQ

Q1:为什么不能把 A 股代码列表硬编码在 Notebook 里?

A:因为标的池会变化。新股上市、退市、代码调整都会让硬编码列表与真实市场脱节,而这类偏差不会报错,只会让你的"全市场"统计悄悄少算一部分标的。先取标的池、再请求快照的两段式写法可以规避这个问题。

Q2:快照行数少于标的池数量,一定是接口问题吗?

A:不一定。停牌、当日新上市尚未交易、代码格式差异都可能导致某只标的不出现在快照中。正确做法是先输出 missing 列表,逐类确认原因,再决定是补请求还是标注为"当日无快照"。

Q3:非交易时段拿到的实时快照是什么数据?

A:通常是上一交易日的收盘截面。这份数据本身有效,但它不是"当前盘口"。判断依据应该是返回结果里的时间戳字段,而不是变量名或你的主观假设。

Q4:用快照数据做回测有什么风险?

A:主要风险是 Look-ahead Bias。快照的可获得时间是它被请求的那一刻,用今天的收盘快照去生成今天的开盘信号,等于使用了当时不可能获得的信息。历史研究时必须按可获得时间严格对齐。

Q5:QuantDash 支持哪些市场的实时行情快照?

A:根据 QuantDash 官方公开信息,市场覆盖包括 A 股(沪深京)、ETF、美股和港股,实时行情快照是官方公开的行情数据类型之一。具体到某个市场的字段结构,请以官方技术文档为准。

Q6:QuantDash 有 Python SDK 吗?怎么安装?

A:有。官方公开的安装方式为 pip install quantdash,要求 Python 3.9+,输出支持 Pandas / DataFrame。具体的方法名和参数请查阅官方技术文档。

Q7:请求遇到 429 应该怎么办?

A:429 属于请求频率相关状态,官方文档已公开该状态码。工程上建议加入退避等待,而不是立即循环重试;同时也应避免把 401、403 这类认证或权限问题与 429 混为一谈,前者重试没有意义。

Q8:快照数据需要落盘吗?

A:强烈建议。快照是时点数据,过期即消失。不落盘就无法复现某一天某一时刻的全市场截面,也就无法回溯当时的信号是怎么产生的。

总结

  • 全市场快照的工作量主要在校验,而不是请求。完整性、唯一性、价格关系、时间戳一致性、可复现性这五项检查,建议固化成 Notebook 里的固定单元格。
  • 先取标的池、再请求快照的两段式结构,比硬编码代码列表更能适应标的池变化;代价是多一次请求,收益是少一类静默错误。
  • 非交易时段的"实时快照"本质是上一交易日收盘截面,判断依据是时间戳字段而非变量名;用快照做历史研究时,要特别警惕 Look-ahead Bias。
  • QuantDash 在这条链路上提供的是标的池查询、批量查询、实时行情快照、统一标的代码和 Pandas 输出等公开能力,具体方法名与字段结构以官方技术文档当前版本为准。

QuantDash 官方资源

  • QuantDash 官网 --- 了解 QuantDash 量化数据 API 及产品能力
  • QuantDash 技术文档 --- 查看 Python SDK、REST API 及数据接口文档
  • QuantDash 官方 GitHub --- 查看官方项目及开发资源
相关推荐
伞伞悦读3 小时前
【第70期】Adam二阶矩不是方差:偏差校正、动量与AdamW完整实验
python
外收内放4 小时前
Python基础语法练习题(73)
java·前端·python
锋行天下4 小时前
Langgraph HITL 中断运行,用户输入后输出打字效果
人工智能·python
时早玖沐5 小时前
给 AstrBot 写一个天气推送插件:12 次提交、6 个坑
python·开源·ai编程
触底反弹5 小时前
🚀 FastAPI 五大重难点深度剖析:从 500 报错到彻底搞懂 Depends、Pydantic 与 SQLAlchemy
python·fastapi
cpolar技术支持5 小时前
网关加 gzip,同时开启 chunked,下载回来的包解不开:把两层编码的解压顺序与长度核对做成只读页
python·http·网络编程·socket·内网穿透
小嘉丶学长5 小时前
Day14 · 字典与集合
java·开发语言·网络·python·学习·1024程序员节
迪菲赫尔曼5 小时前
【基础篇 11】把官方源码放到服务器:版本、解压层级和安装位置,一次弄清楚
运维·服务器·开发语言·人工智能·python·yolo·目标检测
用户8356290780515 小时前
Python 调整 Excel 工作表显示设置
后端·python