四、数据对齐与重采样:将逐笔数据重采样为不同频率(秒级、分钟级)的K线,以及复权处理

逐笔成交数据是市场最原始的交易记录,包含时间戳、价格、成交量、买卖方向等字段。然而,逐笔数据量巨大(单日沪深两市可达数亿条),直接用于策略回测或因子计算既不经济也不必要。将逐笔数据重采样为秒级、分钟级K线,是特征工程的第一步,也是连接微观结构与宏观形态的桥梁。本章将系统讲解重采样的核心原理、操作流程、复权处理及常见误区。

逐笔数据 → 重采样K线 → 复权处理 全链路 逐笔成交数据 时间戳(ms) 价格 / 数量 / 方向 成交编号 / 委托编号 例:09:30:00.123 买 10.25×200 重采样核心逻辑 时间分桶 秒/分钟/自定义 聚合计算 OHLCV / 笔数 边界处理 左闭右开 关键参数: 基准价(昨收/前收) | 成交量口径(股/手) | 时间时区 集合竞价处理 | 停牌时段 | 涨跌停边界 复权处理 前复权 / 后复权 除权除息因子 复权因子计算 送股 / 配股 / 分红 增发 / 拆合股 输出:秒级/分钟级K线 DataFrame datetime | open | high | low | close | volume | amount | count 应用场景 高频因子 / 盘口异动 / 波动率建模 质量校验 数据对齐 / 缺失处理 / 异常检测 核心原则 ① 时间对齐优先于价格对齐 ② 复权必须基于原始价格 ③ 边界条件决定数据质量 ④ 重采样前先清洗 ⑤ 验证成交量守恒 ⑥ 因子计算需统一复权口径

4.1 核心原理:从逐笔到K线的映射逻辑

重采样的本质是将不规则时间间隔的逐笔成交记录,映射到规则时间网格上。这一过程涉及三个关键决策:时间分桶方式、聚合函数选择和边界条件定义。

  • 时间分桶:秒级K线以自然秒为桶宽(如09:30:00~09:30:01),分钟级K线以自然分钟为桶宽。桶的起点通常采用左闭右开区间,即包含起点时刻的成交,不包含终点时刻的成交。
  • 聚合函数:开盘价取桶内第一笔成交价,收盘价取最后一笔,最高价取最大值,最低价取最小值,成交量累加,成交额累加,笔数计数。
  • 边界处理:集合竞价时段(09:15~09:25)的成交通常归入第一根分钟K线;午间休市(11:30~13:00)不产生K线;停牌时段无数据,需填充NaN或前值。
关键认知:重采样不是简单的"降频",而是信息压缩。秒级K线保留了日内微观结构(如买卖压力突变),分钟级K线则更适合捕捉中短期趋势。选择何种频率取决于策略的交易周期——高频做市策略可能需要秒级甚至毫秒级,而日内趋势策略通常使用1分钟或5分钟K线。

4.2 操作流程:标准重采样五步法

以下流程适用于Python pandas、Polars或任何数据处理框架,核心逻辑一致。

  1. 数据清洗:去除时间戳重复、价格为负、数量为零的异常记录;检查时间戳是否严格递增。
  2. 时间标准化:将时间戳统一为北京时间(UTC+8),并转换为datetime类型;注意处理夏令时(国内无)和时区偏移。
  3. 设置时间索引:将时间戳列设为DataFrame的索引,便于重采样。
  4. 执行重采样:使用 resample('1S') 或 resample('1T') 进行分桶,然后对每列应用聚合函数。
  5. 后处理:填充缺失K线(如停牌时段),计算复权因子,生成最终K线DataFrame。
import pandas as pd

# 假设 df 为逐笔数据,包含列:time, price, volume, amount, direction
df['time'] = pd.to_datetime(df['time'], utc=True).dt.tz_convert('Asia/Shanghai')
df = df.set_index('time').sort_index()

# 1分钟K线重采样
kline_1min = df.resample('1T', label='left', closed='left').agg({
    'price': 'ohlc',          # 生成 open, high, low, close
    'volume': 'sum',
    'amount': 'sum',
    'direction': 'count'      # 笔数
})
kline_1min.columns = ['open', 'high', 'low', 'close', 'volume', 'amount', 'count']
kline_1min = kline_1min.dropna(subset=['open'])  # 去除无成交时段
实战建议:对于秒级K线,建议使用 label='left' 和 closed='left',确保时间戳对齐到桶的起点。对于分钟级K线,若需与交易所官方K线对齐,应使用 closed='left' 且时间戳标记为分钟起点(如09:31表示09:31:00~09:31:59)。

