4. 数据清洗与预处理流水线搭建
板块轮动周报与月报的结论是否可信,很大程度上取决于进入计算环节的数据是否干净、口径是否一致。板块轮动数据有三个鲜明特点:一是多源异构,行情来自交易接口、成分股来自指数公司、估值来自财务库,字段命名与时间戳格式各不相同;二是存在停牌、涨跌停、新股纳入、成分调整等结构性断点;三是复权口径不统一会让区间收益完全失真。因此,本节要搭建的不是零散的清洗脚本,而是一条可复用、可配置、可追溯的预处理流水线。
流水线的整体结构
建议把流水线拆成五个串行阶段,每个阶段只做一件事,并统一输出“宽表 + 质量日志”两份产物。宽表以“交易日期 × 板块代码”为索引,列为各板块的收盘价、复权因子、成交额、估值等字段;质量日志记录每个阶段处理的记录数、被修正或被剔除的样本及原因。这样后续周报、月报只依赖宽表,排障时只需查日志。
时间序列对齐:先统一“尺子”
对齐的第一步是确定基准日历。以 A 股为例,应以交易所发布的交易日历为准,而不是自然日。具体做法是:把各数据源的日期列统一转换为 YYYY-MM-DD 字符串或日期类型,剔除周末与节假日记录,再以基准日历为左表做外连接。对于板块指数在某个交易日无记录的情况,先区分是“非交易日”还是“停牌/数据缺失”,前者直接丢弃,后者保留占位并标记。
对齐时还要注意两点:一是估值、财务类数据往往是低频的(季度或月度),需要按“报告期 + 公告日”映射到交易日,避免未来函数;二是不同板块的上市起始日不同,早期数据天然缺失,不能简单用 0 填充,应保留 NaN 并在计算区间收益时按有效样本处理。
复权调整:让区间收益可比
板块指数本身通常已做修正,但若用成分股合成或使用 ETF 价格,就必须处理分红送转。原则是:计算历史区间收益用后复权,展示当前价格用前复权。实现上,读取每个板块的复权因子序列,用“原始价 × 复权因子”得到后复权价,再计算日收益率 r_t = P_t / P_{t-1} - 1。若某板块在区间内发生成分调整,应在调整日重置拼接基准,避免新旧成分价格直接相除产生虚假跳变。
缺失值处理:区分“没交易”和“没数据”
缺失值不能一刀切。停牌导致的缺失,价格应前向填充(保持停牌前收盘价),成交额填 0,并在日志中记录停牌天数;数据源漏采导致的缺失,若缺口不超过 2 个交易日可用线性插值,超过则标记为无效区间,不参与该周期的轮动打分。新股纳入板块时,纳入日之前的数据应视为不存在,而不是补 0,否则会把“从无到有”误判为暴涨。
异常值检测:规则 + 统计双保险
建议先用业务规则兜底:单日涨跌幅超过板块涨跌停限制(如 ±10%,ST 板块 ±5%)的记录直接标记;成交额为负或为 0 但价格发生变化的记录标记为可疑。再用统计方法补充:对日收益率序列计算中位数与 MAD(绝对中位差),把 |r - median| > 5 × 1.4826 × MAD 的点视为异常。被标记的样本不直接删除,而是置为缺失并进入人工复核队列,避免误杀真实的极端行情。
# 示例:基于 MAD 的异常标记(参数为教学假设值)
import numpy as np
import pandas as pd
def flag_outliers(ret: pd.Series, k: float = 5.0) -> pd.Series:
med = ret.median()
mad = (ret - med).abs().median()
scale = 1.4826 * mad
if scale == 0:
return pd.Series(False, index=ret.index)
return (ret - med).abs() > k * scale
# 对每个板块分别标记,保留原始值供复核
ret = wide["close_adj"].pct_change()
mask = ret.groupby(level="sector").transform(flag_outliers)
wide["ret_flag"] = mask
把流水线做成可复用组件
可复用的关键在于“配置与逻辑分离”。把交易日历路径、复权方式、插值上限、MAD 倍数、涨跌停阈值等写入一份 YAML 配置,流水线函数只读取配置执行。每次运行输出带时间戳的宽表与日志文件,并在日志头部记录配置快照,这样周报与月报使用同一份清洗结果,口径可追溯、结果可复现。下表给出建议的配置字段示例(数值为教学假设)。
| 配置项 | 示例值 | 作用 |
|---|---|---|
| calendar_source | exchange_calendar.csv | 基准交易日历 |
| adjust_method | backward | 区间收益采用后复权 |
| max_interp_gap | 2 | 允许插值的最大连续缺口(交易日) |
| mad_k | 5.0 | 异常判定倍数 |
| price_limit | 0.10 | 常规板块单日涨跌幅阈值 |
| suspend_fill | ffill | 停牌价格填充方式 |
完成上述搭建后,后续的板块动量计算、轮动打分与周报月报生成都只需读取标准化宽表,数据口径问题被收敛在流水线内部,这正是可复用预处理的价值所在。