2. 行业收益率数据获取与清洗

行业间相关性研究的第一步,不是算相关系数,而是把“每个行业每天涨跌多少”这件事变成一张干净、对齐、可复用的面板数据。面板的质量直接决定后续相关性矩阵、分散度指标和滚动窗口分析是否可信。本节围绕行业指数选择、数据源对接、复权处理、缺失值与异常值处理四个环节,给出可落地的操作路径,并最终产出一份结构统一的行业日收益率面板。

一、行业指数选择:先定义“行业”的口径

行业收益率面板的第一决策是行业划分标准。常见口径包括申万一级行业、中信一级行业、证监会行业以及自定义主题行业。对相关性研究而言,应优先选择分类稳定、成分调整频率可控、历史长度足够的指数体系。以申万一级行业为例,31个行业覆盖A股全市场,行业指数以自由流通市值加权,适合做行业间相对强弱与相关性分析。若研究聚焦某一产业链,也可用主题指数替代,但需注意主题指数成分重叠度高,会人为抬高相关性。

选择时需明确三个参数:行业数量(决定面板宽度)、指数代码(决定数据源对接键)、基期与发布日(决定可用历史区间)。建议在面板元数据中固定记录这三项,避免后续换口径导致结果不可比。

二、数据源对接:统一字段与频率

行业日行情可从Wind、Choice、聚宽、Tushare等渠道获取。无论来源如何,对接时应统一为“长表”结构:每行一条记录,字段至少包含trade_date、industry_code、industry_name、open/high/low/close、volume、amount,以及复权因子adj_factor。长表便于按日期和行业双向索引,也便于后续透视成宽表计算收益率。

对接时最容易出错的是交易日历不一致。不同数据源的节假日安排、停市处理可能不同,必须以交易所官方交易日历为准做一次对齐。示例中假设使用2018-01-01至2024-12-31的日频数据,共约1700个交易日、31个行业,原始长表约5.3万行。这个规模用Pandas即可流畅处理。

三、复权处理:让收益率可比

行业指数通常已是不含分红再投资的价格指数,但若使用含分红全收益指数或个股聚合数据,就必须处理复权。复权分前复权与后复权:前复权以当前价格为基准回溯调整,适合看近期形态;后复权以历史某点为基准向前调整,适合计算长周期收益率。相关性研究关注的是收益率序列,因此应统一采用后复权价格计算日收益率,避免前复权在每次除权后导致历史收益率被“重写”。

日收益率计算采用简单收益率:r_t = P_t / P_{t-1} - 1,其中P为后复权收盘价。若数据源只提供复权因子,则先计算复权价P_adj = close × adj_factor,再求收益率。注意首个交易日无前值,收益率记为缺失而非0,否则会污染后续统计。

四、缺失值与异常值处理

缺失值主要来自停牌、数据源断点和新上市行业。处理原则是“先对齐、再判断、后填补”:先按交易日历与行业代码做笛卡尔积对齐,暴露真实缺失;再区分整段缺失(行业尚未发布)与零星缺失(停牌或断点)。对零星缺失,若连续缺失不超过2个交易日,可用前值填充价格后再算收益率(等价于该日收益率为0);若超过阈值,应标记为缺失并在相关性计算时成对剔除,而不是强行插值。

异常值通常表现为单日涨跌幅绝对值远超合理范围。行业指数单日涨跌超过±11%(考虑涨跌停与指数平滑)即应核查。常见原因是复权因子错配、数据源单位错误(如把“点”当成“元”)或除权日未调整。处理时先定位原始记录,核对复权因子与相邻交易日价格连续性;确认是数据错误则修正或置为缺失,确认是真实极端行情则保留,因为极端值本身是相关性研究的重要信息,不应随意 winsorize。