4.3 复权处理:价格连续性的关键

复权是为了消除分红、送股、配股等公司行为对价格序列造成的"跳空",使历史价格具有可比性。复权分为前复权和后复权两种方式。

复权类型 定义 适用场景 特点
前复权 以当前价格为基准,调整历史价格 技术分析、形态识别 最新价格与市场一致,但历史价格会随每次除权而变动
后复权 以上市首日价格为基准,调整后续价格 长期收益计算、因子回测 历史价格固定不变,但最新价格与市场不一致
不复权 使用原始成交价格 日内高频策略、盘口分析 保留真实成交价,但存在除权跳空

复权因子的计算公式为:

复权因子 = (除权前收盘价 - 现金红利 + 配股价 × 配股比例) / (除权前收盘价 + 送股比例 × 除权前收盘价 + 配股比例 × 配股价)

实际应用中,通常使用第三方数据源(如Wind、Tushare、AkShare)提供的复权因子,避免自行计算带来的误差。对于逐笔数据重采样后的K线,建议先重采样,后复权,因为复权因子是基于日线计算的,直接应用于逐笔价格会引入偏差。

风险提示:在因子计算中,若混合使用前复权和后复权数据,会导致因子值失真。例如,计算过去20日的收益率时,前复权数据会因最近一次除权而整体调整历史价格,导致收益率计算错误。建议在因子库中统一使用后复权数据,仅在展示图表时使用前复权。

4.4 案例拆解:分钟K线重采样与复权实战

以下案例基于某股票2024年3月15日的逐笔数据(约12万条),目标生成1分钟K线并处理后复权价格。

背景与约束:该股票当日发生除权(10送2派1元),除权前收盘价为20.00元,除权参考价为16.58元。逐笔数据包含原始成交价,未复权。

操作步骤:

  1. 读取逐笔数据,清洗异常记录(删除时间戳重复的3条、价格为0的1条)。
  2. 按上述代码重采样为1分钟K线,得到240根K线(09:30~11:30共121根,13:00~15:00共119根)。
  3. 从数据源获取复权因子:当日复权因子为0.829(=16.58/20.00)。
  4. 计算后复权价格:将每根K线的OHLC乘以复权因子,成交量不变,成交额除以复权因子(保持金额一致)。
  5. 验证:后复权收盘价序列的收益率与原始价格序列的日收益率一致。
时间 原始收盘价 后复权收盘价 成交量(手) 后复权成交额(元)
09:31 16.60 13.76 1,250 1,720,000
09:32 16.58 13.74 980 1,346,000
... ... ... ... ...
15:00 17.20 14.26 2,100 2,994,000

结果对比:未复权的分钟K线在除权日会出现明显的价格跳空(从20元直接跳到16.58元),而后复权K线则呈现连续的价格序列,便于技术指标计算。但需注意,后复权价格并非真实成交价,在计算涨跌停幅度、盘口深度等场景时,必须使用原始价格。

复盘经验:① 复权处理必须在重采样之后进行,否则会破坏逐笔数据的原始性;② 对于高频策略,建议同时保留原始价格和复权价格两套数据;③ 复权因子的时效性——每次除权后,历史复权因子都会变化,需定期更新。

4.5 常见误区与总结复盘

在数据对齐与重采样过程中,以下误区最为常见:

  • 误区一:忽略集合竞价时段。集合竞价的成交量应归入第一根K线,否则会低估开盘时的交易活跃度。
  • 误区二:时间戳精度不一致。部分交易所提供微秒级时间戳,部分仅到秒级,混用会导致排序错误。
  • 误区三:复权因子使用错误。将前复权因子应用于后复权计算,或反之,导致价格序列扭曲。
  • 误区四:未处理停牌时段。停牌期间不应生成K线,但需保留时间索引以便后续对齐。
  • 误区五:成交量单位混淆。逐笔数据可能以"股"为单位,而K线通常以"手"为单位(1手=100股),需统一换算。
最佳实践:建立标准化的重采样配置模板,包含时间频率、聚合规则、复权方式、单位换算等参数。每次处理新数据时,先运行数据质量检查脚本,再执行重采样,最后进行一致性校验(如总成交量守恒、OHLC关系验证)。

总结而言,数据对齐与重采样是量化分析的基础工程,其质量直接决定后续特征提取和模型训练的有效性。掌握时间分桶、聚合计算、复权处理三大核心技能,并建立严格的数据校验流程,是每一位量化工程师的必修课。