一句话回答
批量行情请求成功,只能说明请求流程没有报告整体失败,并不意味着研究股票池中的每只股票都有可用数据。部分股票可能返回空数据、缺少关键字段,或者实际返回的标的集合与请求集合不一致。在量化研究中,需要把接口执行状态与数据覆盖率分开验证。可以使用 QuantDash Python SDK 批量获取历史 K 线,再通过 Python 对请求股票池和实际数据进行逐项核对。
为什么会有这个问题 / 传统做法有什么问题?
假设我们正在开发一个覆盖 300 只股票的量化选股策略,每天需要获取最近 20 根日 K 线,并计算短期动量指标。
传统实现通常比较简单:
- 准备股票代码列表。
- 执行批量行情请求。
- 判断请求是否抛出异常。
- 如果没有异常,就将返回数据交给因子计算模块。
问题出在第三步。
请求执行成功和数据集合完整,是两个不同层次的判断。
从工程角度看,至少存在三种状态:
- 请求层成功:SDK 调用正常结束,没有抛出整体异常。
- 标的层覆盖:请求的每个股票代码都能在返回数据中找到。
- 记录层可用:对应股票的行情记录满足后续计算要求。
假设研究股票池包含 300 只股票,其中只有 285 只获得可识别的行情记录,那么标的覆盖率为:
[
覆盖率=\frac{285}{300}\times100%=95%
]
这里的数字只是用于说明计算方法的假设,不代表 QuantDash 的实际接口表现。
95% 的覆盖率并不能直接说明这批数据可以使用。剩余 15 只股票为什么缺失、是否集中在某个交易所、是否会影响策略结果,都需要进一步检查。
如果直接计算因子,还可能出现一个不容易发现的问题:缺失股票被静默排除,实际参与研究的股票池发生变化。
例如,策略原本希望在完整股票池中选出动量排名前 20 的股票,但程序直接对已有行情执行排序。最终得到的结果实际上是“在有数据的股票中选出前 20”,与原始研究目标并不完全相同。
另外,也不能将所有数据缺口都归因于接口故障。停牌、新上市、标的代码配置错误、查询时间范围不合适,都可能导致数据不满足研究要求。
因此,批量行情接入应当增加独立的数据覆盖验证,而不是只检查函数是否执行成功。
分步骤解决:使用 QuantDash 进行实践
第 1 步:安装 SDK 并初始化客户端
首先安装 Python SDK:
# 在终端执行
# pip install quantdash
实际安装命令为 pip install quantdash。
初始化客户端时,使用环境变量读取 API Key,避免把认证信息写入研究代码。
import os
from quantdash import QuantDash
api_key = os.getenv("QUANTDASH_API_KEY")
if not api_key:
raise RuntimeError(
"请先设置 QUANTDASH_API_KEY 环境变量"
)
qd = QuantDash(api_key=api_key)
这里的 qd 是后续行情请求使用的客户端对象。
API Key 是否具备相应的数据访问权限,还需要通过实际请求验证。
第 2 步:获取批量历史 K 线
本例使用 qd.klines.batch() 获取两只 A 股的历史日 K 线。
import pandas as pd
requested_symbols = [
"600519.SH",
"000001.SZ",
]
try:
batch_df = qd.klines.batch(
requested_symbols,
period="1d",
count=20,
to_dataframe=True,
show_progress=True,
)
except Exception as exc:
raise RuntimeError(
"批量行情请求执行失败"
) from exc
if not isinstance(batch_df, pd.DataFrame):
raise TypeError(
"预期返回 DataFrame,请核对实际接口返回类型"
)
if batch_df.empty:
raise ValueError(
"批量请求未返回任何行情记录"
)
print("实际返回字段:", batch_df.columns.tolist())
print("返回记录数:", len(batch_df))
几个关键参数分别承担不同职责:
- period=“1d”:请求日 K 线。
- count=20:指定请求的 K 线数量,不应直接理解为每只股票必然返回 20 条有效记录。
- to_dataframe=True:请求使用 DataFrame 表示返回数据。
- show_progress=True:显示请求进度,但进度完成不能替代数据完整性验证。
这里有一个必须说明的字段问题。
目前不能仅凭批量调用示例,确定不同 SDK 版本返回的 DataFrame 一定使用 symbol 作为股票代码列,也不能断言每条记录都采用相同的索引组织方式。
所以下一步需要先确认返回结构,再配置字段映射。
第 3 步:检查股票池覆盖率
这一阶段先不计算因子,而是回答三个问题:
- 请求了哪些股票?
- 实际识别到了哪些股票?
- 哪些股票缺失?
下面的示例假设实际返回数据包含一列股票代码。运行前,需要根据第二步打印的字段列表,确认并配置 SYMBOL_COLUMN。
# 以下字段映射必须根据实际返回结构确认
SYMBOL_COLUMN = "symbol"
if SYMBOL_COLUMN not in batch_df.columns:
raise KeyError(
f"缺少标的代码列 {SYMBOL_COLUMN},"
"请检查实际返回字段或索引结构"
)
requested_set = set(requested_symbols)
symbol_series = (
batch_df[SYMBOL_COLUMN]
.dropna()
.astype(str)
.str.strip()
)
returned_set = set(symbol_series)
covered_set = requested_set & returned_set
missing_symbols = sorted(
requested_set - covered_set
)
unexpected_symbols = sorted(
returned_set - requested_set
)
coverage_rate = (
len(covered_set) / len(requested_set)
if requested_set else 0.0
)
print("请求标的数:", len(requested_set))
print("已覆盖标的数:", len(covered_set))
print("标的覆盖率:", f"{coverage_rate:.2%}")
print("缺失标的:", missing_symbols)
print("非预期标的:", unexpected_symbols)
这段代码的重点不是计算百分比,而是保留两个集合:
missing_symbols 表示请求范围内没有识别到记录的股票。
unexpected_symbols 表示返回数据中出现、但不属于本次请求集合的代码。
实际开发中,还应检查代码格式是否统一。例如,不能随意把交易所后缀删除后再比较,否则不同市场可能出现代码识别歧义。
需要注意,覆盖率只检查“是否存在记录”,还没有判断记录数量、价格有效性和时间连续性。因此,它是第一道检查,而不是最终的数据质量结论。
第 4 步:生成逐只股票的数据覆盖报告
覆盖率可以反映整体情况,但数据修复和排查通常需要落实到具体股票。
下面进一步统计每只股票的记录数,并生成一份可供研究程序读取的覆盖报告。
record_counts = (
symbol_series
.value_counts()
.to_dict()
)
report_rows = []
for symbol in requested_symbols:
count = int(record_counts.get(symbol, 0))
report_rows.append({
"symbol": symbol,
"record_count": count,
"has_records": count > 0,
"meets_count_target": count >= 20,
})
coverage_report = pd.DataFrame(report_rows)
if coverage_report["symbol"].duplicated().any():
raise ValueError(
"请求股票池存在重复代码,请先去重"
)
print(coverage_report.to_string(index=False))
coverage_report.to_csv(
"stock_pool_coverage.csv",
index=False,
encoding="utf-8-sig",
)
报告区分了两个概念:
has_records 判断股票是否存在行情记录。
meets_count_target 判断股票是否至少有 20 条记录。
不过,满足数量要求仍不意味着拥有 20 个不同交易日的有效行情。重复记录、无效价格、日期错位等情况,还需要单独检查。
同样,对于上市时间不足、查询区间内停牌或历史记录有限的股票,记录数不足不应自动解释为接口请求失败。
在工程系统中,这份报告可以与后续的数据清洗结果关联起来,形成逐只股票的质量状态记录。
关键坑与注意事项
- 批量接口没有抛异常,不等于每个标的都成功。 应当建立请求股票池与实际返回股票集合之间的对账逻辑,不能仅以异常状态判断数据可用性。
- 空 DataFrame 和部分标的缺失不是一回事。 整体为空可以立即中止处理,部分缺失则需要输出具体股票代码并判断对研究任务的影响。
- 股票代码字段不能凭经验假定。 应先检查实际返回的列名和索引结构,再建立标的代码映射,避免误把行情记录归属给其他股票。
- 记录数达标不等于行情完整。 同一天的重复 K 线也会增加记录数,应结合真实日期字段进一步检查唯一性。
- 缺失股票不能直接从研究池中删除。 如果研究任务要求固定股票池,应记录缺失原因,并采用明确的排除或暂停计算规则。
- 覆盖率应当与请求参数一起保存。 记录股票池版本、请求时间范围、K 线周期和实际返回情况,便于后续复核研究结果。
常见问题(FAQ)
Q:批量行情请求没有报错,为什么还需要检查每只股票?
A:请求状态主要反映调用流程是否正常结束,而策略需要的是逐只股票可用于计算的数据。两者的验证对象不同。
Q:覆盖率达到 100%,是否可以立即计算因子?
A:不能。覆盖率只表示所有请求标的都有可识别记录,还需要检查日期、价格、记录数量和计算窗口是否满足要求。
Q:为什么不直接检查返回 DataFrame 的总行数?
A:总行数无法反映各股票的数据分布。某只股票记录重复、另一只股票完全缺失时,总行数仍可能达到预期。
Q:如何判断缺失是代码错误还是正常无交易记录?
A:可以结合查询时间、交易日历、标的上市状态以及元数据进行排查。QuantDash 提供 qd.instruments.get() 接口,但具体元数据字段和可用范围需要以实际返回结果为准。
Q:QuantDash 会自动保证批量结果中的每只股票都有数据吗?
A:不能根据批量调用示例作出这一保证。SDK 用于请求行情数据,逐标的覆盖检查和策略准入判断应由研究程序自行实现。
小结
批量行情请求成功,只是完成了数据接入流程中的一个步骤。真正影响量化研究结果的是:原始股票池中的每只股票是否获得了足够、正确且符合研究要求的行情记录。如果忽略覆盖率检查,缺失标的可能在因子计算前被静默排除,使实际研究样本偏离预期。
使用 QuantDash Python SDK 可以通过统一接口获取批量历史 K 线,减少重复编写行情接入代码的工作量。但股票池对账、缺失记录识别、异常分类和数据质量报告仍然需要由业务系统负责。将这些检查放在因子计算之前,能够让数据缺口更容易定位,也有助于保证选股研究结果的可靠性与可复现性。
参考
QuantDash Python SDK 文档:https://docs.quantdash.net/zh-Hans/sdk/python-quickstart
1605



被折叠的 条评论
为什么被折叠?