行业日收益率面板:从原始行情到可复用宽表 ① 行业指数选择 申万一级 31 个行业 固定代码 / 基期 / 发布日 口径写入元数据 ② 数据源对接 长表字段统一 交易日历对齐 约 5.3 万行原始记录 ③ 复权与收益率 后复权价 P_adj r_t = P_t / P_{t-1} − 1 首日收益记缺失 ④ 缺失与异常 ≤2 日缺失前值填充 长段缺失成对剔除 ±11% 阈值核查 原始长表(节选) trade_date industry_code close adj_factor 2024-01-02 801010.SI 3120.45 1.0000 2024-01-02 801020.SI 2876.10 1.0000 2024-01-03 801010.SI 3098.72 1.0000 2024-01-03 801020.SI 2901.33 1.0000 … 共 31 行业 × 约 1700 交易日 目标宽表:行业日收益率面板 trade_date 农林牧渔 基础化工 电子 2024-01-02 — — — 2024-01-03 -0.00696 0.00876 -0.00312 2024-01-04 0.00421 -0.00115 0.01207 2024-01-05 0.00000 0.00334 -0.00558 首日无前值 → 缺失;停牌日 → 前值填充后收益为 0 面板验收检查 ① 行数 = 交易日数 × 行业数 ② 首日收益全为缺失 ③ 单日收益绝对值 > 11% 的记录已逐条核查 ④ 长段缺失已标记而非插值 ⑤ 列名与行业代码一一对应

五、面板成型与验收

完成上述步骤后,将长表按trade_date为行、industry_code为列透视,即得到宽表形式的行业日收益率面板。面板应满足五项验收条件:行数等于交易日数乘以行业数;首日收益率全部为缺失;单日收益绝对值超过11%的记录已逐条核查并留下处理说明;连续缺失超过2个交易日的区段已标记为缺失而非插值;列名与行业代码一一对应且顺序固定。

下面给出一个可复用的清洗骨架,示例参数为31个行业、2018至2024年日频数据,实际运行时替换数据源即可:

import pandas as pd

# 1. 读取长表并统一字段
raw = pd.read_csv("industry_daily.csv", parse_dates=["trade_date"])
raw = raw[["trade_date","industry_code","close","adj_factor"]]

# 2. 后复权价与日收益率
raw["p_adj"] = raw["close"] * raw["adj_factor"]
raw = raw.sort_values(["industry_code","trade_date"])
raw["ret"] = raw.groupby("industry_code")["p_adj"].pct_change()

# 3. 按交易日历对齐,暴露真实缺失
cal = pd.read_csv("trade_calendar.csv", parse_dates=["trade_date"])["trade_date"]
codes = raw["industry_code"].unique()
full = pd.MultiIndex.from_product([cal, codes], names=["trade_date","industry_code"])
panel = raw.set_index(["trade_date","industry_code"]).reindex(full)["ret"].unstack()

# 4. 短缺失前值填充(等价收益为0),长缺失保留
panel = panel.fillna(method="ffill", limit=2)

# 5. 异常值标记
mask = panel.abs() > 0.11
print("待核查异常记录数:", int(mask.sum().sum()))

这段代码的关键在于第3步:用交易日历与行业代码的笛卡尔积重建索引,把“数据源没给”与“当天确实没有交易”区分开。第4步的limit=2是经验阈值,表示最多容忍连续两个交易日缺失;超过则保留缺失,让后续相关性计算按成对完整样本处理。第5步只做标记不做删除,把判断权留给研究者。

复权方向要与研究目标一致:算长周期收益率用后复权,看近期形态可用前复权,但同一面板内不可混用。若数据源提供的是全收益指数,则无需再乘复权因子,否则会重复调整。
不要对行业收益率做全局winsorize。极端行情往往伴随行业间相关性结构变化,是分散度研究的重要信号;正确做法是核查数据错误,而非机械截尾。

至此,一份结构统一、口径明确、缺失与异常均有处理记录的行业日收益率面板即告完成。它可以直接作为后续相关性矩阵、滚动相关性和分散度指标的输入,也可以在元数据中记录指数体系、时间区间与清洗规则,保证不同批次研究之间结果可比、过程可复现。