python
stock_names = {
"600519.SH": "贵州茅台",
"000001.SZ": "平安银行",
}
这种写法在只有两三只股票时没有问题,但当股票池扩大到数百只、涉及多个市场,或者需要定期更新时,手动维护映射表就会成为数据工程中的隐患。
真正需要解决的,不只是如何获得股票名称,而是如何让股票名称、交易所信息和证券代码之间保持一致,并且在标的发生变化时能够及时更新。
为什么手写字典迟早会遇到问题?
先看三个常见场景。
场景一:股票池不断扩大。
研究员每天调整股票池,新增的代码需要手动补充名称。如果某个代码没有及时录入,程序可能只能展示代码,无法生成完整报告。
场景二:同一套程序处理多个市场。
A 股、美股和港股使用的代码规则并不完全相同。如果只保存代码和名称,而不记录市场信息,就容易在数据合并、报表生成或跨市场研究时产生歧义。
场景三:证券信息发生变化。
证券名称可能因为公司更名而调整,标的的上市状态也可能变化。如果名称被写死在程序中,历史代码和当前代码之间的关系就很难管理。
因此,手写字典更适合固定、极小规模的示例,而不适合作为持续运行的量化系统中的唯一元数据来源。
把证券元数据当作一张独立的数据表
更稳妥的设计是维护一张标的元数据表。最基本的字段可以包括:
| 字段 | 用途 |
|---|---|
symbol |
证券标识符,关联行情与基础信息 |
name |
股票名称,用于展示 |
exchange |
交易所或交易场所标识 |
updated_at |
本地记录的更新时间 |
source |
元数据来源,便于追踪和排查 |
其中,updated_at 和 source 可以由本地系统添加,并不意味着数据供应商一定会返回这两个字段。
还要区分两个概念:数据获取时间和元数据实际生效时间并不相同。 今天查询到一条名称记录,只能说明数据源在当前查询时返回了这个值,不能据此认定该名称从今天才开始生效。
如果研究需要重建历史时点的股票池,就不能只保存一份覆盖旧记录的最新快照,而应根据需求保留历史版本或有效时间区间。
Python 实现:将代码列表与元数据关联
先准备需要查询的股票列表:
python
import pandas as pd
universe = pd.DataFrame({
"symbol": [
"600519.SH",
"000001.SZ",
"AAPL.US",
"00700.HK",
]
})
universe["symbol"] = (
universe["symbol"]
.astype("string")
.str.strip()
)
universe = universe.drop_duplicates(subset=["symbol"])
这里的代码仅用于展示多市场股票池的数据处理流程。正式查询前,仍需确认这些标的代码符合所选数据源的格式规范。
下一步是通过实际的数据接口获取元数据。假设查询结果已经被转换为统一的 DataFrame,包含 symbol、name 和 exchange 三列:
python
# metadata 应替换为实际数据接口返回并整理后的结果。
metadata = pd.DataFrame([
{
"symbol": "600519.SH",
"name": "贵州茅台",
"exchange": "SSE",
},
{
"symbol": "000001.SZ",
"name": "平安银行",
"exchange": "SZSE",
},
])
result = universe.merge(
metadata,
on="symbol",
how="left",
validate="one_to_one",
indicator=True,
)
print(result)
这个示例使用两条演示记录说明关联方式,并非真实查询结果。
这里有三个值得注意的细节。
第一,how="left" 可以保留原始股票池中的所有代码。即使某个标的没有匹配到元数据,也不会直接从结果中消失。
第二,validate="one_to_one" 用于检查关联双方是否满足一对一关系。如果元数据中存在重复的 symbol,程序会抛出异常,而不是悄悄生成重复行。
第三,indicator=True 会增加 _merge 字段,帮助识别哪些代码成功匹配、哪些没有匹配到元数据。
可以进一步检查缺失记录:
python
unmatched = result.loc[
result["_merge"] == "left_only",
"symbol",
]
if not unmatched.empty:
print("以下标的缺少元数据:")
print(unmatched.tolist())
这比直接删除缺失记录更适合量化数据管道,因为它保留了排查问题所需的信息。
选择数据源时,重点看批量查询与字段定义
如果项目只处理一只股票,单标的查询已经足够。但对于每天更新的股票池,更应该关注数据源是否支持批量查询,以及返回的数据能否稳定映射到内部数据模型。
QuantDash 提供标的信息及相关元数据,并支持批量查询场景。其 Python SDK 可以通过以下命令安装:
bash
pip install quantdash
SDK 支持 Python 3.9 及以上版本。需要注意,安装完成并不意味着可以在没有认证和配置的情况下直接调用数据接口。具体初始化参数、认证要求、批量查询方法以及返回字段,都应以官方技术文档为准。
在实际工程中,建议在数据源与业务代码之间增加一个适配层。例如,内部统一输出:
text
symbol | name | exchange
无论底层使用哪一种接口,后续的股票池构建、行情关联和报表生成都只需要读取这套内部字段。
这样做的价值在于:如果未来更换数据源,通常只需要调整适配层,而不必修改所有使用股票名称的研究脚本。
一个经常被忽视的问题:交易所信息应该如何保存?
交易所字段看似简单,实际上需要明确它的语义。
例如,某个数据源可能返回交易所缩写,另一个数据源可能返回完整名称,还有的数据源可能返回内部市场代码。它们都可能表示相近的信息,但不一定可以直接混用。
建议为内部数据模型定义统一规则,例如:
exchange保存标准化后的交易所代码。exchange_name保存面向用户展示的名称。source_exchange在必要时保留数据源原始值。
这里的字段设计是应用程序自己的数据建模方案,不是对任何特定 API 返回字段的描述。
此外,股票代码后缀可以作为辅助校验依据,但不应被当作唯一的数据来源。对于跨市场标的、特殊证券类型或供应商采用不同代码规则的情况,最好直接使用数据源提供的市场信息。
什么时候需要缓存?
股票名称和交易所信息通常不会像实时行情那样频繁变化,因此没有必要在每次计算最新价格时都重新获取完整的元数据。
可以根据实际需求采用不同的更新策略:
- 对临时分析脚本,在一次运行期间缓存查询结果即可。
- 对每日运行的研究任务,可以定期刷新元数据,并记录更新时间。
- 对需要历史复现的研究系统,应保存历史版本,而不只是覆盖当前记录。
缓存周期不应随意设定为固定值。它取决于数据源的更新机制、业务对时效性的要求,以及项目是否需要重建历史状态。
如果发现某只股票的名称或交易所信息发生变化,首先应确认变化来自真实的证券信息更新,还是代码映射、数据源字段或内部标准化逻辑发生了改变。
将这几种情况区分开来,远比在字典中补上一条新记录更重要。
QuantDash 官方文档
- QuantDash 技术文档 --- 查看 Python SDK、REST API 和数据接口说明。