2. 盘口因子数据基础
做盘口因子的样本外滚动验证,第一步不是写回测框架,而是把数据“看透”。Level-2行情与普通快照行情的本质区别在于:它记录的是事件流而非定时切片。交易所每发生一次委托申报、撤单或成交,就推送一条带时间戳的记录。理解这些记录的结构与字段含义,决定了你后续构造的因子是否具有经济含义,也决定了滚动验证时会不会因为数据对齐错误而得到虚假的稳定收益。
逐笔委托与逐笔成交是两条独立的事件流
以深市Level-2为例,逐笔委托(Order)与逐笔成交(Trade)分别推送,二者通过委托编号(ApplSeqNum / OrderNo)建立关联。逐笔委托记录的是“谁在什么价位挂了多少量、是买还是卖、是限价还是市价、是新增还是撤单”;逐笔成交记录的是“哪两笔委托在什么价位成交了多少量、成交方向是主动买还是主动卖”。上交所的逐笔成交还带有买方委托号和卖方委托号,可以直接还原每笔成交对应的双方委托;深市逐笔成交则通过成交编号与委托编号的映射关系关联,实操中常需借助逐笔委托的撤单标志来还原订单生命周期。
关键字段可以归纳为几类:标识类(证券代码、频道号、委托编号、成交编号)、时间类(交易所时间戳,精确到毫秒甚至微秒)、价格类(委托价、成交价)、数量类(委托量、成交量)、方向与类型类(买卖方向、订单类型、撤单标志)。很多初学者只盯着价格和数量,忽略了订单类型和撤单标志,结果把撤单当成新增委托计入订单流不平衡,因子方向直接反了。
时间戳对齐是滚动验证中最容易出错的地方
逐笔数据的时间戳来自交易所,而你的因子计算、标签构造、滚动窗口切分都在本地完成。这里有两个必须明确的约定。第一,时间戳的精度与单调性:同一毫秒内可能有多条记录,排序时不能只按时间戳,还要按委托编号或成交编号做稳定排序,否则订单簿回放会出现“先撤后挂”的错乱。第二,标签的时间边界:如果你用未来5分钟收益作为标签,那么因子计算窗口的最后一个事件时间必须严格早于标签窗口起点,滚动验证的每一折都要重新检查这个边界,而不是只在全样本上检查一次。
实操中建议统一把时间戳转换为“交易日内的毫秒偏移”,并显式记录集合竞价、连续竞价、午休的时段标记。很多因子在午休前后表现差异巨大,如果不做时段标记,滚动验证的折间稳定性会被时段结构污染。
因子计算所需的最小数据集
“最小”不是越少越好,而是刚好够用且不引入前视偏差。对于绝大多数盘口因子,最小数据集包含四类:逐笔委托全量记录、逐笔成交全量记录、证券静态信息(代码、板块、涨跌停价)、以及用于收益计算的复权因子或收盘价序列。逐笔委托和逐笔成交必须同一交易日、同一证券、同一频道配对使用,缺一不可。
一个常见的误区是只保留“有成交的委托”。这会导致订单簿重建时丢失大量挂单和撤单信息,算出来的盘口深度系统性偏小。正确做法是保留全部委托事件,在回放时按事件类型分别处理:新增委托插入订单簿,撤单事件从订单簿移除对应委托,成交事件扣减对应委托的剩余量。
从原始字段到因子输入的转换要点
拿到原始数据后,通常需要先做三件事:字段重命名与类型转换(价格从整数分转为浮点元,数量转为整型)、方向编码统一(买=1、卖=-1,或按交易所约定统一)、事件类型标记(新增、撤单、成交分别打标)。这三步做完,才能进入订单簿回放。回放时维护两个有序结构:买方按价格降序、卖方按价格升序,同价位按时间先后排队。每处理一个事件,更新对应档位的量,并记录买一卖一价量序列,这就是后续所有盘口因子的基础时间序列。
需要强调的是,订单流不平衡(OFI)这类因子对事件顺序极其敏感。它的标准定义是:买一价不变时买一量增加、卖一价不变时卖一量减少,都计为正贡献;反之计为负贡献。如果事件排序错了,OFI的符号就会错,滚动验证得到的IC也会失真。因此在进入样本外滚动验证之前,务必先用一小段数据手工核对订单簿回放结果与行情快照是否一致,这是数据基础环节最值得投入的校验动作。
| 数据对象 | 关键字段 | 在因子计算中的作用 | 缺失后果 |
|---|---|---|---|
| 逐笔委托 | 委托编号、方向、价、量、撤单标志 | 订单簿重建、OFI、撤单率 | 盘口深度偏小、OFI符号错误 |
| 逐笔成交 | 成交价、量、主动方向、双方委托号 | 有效价差、成交方向占比、大单识别 | 流动性因子失真 |
| 静态信息 | 代码、板块、涨跌停价 | 样本分组、异常值过滤 | 跨板块可比性下降 |
| 复权因子 | 累计复权系数 | 标签收益计算 | 除权日标签跳变 |
把上述数据结构、字段语义、时间对齐规则和最小数据集确认清楚之后,样本外滚动验证才有了可靠的地基。后续无论是构造哪一类盘口因子,都可以回到这张“事件流—订单簿—因子”的链条上检查:输入是否完整、顺序是否正确、边界是否干净。数据基础扎实,滚动验证的结论才值得信任。