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。
五、面板成型与验收
完成上述步骤后,将长表按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步只做标记不做删除,把判断权留给研究者。
至此,一份结构统一、口径明确、缺失与异常均有处理记录的行业日收益率面板即告完成。它可以直接作为后续相关性矩阵、滚动相关性和分散度指标的输入,也可以在元数据中记录指数体系、时间区间与清洗规则,保证不同批次研究之间结果可比、过程可复现。