4. 数据获取与原始数据探查
在真正开始构建交易日历与停复牌数据表之前,第一步不是写复杂的清洗逻辑,而是把原始数据“拿进来、看清楚”。这一步做扎实,后续的日期对齐、状态判定和回测撮合才有可靠基础。本节围绕三个动作展开:从常见数据源读取数据、做初步质量检查、完成字段映射,并给出可直接参考的实现方式与判断依据。
一、常见数据源与读取方式
交易日历与停复牌数据在实际工程中通常来自三类来源。第一类是交易所或官方披露文件,例如上交所、深交所网站提供的交易日历与停复牌公告,权威性最高,但格式多为网页或 PDF,需要解析。第二类是商业数据终端或数据接口,例如 Wind、Choice、聚源,以及 Tushare、AkShare 等开源数据接口,字段规范、更新及时,适合批量获取。第三类是本地已有文件,例如券商提供的 CSV/Excel 导出、历史归档的数据库表,常见于机构内部环境。
不同来源的读取方式差异明显。以开源接口为例,交易日历通常一次返回全年日期与是否交易标志;停复牌记录则按日期区间或按股票代码查询,返回停牌开始日、停牌结束日、停牌原因等字段。读取时要特别注意两点:一是接口的日期参数是“交易日”还是“自然日”,二是返回结果是否包含当日盘中临时停牌。下面给出一个基于常见开源接口的读取示例,参数与返回结构为教学示例,实际字段以所用接口文档为准。
import pandas as pd
import akshare as ak
# 读取交易日历(示例:返回 trade_date 与 is_open 两列)
cal_raw = ak.tool_trade_date_hist_sina()
cal_raw["trade_date"] = pd.to_datetime(cal_raw["trade_date"])
# 读取停复牌记录(示例:按日期区间查询)
susp_raw = ak.stock_tfp_em(date="20240102")
print(cal_raw.head())
print(susp_raw.columns.tolist())
读取完成后不要急着合并,先把原始数据单独保存一份快照,例如 cal_raw_20240102.parquet 与 susp_raw_20240102.csv。保留原始快照的意义在于:后续字段映射或清洗规则调整时,可以随时回溯,避免反复请求接口导致结果不一致。
二、初步质量检查:先看“形状”再看“内容”
质量检查的目标不是一次性解决所有问题,而是快速识别会影响后续处理的风险点。建议按“形状—内容—一致性”三层推进。
形状检查关注行数、列数、日期范围与缺失比例。例如交易日历应覆盖目标年份的完整自然日区间,若某年只有 200 多行且缺少周末记录,说明该数据源只返回交易日,需要在后续补齐自然日序列。停复牌记录则要检查是否为空表、是否只有单日快照。
内容检查关注关键字段的取值分布。例如停牌开始日是否早于停牌结束日、是否存在结束日为空(表示仍在停牌)、停牌原因字段是否有大量空值或异常编码。可以用下面的方式快速统计:
# 形状与缺失
print(cal_raw.shape, cal_raw.isna().mean())
print(susp_raw.shape, susp_raw.isna().mean())
# 日期逻辑检查:开始日不应晚于结束日
bad = susp_raw[susp_raw["停牌开始日"] > susp_raw["停牌结束日"]]
print("日期倒置记录数:", len(bad))
# 仍在停牌的记录(结束日为空)
print("未复牌记录数:", susp_raw["停牌结束日"].isna().sum())
一致性检查关注跨源对齐。例如停复牌记录中的日期是否都落在交易日历标记为交易日的范围内;若出现停牌开始日为非交易日,需要判断是数据源按自然日记录,还是存在录入偏差。这一步的结论会直接影响后续字段映射时日期字段的语义定义。
三、字段映射:把“别人的字段”变成“自己的字段”
不同数据源对同一概念的命名往往不同。交易日历可能叫 trade_date、日期、cal_date;停复牌可能叫 停牌开始日、suspend_date、start_date。字段映射的本质是建立一张“源字段—标准字段—转换规则”的对照表,让后续所有处理只依赖标准字段。
建议先定义一套最小标准字段集。交易日历至少需要:cal_date(日期)、is_trading_day(是否交易日)。停复牌至少需要:symbol(证券代码)、suspend_start(停牌开始日)、suspend_end(停牌结束日,空值表示未复牌)、suspend_type(停牌类型或原因)。映射时同步完成类型转换:日期统一转为 datetime,布尔标志统一为 0/1 或 True/False,证券代码统一为带交易所后缀的字符串,避免前导零丢失。
下面是一个映射与类型统一的示例,字段名与转换规则为教学示例:
cal = cal_raw.rename(columns={
"trade_date": "cal_date"
}).assign(
is_trading_day=lambda d: d["cal_date"].dt.dayofweek.lt(5).astype(int)
)
susp = susp_raw.rename(columns={
"代码": "symbol",
"停牌开始日": "suspend_start",
"停牌结束日": "suspend_end",
"停牌原因": "suspend_type"
})
susp["symbol"] = susp["symbol"].astype(str).str.zfill(6)
susp["suspend_start"] = pd.to_datetime(susp["suspend_start"])
susp["suspend_end"] = pd.to_datetime(susp["suspend_end"])
映射完成后要做一次“回读验证”:随机抽取若干条原始记录,逐字段核对映射后的值是否与源数据语义一致,尤其是日期字段和代码字段。若源数据按自然日记录停牌区间,而交易日历只保留交易日,则需要在映射阶段明确标注这一差异,并在后续展开区间时按交易日历过滤,而不是在映射阶段强行对齐。
四、从原始数据到可用表的判断依据
完成读取、检查与映射后,可以用几条可观察的判据判断是否达到“可进入下一步”的状态:交易日历覆盖目标年份全部自然日且交易日标志与官方日历一致;停复牌记录的关键字段缺失率在可接受范围内,日期倒置记录已清零或已单独标记;标准字段命名统一、类型正确;原始快照与映射结果均可追溯。满足这些条件,才适合进入停牌区间展开、状态标记与交易日对齐等后续处理。
需要强调的是,本节的质量检查是“初步”的,它不追求把所有异常都消灭,而是把风险显性化并记录下来。字段映射也不是一次性的重命名,而是对数据语义的正式约定。把这两件事做清楚,后续的停牌区间展开、交易日对齐和状态标记才有稳定的输入,整个交易日历与停复牌数据处理链路才具备可复现、可审计的基础。