4. 板块指数与个股数据的整合

跨板块量价共振回测的第一道工程门槛,不是策略逻辑,而是数据对齐。板块指数(如申万行业指数、中证主题指数或自建等权指数)与成分股行情来自不同数据源,交易日历、复权方式、频率、时间戳口径往往不一致。如果直接按行号拼接或按日期字符串简单合并,回测结果会出现"未来函数"或"错位共振"——指数在某日放量上涨,而个股数据实际对应的是前一交易日,共振信号被人为制造出来。本节要解决的就是:如何把板块指数与成分股数据在时间轴和频率上严格对齐,形成可直接进入回测引擎的统一面板。

先明确三个对齐维度

整合工作可以拆成三个必须同时满足的维度。第一是标的维度:板块指数是一个序列,成分股是多个序列,需要确定成分股名单的生效区间,避免用今天的成分股回测三年前(幸存者偏差)。第二是时间维度:两边的交易日历必须取交集或按统一日历重采样,停牌日、节假日、半天交易都要显式处理。第三是频率维度:指数可能是日频,个股若为分钟频,需要先聚合到日频再对齐,或统一降采样到相同周期。三个维度中任何一个错位,量价共振条件就失去意义。

板块指数与成分股:三维对齐与频率聚合 板块指数序列 日期 · 收盘 · 成交量 频率:日频 日历:交易所A 成分股序列(多只) 代码 · 日期 · 收盘 · 成交量 频率:分钟/日频混合 含停牌、复权差异 对齐引擎 ① 统一交易日历(取交集) ② 频率聚合 → 日频 ③ 复权与成分区间匹配 频率聚合:分钟 → 日(以个股为例) 09:31 09:32 … 14:59 15:00 日频:收盘/成交量求和 统一面板(宽表) 日期 × [指数, 个股1, 个股2 …] 对齐后必做校验 行数 = 交易日数;无 NaN 残留;指数与个股同日涨跌方向可解释;停牌日标记而非删除;成分股生效区间与回测区间一致。

交易日历与频率的统一处理

实际数据中,指数与个股的日期列经常出现"看起来一样、实际对不上"的情况。稳妥做法是:先构造一个主日历,取指数交易日与个股交易日的并集,再按回测需要决定是取交集(只保留双方都有数据的日期)还是保留并集并对缺失值做前向填充。对于量价共振策略,成交量缺失不能简单填零,否则会误判为"缩量";更合理的做法是标记为停牌,并在计算共振条件时跳过该日。

频率聚合要遵循"先聚合、后对齐"的顺序。以个股分钟数据聚合为日频为例,收盘价取当日最后一根分钟K线的收盘,成交量取当日所有分钟成交量之和,最高/最低取当日极值。示例参数:某成分股在 2023-06-15 有 240 根分钟线,成交量合计 1,860,000 股,则日频成交量记为 1,860,000;若当日 11:00 起停牌,则只聚合停牌前的分钟线,并在停牌标记列记 1。聚合完成后再与指数日频序列按日期合并,才能保证"同一天"指的是同一个交易日的完整信息。

复权与成分股区间匹配

指数通常是不复权的点位序列,而个股存在除权除息。若个股用不复权价格与指数比较涨跌幅,除权日会出现虚假大跌。建议统一使用后复权价格计算收益率,或直接用"涨跌幅"字段对齐,避免价格量纲差异。成分股名单则要按生效日期做区间匹配:假设某板块在 2023-01-01 至 2023-06-30 的成分股为 A、B、C,2023-07-01 起调整为 A、B、D,则回测在 7 月之前只能使用 A、B、C 的数据计算板块内个股共振,不能提前引入 D。

对齐完成后,建议输出一张"对齐质量检查表":记录主日历天数、指数缺失天数、各成分股停牌天数、复权方式、成分股区间版本。这张表在后续排查"共振信号异常"时非常有用,能快速定位是数据错位还是策略逻辑问题。

一个可执行的对齐示例

下面给出一个简化的 Python 处理骨架,演示"聚合—对齐—校验"的关键步骤(示例数据为假设值,仅用于说明流程):

import pandas as pd

# 假设 idx_daily: 指数日频,index 为日期
# stock_min: 个股分钟频,含 code、datetime、close、volume
stock_min['date'] = stock_min['datetime'].dt.normalize()
stock_daily = stock_min.groupby(['code', 'date']).agg(
    close=('close', 'last'),
    volume=('volume', 'sum')
).reset_index()

# 统一日历:取指数日期与个股日期的交集
calendar = pd.Index(sorted(set(idx_daily.index) & set(stock_daily['date'])))
panel = idx_daily.reindex(calendar).rename(columns={'close': 'idx_close'})
for code, g in stock_daily.groupby('code'):
    s = g.set_index('date')['close'].reindex(calendar)
    panel[f'{code}_close'] = s

# 校验:行数、缺失率、同日方向一致性
assert len(panel) == len(calendar)
print(panel.isna().mean())

运行后重点观察两件事:一是 panel.isna().mean() 是否集中在少数停牌日,若某列缺失率异常高,说明该成分股数据源覆盖不足;二是抽查若干交易日,确认指数涨跌方向与多数成分股方向一致,若出现系统性反向,往往是复权口径或日期错位导致。完成这一步,板块指数与个股数据才真正具备进入共振条件回测的资格。