量化学习(一):初识A股Level2数据集

量化学习(一):初识A股Level2数据集

摘要

Level2数据记录了证券市场的十档盘口、逐笔委托和逐笔成交信息。相较于日线、分钟线和Level1行情,Level2能够进一步描述订单如何进入市场、如何撤销、如何成交,以及这些行为怎样影响盘口和价格。

本文面向零基础读者,使用一份真实A股Level2数据,介绍三类原始文件及其全部字段,解释订单簿、主动成交、盘口失衡、订单流失衡、VWAP和价格冲击等术语,并给出可以直接运行的Python代码。实验部分以600009.SH20260626 的数据为例,展示原始数据清洗前后的变化、重复字段、低信息字段和具有研究价值的核心字段。

关键词: Level2;订单簿;逐笔委托;逐笔成交;市场微观结构;量化交易

20260626 Level2 数据:

通过网盘分享的文件:20260626.7z

链接: https://pan.baidu.com/s/1-wDwn9ffkIYDHPDdf5XNxw 提取码: pita

一、研究背景

1.1 什么是Level2数据

股票软件中常见的日线和分钟线,主要由开盘价、最高价、最低价、收盘价、成交量和成交额组成。这些数据描述了某段时间内形成的最终结果,却无法完整解释价格形成过程。

Level1行情通常提供最新成交价、累计成交量以及有限档位的买卖盘口。Level2进一步提供十档盘口、逐笔委托、逐笔成交和撤单事件。

例如,某只股票从10.00元上涨至10.10元,分钟线只能告诉我们价格上涨了1%。Level2可以继续回答以下问题:

问题 对应Level2数据
上涨由主动买入推动,还是卖盘主动撤单造成 逐笔成交、逐笔委托
买一到买十共有多少挂单 十档盘口
是否出现连续大额主动买入 逐笔成交
是否存在频繁挂单和快速撤单 逐笔委托
资金集中在开盘、盘中还是尾盘交易 毫秒级时间字段
大额成交发生后价格是否明显变化 成交记录与行情快照

Level2研究属于市场微观结构研究。市场微观结构关注订单、流动性、交易机制、买卖价差和价格发现过程。

1.2 为什么量化学习需要了解Level2

日线和分钟线主要描述价格结果,Level2能够描述形成结果的交易行为。对于短周期交易、盘口因子、成交执行、异常交易识别和资金行为分析,Level2通常具有更高的信息密度。

Level2的研究价值主要体现在以下方面:

研究方向 Level2提供的信息
短周期价格预测 盘口失衡、订单流失衡、主动成交方向
流动性研究 买卖价差、盘口深度、撤单率
交易执行 成交概率、滑点、订单等待时间
行为识别 大额成交、拆单、快速挂撤、脉冲交易
价格发现 成交后价格变化、大单价格冲击
日内结构 开盘、午间、尾盘成交集中度

Level2也存在明确边界。它能够观察订单和成交行为,却通常不能直接观察交易账户身份。仅凭Level2无法确认某笔交易来自散户、量化机构或游资,只能判断其行为更接近哪一种交易模式。

二、Level2数据集结构与参数

2.1 数据目录

本文使用的数据按"交易日/股票代码"组织:

text 复制代码
20260626/
└── 600009.SH/
    ├── 行情.csv
    ├── 逐笔委托.csv
    └── 逐笔成交.csv

三张表分别表示市场状态、委托事件和成交事件。

文件 行数示例 字段数 数据性质
行情.csv 4820 66 盘口状态快照
逐笔委托.csv 44533 10 委托事件流
逐笔成交.csv 29209 12 成交事件流

原始CSV使用GB18030编码。价格字段以万分之一元存储,数量字段通常以股为单位。本文后续字段名称、事件代码和缩放规则均以这份数据为准;更换数据商、交易所或证券品种时,应先核对对应数据字典。

python 复制代码
from pathlib import Path
import pandas as pd

stock_dir = Path("20260626/600009.SH")

market = pd.read_csv(stock_dir / "行情.csv", encoding="gb18030")
orders = pd.read_csv(stock_dir / "逐笔委托.csv", encoding="gb18030")
trades = pd.read_csv(stock_dir / "逐笔成交.csv", encoding="gb18030")

print(market.shape)
print(orders.shape)
print(trades.shape)

输出为:

text 复制代码
(4820, 66)
(44533, 10)
(29209, 12)

2.2 三张表共有字段

字段 含义 是否进入模型
万得代码 完整证券代码,例如600009.SH 用于索引,不直接作为数值特征
交易所代码 不带后缀的证券代码,例如600009 与万得代码重复,可删除
自然日 交易日期,格式为YYYYMMDD 用于日期索引
时间 HHMMSSmmm格式的日内时间 核心字段

例如:

text 复制代码
92501000 → 09:25:01.000
145700090 → 14:57:00.090

同一毫秒可能发生多笔委托或成交,因此时间不能单独充当唯一主键。本文样本中,逐笔委托有21073行与其他记录共享时间戳,逐笔成交有21366行共享时间戳,但委托编号成交编号均没有重复。

2.3 行情快照字段

行情.csv共66列。除4个公共字段外,其余字段如下。

编号 字段 字段含义 研究价值
1 万得代码 万得统一证券代码,例如600000.SH 用于识别股票和关联其他数据,属于公共标识字段
2 交易所代码 当前数据中为不带市场后缀的证券代码,例如600009 与万得代码重复,可用于跨表校验;字段名沿用数据源命名
3 自然日 行情快照对应的自然日期 用于按交易日分组、过滤和回放,属于公共时间字段
4 时间 行情快照对应的盘中时间 用于恢复盘口时序、划分集合竞价与连续竞价,属于公共时间字段
5 成交价 当前快照记录的最新成交价格 用于计算收益率、价格趋势、波动率和价格冲击,研究价值高
6 成交量 当前快照对应的增量成交量 当前样本中恒为0,应优先使用累计成交量差分,直接研究价值低
7 成交额 当前快照对应的增量成交金额 当前样本中恒为0,应优先使用累计成交额差分,直接研究价值低
8 成交笔数 截至当前快照的累计成交笔数 可通过相邻快照差分估计成交活跃度,研究价值高
9 IOPV ETF盘中参考净值 对ETF估值和折溢价分析有用,普通股票中通常为0
10 成交标志 当前快照的成交状态标志 可辅助判断行情事件类型,当前样本为空,直接研究价值低
11 BS标志 当前快照的买卖方向标志 理论上可辅助识别买卖方向,当前样本为空,直接研究价值低
12 当日累计成交量 截至当前时点的累计成交数量 差分后可得到区间成交量,是成交活跃度与量价分析的核心字段
13 当日成交额 截至当前时点的累计成交金额 差分后可得到区间成交额,是资金规模与时段集中度分析的核心字段
14 最高价 截至当前时点的日内最高成交价 用于计算日内位置、突破行为和最大上涨幅度,研究价值中高
15 最低价 截至当前时点的日内最低成交价 用于计算日内位置、回撤行为和最大下跌幅度,研究价值中高
16 开盘价 当日开盘成交价格 用于计算开盘后收益、跳空幅度和竞价强弱,研究价值中高
17 前收盘 上一交易日收盘价格 用于计算涨跌幅、跳空幅度和涨跌停参考价格,研究价值高
18 申卖价1 当前最低卖出报价,即卖一价 用于计算买卖价差、成交成本和最优卖价变化,研究价值高
19 申卖价2 当前第二低的卖出报价 用于观察近端卖盘价格梯度和卖一消耗后的潜在阻力,研究价值高
20 申卖价3 当前第三低的卖出报价 用于分析近端卖盘深度与价格阶梯,研究价值高
21 申卖价4 当前第四低的卖出报价 用于分析卖方报价层次和短期上行阻力,研究价值中高
22 申卖价5 当前第五低的卖出报价 用于构建五档卖盘价格结构,研究价值中高
23 申卖价6 当前第六低的卖出报价 用于观察五档之外的深层卖盘价格,研究价值中高
24 申卖价7 当前第七低的卖出报价 用于分析深层卖盘分布与潜在阻力,研究价值中高
25 申卖价8 当前第八低的卖出报价 用于分析远端卖盘价格梯度,研究价值中等
26 申卖价9 当前第九低的卖出报价 用于补充深层卖盘结构,研究价值中等
27 申卖价10 当前第十低的卖出报价 用于确定十档卖盘边界和完整深度范围,研究价值中等
28 申卖量1 卖一价格上的未成交委托量 反映最优卖价即时抛压,是盘口分析的核心字段
29 申卖量2 卖二价格上的未成交委托量 反映卖一被消耗后的下一层抛压,研究价值高
30 申卖量3 卖三价格上的未成交委托量 用于分析近端卖盘深度与上行阻力,研究价值高
31 申卖量4 卖四价格上的未成交委托量 用于分析卖方流动性分布,研究价值中高
32 申卖量5 卖五价格上的未成交委托量 用于计算五档卖盘总量和集中度,研究价值中高
33 申卖量6 卖六价格上的未成交委托量 用于观察深层卖方流动性,研究价值中高
34 申卖量7 卖七价格上的未成交委托量 用于分析潜在远端抛压,研究价值中高
35 申卖量8 卖八价格上的未成交委托量 用于补充深层卖盘数量结构,研究价值中等
36 申卖量9 卖九价格上的未成交委托量 用于识别远端大额挂单和卖盘堆积,研究价值中等
37 申卖量10 卖十价格上的未成交委托量 用于计算完整十档卖盘深度,研究价值中等
38 申买价1 当前最高买入报价,即买一价 用于计算买卖价差、即时支撑和最优买价变化,研究价值高
39 申买价2 当前第二高的买入报价 用于观察买一消耗后的下一层价格支撑,研究价值高
40 申买价3 当前第三高的买入报价 用于分析近端买盘价格梯度与承接结构,研究价值高
41 申买价4 当前第四高的买入报价 用于分析买方报价层次和短期支撑,研究价值中高
42 申买价5 当前第五高的买入报价 用于构建五档买盘价格结构,研究价值中高
43 申买价6 当前第六高的买入报价 用于观察五档之外的深层买盘价格,研究价值中高
44 申买价7 当前第七高的买入报价 用于分析深层买盘分布与潜在支撑,研究价值中高
45 申买价8 当前第八高的买入报价 用于分析远端买盘价格梯度,研究价值中等
46 申买价9 当前第九高的买入报价 用于补充深层买盘结构,研究价值中等
47 申买价10 当前第十高的买入报价 用于确定十档买盘边界和完整深度范围,研究价值中等
48 申买量1 买一价格上的未成交委托量 反映最优买价即时承接,是盘口分析的核心字段
49 申买量2 买二价格上的未成交委托量 反映买一被消耗后的下一层承接,研究价值高
50 申买量3 买三价格上的未成交委托量 用于分析近端买盘深度与下行支撑,研究价值高
51 申买量4 买四价格上的未成交委托量 用于分析买方流动性分布,研究价值中高
52 申买量5 买五价格上的未成交委托量 用于计算五档买盘总量和集中度,研究价值中高
53 申买量6 买六价格上的未成交委托量 用于观察深层买方流动性,研究价值中高
54 申买量7 买七价格上的未成交委托量 用于分析潜在远端承接力量,研究价值中高
55 申买量8 买八价格上的未成交委托量 用于补充深层买盘数量结构,研究价值中等
56 申买量9 买九价格上的未成交委托量 用于识别远端大额挂单和买盘堆积,研究价值中等
57 申买量10 买十价格上的未成交委托量 用于计算完整十档买盘深度,研究价值中等
58 加权平均叫卖价 按委托量加权计算的卖方平均报价 用于衡量整体卖盘成本、卖盘重心及其时序变化,研究价值中高
59 加权平均叫买价 按委托量加权计算的买方平均报价 用于衡量整体买盘成本、买盘重心及其时序变化,研究价值中高
60 叫卖总量 当前全部有效卖出委托的总数量 用于衡量整体卖方供给和深层抛压,研究价值高
61 叫买总量 当前全部有效买入委托的总数量 用于衡量整体买方需求和深层承接,研究价值高
62 不加权指数 数据商预留的市场指数类字段 当前个股样本中恒为0,直接研究价值低
63 品种总数 当前市场统计口径下的证券品种数量 当前个股样本中恒为0,直接研究价值低
64 上涨品种数 当前市场中价格上涨的证券数量 理论上可衡量市场广度,当前个股样本中恒为0
65 下跌品种数 当前市场中价格下跌的证券数量 理论上可衡量市场弱势广度,当前个股样本中恒为0
66 持平品种数 当前市场中价格持平的证券数量 理论上可补充市场广度结构,当前个股样本中恒为0

所有价格类字段都需要除以10000。例如:

text 复制代码
成交价      232000 → 23.20元
申买价1     231900 → 23.19元
申卖价1     232000 → 23.20元
前收盘      233000 → 23.30元

申买价1是当前最高买价,申卖价1是当前最低卖价。档位数字越大,报价距离当前成交价格通常越远。

叫买总量和十档买量之和含义不同。前者可能覆盖更完整的订单簿,后者只表示当前显示的十档深度。因此,这两个字段不能视为简单重复字段。

2.4 逐笔委托字段

编号 逐笔委托.csv字段 字段含义 研究价值
1 万得代码 完整证券代码,例如600009.SH 股票标识和跨表关联
2 交易所代码 不带市场后缀的证券代码 与万得代码重复,可用于校验
3 自然日 委托事件对应的交易日期 日期分组和多日研究
4 时间 委托事件发生的毫秒级时间 订单排序、节奏和撤单速度分析
5 委托编号 数据中的委托事件顺序编号 事件排序和唯一性检查
6 交易所委托号 交易所分配的订单编号 关联新增、撤销和成交事件
7 委托类型 新增、撤销或状态事件类型 识别订单进入和退出过程
8 委托代码 买卖方向或状态代码 区分买单、卖单和状态记录
9 委托价格 订单申报价格 挂单位置、侵略性和订单金额
10 委托数量 订单申报或撤销数量 订单规模、撤单量和流动性分析

当前样本中的委托类型如下:

委托类型 数量 含义
A 32101 新增委托
D 12428 撤销委托
S 4 市场状态记录

委托类型AD时,委托代码=B表示买单,委托代码=S表示卖单。状态记录中还出现IOJC等代码,其精确语义需要对应数据商字典确认。

一条原始新增委托为:

text 复制代码
时间       委托类型  委托代码  委托价格  委托数量
91500070   A        S        239800    5000

清洗后可以解释为:

text 复制代码
09:15:00.070新增一笔卖单
委托价格:23.98元
委托数量:5000股
委托金额:119900元

2.5 逐笔成交字段

编号 逐笔成交.csv字段 字段含义 研究价值
1 万得代码 完整证券代码,例如600009.SH 股票标识和跨表关联
2 交易所代码 不带市场后缀的证券代码 与万得代码重复,可用于校验
3 自然日 成交事件对应的交易日期 日期分组和多日研究
4 时间 成交事件发生的毫秒级时间 成交排序、节奏和时段分析
5 成交编号 当日成交事件编号 成交排序和唯一性检查
6 成交代码 成交记录附带的状态代码 当前样本恒为0x00,直接研究价值低
7 委托代码 成交记录附带的委托状态代码 当前样本恒为0x00,直接研究价值低
8 BS标志 主动买入或主动卖出方向 主动成交、订单流和资金方向分析
9 成交价格 本笔交易的实际成交价格 收益、VWAP和价格冲击分析
10 成交数量 本笔交易的成交股数 成交金额和订单规模分层
11 叫卖序号 参与成交的卖方订单序号 关联卖方委托及恢复订单生命周期
12 叫买序号 参与成交的买方订单序号 关联买方委托及恢复订单生命周期

原始成交记录为:

text 复制代码
时间       BS标志  成交价格  成交数量  叫卖序号  叫买序号
92501000   S       232000    100      62071     53871

清洗后表示:

text 复制代码
09:25:01.000发生主动卖出成交
成交价格:23.20元
成交数量:100股
成交金额:2320元

在本文数据口径中,B表示主动买入,S表示主动卖出。主动买入通常意味着买方接受当前卖价,主动卖出通常意味着卖方接受当前买价。

2.6 重复字段和低信息字段

字段重复可以分为结构重复、可校验重复和低信息重复。

字段关系 判断 处理方式
万得代码交易所代码 表达同一证券身份 保留万得代码
三张表中的代码、日期和时间 为独立文件提供索引 合并后保留一份
当日累计成交量与逐笔成交数量之和 可以相互推导 保留一份建模,另一份用于校验
当日成交额与逐笔成交金额之和 可以相互推导 保留一份建模,另一份用于校验
行情开高低价与逐笔成交价格 可以重新计算 快照字段用于审计
十档买卖量与叫买、叫卖总量 统计范围可能不同 两者均保留
加权买卖价与十档报价 数据商计算口径可能覆盖更多深度 两者均保留
行情成交量成交额 当前样本恒为0 删除
行情成交标志BS标志 当前样本全空 删除
成交代码、成交表委托代码 当前样本恒为0x00 删除
指数、品种数、涨跌家数 当前个股文件恒为0 删除

本文样本中,行情末行累计成交量为15024986股,与逐笔成交数量之和完全一致;行情末行累计成交额为349450088元,与逐笔成交计算结果349450087.92元一致;累计成交笔数为29209,也与逐笔成交行数一致。

这些字段虽然具有重复性,却非常适合用于数据完整性验证。

三、相关术语和数学原理

3.1 买卖价差

买卖价差是卖一价与买一价之差:

S p r e a d t = A s k 1 , t − B i d 1 , t Spread_t=Ask_{1,t}-Bid_{1,t} Spreadt=Ask1,t−Bid1,t

假设买一为10.00元,卖一为10.01元,则价差为0.01元。价差越小,交易成本通常越低,市场流动性通常越好。

相对价差可以消除不同股价的影响:

R e l a t i v e S p r e a d t = A s k 1 , t − B i d 1 , t ( A s k 1 , t + B i d 1 , t ) / 2 RelativeSpread_t= \frac{Ask_{1,t}-Bid_{1,t}} {(Ask_{1,t}+Bid_{1,t})/2} RelativeSpreadt=(Ask1,t+Bid1,t)/2Ask1,t−Bid1,t

3.2 中间价

中间价是买一与卖一的平均值:

M i d P r i c e t = A s k 1 , t + B i d 1 , t 2 MidPrice_t=\frac{Ask_{1,t}+Bid_{1,t}}{2} MidPricet=2Ask1,t+Bid1,t

买一10.00元、卖一10.02元时,中间价为10.01元。中间价常用于衡量盘口中心和计算短周期价格变化。

3.3 盘口深度

盘口深度表示各档位能够承接的挂单数量。十档买盘深度为:

B i d D e p t h t = ∑ i = 1 10 B i d V o l u m e i , t BidDepth_t=\sum_{i=1}^{10}BidVolume_{i,t} BidDeptht=i=1∑10BidVolumei,t

十档卖盘深度为:

A s k D e p t h t = ∑ i = 1 10 A s k V o l u m e i , t AskDepth_t=\sum_{i=1}^{10}AskVolume_{i,t} AskDeptht=i=1∑10AskVolumei,t

深度较大通常意味着市场能够吸收更大的订单,但挂单随时可能撤销,因此深度不能视为确定的成交承诺。

3.4 盘口失衡

盘口失衡衡量买卖两侧挂单数量差异:

I m b a l a n c e t = B i d D e p t h t − A s k D e p t h t B i d D e p t h t + A s k D e p t h t Imbalance_t= \frac{BidDepth_t-AskDepth_t} {BidDepth_t+AskDepth_t} Imbalancet=BidDeptht+AskDepthtBidDeptht−AskDeptht

假设十档买盘为120万股,十档卖盘为80万股:

I m b a l a n c e = 120 − 80 120 + 80 = 0.2 Imbalance=\frac{120-80}{120+80}=0.2 Imbalance=120+80120−80=0.2

结果为0.2,表示买盘数量相对占优。该指标取值范围为 − 1 , 1 -1,1 −1,1

3.5 微价格

中间价只使用买卖报价,没有考虑一档数量。微价格将一档挂单量加入计算:

M i c r o P r i c e t = A s k 1 , t × B i d V o l u m e 1 , t + B i d 1 , t × A s k V o l u m e 1 , t B i d V o l u m e 1 , t + A s k V o l u m e 1 , t MicroPrice_t= \frac{Ask_{1,t}\times BidVolume_{1,t} +Bid_{1,t}\times AskVolume_{1,t}} {BidVolume_{1,t}+AskVolume_{1,t}} MicroPricet=BidVolume1,t+AskVolume1,tAsk1,t×BidVolume1,t+Bid1,t×AskVolume1,t

买一挂单量明显高于卖一时,微价格会更接近卖一价,表示买方承接力量较强。

3.6 主动买卖

成交金额为:

A m o u n t i = P r i c e i × V o l u m e i Amount_i=Price_i\times Volume_i Amounti=Pricei×Volumei

主动买入金额占比为:

A c t i v e B u y R a t i o = ∑ i : B S i = B A m o u n t i ∑ i : B S i ∈ { B , S } A m o u n t i ActiveBuyRatio= \frac{\sum_{i:BS_i=B}Amount_i} {\sum_{i:BS_i\in\{B,S\}}Amount_i} ActiveBuyRatio=∑i:BSi∈{B,S}Amounti∑i:BSi=BAmounti

主动净成交方向为:

A c t i v e N e t = B u y A m o u n t − S e l l A m o u n t B u y A m o u n t + S e l l A m o u n t ActiveNet= \frac{BuyAmount-SellAmount} {BuyAmount+SellAmount} ActiveNet=BuyAmount+SellAmountBuyAmount−SellAmount

ActiveNet接近1表示主动买入占优,接近-1表示主动卖出占优,接近0表示双方较均衡。

主动成交方向只能描述谁接受了当前报价,无法直接证明资金最终流入或流出。

3.7 VWAP

VWAP是成交量加权平均价格:

V W A P = ∑ i P r i c e i × V o l u m e i ∑ i V o l u m e i VWAP= \frac{\sum_i Price_i\times Volume_i} {\sum_i Volume_i} VWAP=∑iVolumei∑iPricei×Volumei

假设有两笔成交:

成交价格 成交数量
10.00 1000
10.10 3000

则:

V W A P = 10.00 × 1000 + 10.10 × 3000 4000 = 10.075 VWAP=\frac{10.00\times1000+10.10\times3000}{4000} =10.075 VWAP=400010.00×1000+10.10×3000=10.075

最新价格高于VWAP,说明价格相对日内平均成交成本偏高;最新价格低于VWAP,说明价格相对平均成本偏低。

3.8 撤单事件占比

本文计算的是撤单事件在新增与撤单事件总数中的占比:

C a n c e l E v e n t S h a r e = C a n c e l C o u n t A d d C o u n t + C a n c e l C o u n t CancelEventShare= \frac{CancelCount} {AddCount+CancelCount} CancelEventShare=AddCount+CancelCountCancelCount

该指标描述委托事件构成,不等同于"被撤订单数占新增订单数的比例",也不表示撤销数量占申报数量的比例。撤单事件占比较高可能来自算法做市、行情快速变化、流动性管理或试探性挂单,仍需与撤单速度、撤单方向、挂单位置和后续成交共同分析。

3.9 变异系数

变异系数用于衡量数据相对波动程度:

C V = σ ∣ μ ∣ CV=\frac{\sigma}{|\mu|} CV=∣μ∣σ

其中 σ \sigma σ是标准差, μ \mu μ是均值。

对于成交间隔,较低的CV通常表示交易节奏较均匀;较高的CV表示交易集中在少数时段或存在明显脉冲。

午间休市和集合竞价会显著放大成交间隔CV,因此应分别计算连续竞价时段,避免将休市时间误认为交易节奏异常。

3.10 信息熵

信息熵可以衡量成交金额在时间上的分散程度:

H = − ∑ j = 1 n p j ln ⁡ p j H=-\sum_{j=1}^{n}p_j\ln p_j H=−j=1∑npjlnpj

其中 p j p_j pj表示第 j j j个时间窗口的成交金额占比。

成交金额均匀分布在多个时间窗口时,熵较高;成交集中在少数窗口时,熵较低。为了便于跨样本比较,可以除以 ln ⁡ n \ln n lnn得到0到1之间的归一化熵。

3.11 简化订单事件净流

为了把新增与撤销事件统一到同一个方向上,本文定义简化订单事件净流:

N e t O r d e r E v e n t F l o w = N e w B u y − N e w S e l l − C a n c e l B u y + C a n c e l S e l l NetOrderEventFlow= NewBuy-NewSell-CancelBuy+CancelSell NetOrderEventFlow=NewBuy−NewSell−CancelBuy+CancelSell

新增买单提高买方压力,新增卖单提高卖方压力;买单撤销削弱买方压力,卖单撤销削弱卖方压力。计算时应统一使用委托数量或委托金额,并固定统计窗口:

N o r m a l i z e d N e t O r d e r E v e n t F l o w = N e t O r d e r E v e n t F l o w N e w B u y + N e w S e l l + C a n c e l B u y + C a n c e l S e l l + ϵ NormalizedNetOrderEventFlow= \frac{NetOrderEventFlow} {NewBuy+NewSell+CancelBuy+CancelSell+\epsilon} NormalizedNetOrderEventFlow=NewBuy+NewSell+CancelBuy+CancelSell+ϵNetOrderEventFlow

这个指标是当前数据口径下的简化统计。Cont、Kukanov与Stoikov提出的经典订单流失衡(OFI)跟踪最优买卖价及其队列数量变化,定义范围更严格。使用"OFI"名称时,应明确价格层级、事件映射和时间窗口。

3.12 价格冲击

本文使用成交方向调整后的未来中间价收益,作为短期价格响应的代理指标:

S i g n e d M i d R e s p o n s e i = D i r e c t i o n i × M i d P r i c e i + Δ t − M i d P r i c e i M i d P r i c e i SignedMidResponse_i= Direction_i\times \frac{MidPrice_{i+\Delta t}-MidPrice_i} {MidPrice_i} SignedMidResponsei=Directioni×MidPriceiMidPricei+Δt−MidPricei

主动买入的方向取1,主动卖出的方向取-1。如果一笔主动买入后中间价上涨,该指标为正。实际计算必须固定 Δ t \Delta t Δt,处理同毫秒事件顺序,并避免跨越午间休市或集合竞价边界。

该指标描述成交方向与随后价格变化的一致程度,不能单独证明某笔交易对价格变化具有因果作用。它可以帮助区分"成交金额很大但随后价格变化较小"和"成交金额较小但随后价格快速变化"两类现象。

四、方法论与真实数据实验

4.1 数据清洗代码

下面的代码可以直接读取三张表、转换价格和时间、过滤状态记录,并删除当前样本中的重复或低信息字段。

python 复制代码
from pathlib import Path
import numpy as np
import pandas as pd

PRICE_SCALE = 10000.0

def time_to_ms(series: pd.Series) -> pd.Series:
    text = series.astype("int64").astype(str).str.zfill(9)
    return (
        text.str[0:2].astype(int) * 3_600_000
        + text.str[2:4].astype(int) * 60_000
        + text.str[4:6].astype(int) * 1_000
        + text.str[6:9].astype(int)
    )

def load_level2(stock_dir: str):
    root = Path(stock_dir)

    market = pd.read_csv(root / "行情.csv", encoding="gb18030")
    orders = pd.read_csv(root / "逐笔委托.csv", encoding="gb18030")
    trades = pd.read_csv(root / "逐笔成交.csv", encoding="gb18030")

    market["time_ms"] = time_to_ms(market["时间"])
    orders["time_ms"] = time_to_ms(orders["时间"])
    trades["time_ms"] = time_to_ms(trades["时间"])

    market_price_columns = [
        "成交价", "最高价", "最低价", "开盘价", "前收盘",
        "加权平均叫卖价", "加权平均叫买价",
        *[f"申卖价{i}" for i in range(1, 11)],
        *[f"申买价{i}" for i in range(1, 11)],
    ]
    market[market_price_columns] = (
        market[market_price_columns].astype(float) / PRICE_SCALE
    )

    orders["order_price"] = orders["委托价格"] / PRICE_SCALE
    orders["order_amount"] = (
        orders["order_price"] * orders["委托数量"]
    )

    trades["trade_price"] = trades["成交价格"] / PRICE_SCALE
    trades["trade_amount"] = (
        trades["trade_price"] * trades["成交数量"]
    )

    market_drop_columns = [
        "交易所代码",
        "成交量",
        "成交额",
        "IOPV",
        "成交标志",
        "BS标志",
        "不加权指数",
        "品种总数",
        "上涨品种数",
        "下跌品种数",
        "持平品种数",
    ]
    market = market.drop(columns=market_drop_columns)

    orders = orders[
        orders["委托类型"].isin(["A", "D"])
    ].copy()
    orders = orders.drop(columns=["交易所代码"])

    trades = trades[
        trades["BS标志"].isin(["B", "S"])
        & (trades["成交价格"] > 0)
        & (trades["成交数量"] > 0)
    ].copy()
    trades = trades.drop(
        columns=["交易所代码", "成交代码", "委托代码"]
    )

    valid_market = market[
        (market["申买价1"] > 0)
        & (market["申卖价1"] > 0)
    ].copy()
    valid_market.attrs["valid_two_sided_quote_ratio"] = (
        len(valid_market) / len(market)
    )

    return valid_market, orders, trades

运行:

python 复制代码
market, orders, trades = load_level2(
    "20260626/600009.SH"
)

print(market.shape)
print(orders.shape)
print(trades.shape)

这里的valid_market只保留买一和卖一同时大于0的双边有效盘口,用于计算价差和常规盘口失衡。集合竞价转换状态以及涨跌停等单边盘口具有独立研究价值,正式研究时应单独标记,不能直接当作异常记录删除。

数据变化如下:

数据表 清洗前 清洗后 主要变化
行情 4820×66 4817×56 删除10个无信息字段和交易所代码,过滤3条无有效一档盘口记录,新增time_ms
委托 44533×10 44529×12 删除4条状态记录和交易所代码,新增时间、价格、金额字段
成交 29209×12 29209×12 删除3个冗余字段,新增时间、价格、金额字段

4.2 特征构造代码

python 复制代码
def safe_ratio(numerator, denominator):
    return float(numerator / denominator) if denominator else 0.0

def coefficient_of_variation(values):
    values = pd.Series(values).replace(
        [np.inf, -np.inf], np.nan
    ).dropna()

    if len(values) == 0 or abs(values.mean()) < 1e-12:
        return 0.0

    return float(values.std(ddof=0) / abs(values.mean()))

def build_level2_features(market, orders, trades):
    amount = trades["trade_amount"]
    buy = trades["BS标志"].eq("B")
    sell = trades["BS标志"].eq("S")

    buy_amount = amount[buy].sum()
    sell_amount = amount[sell].sum()

    small = amount < 40_000
    medium = (amount >= 40_000) & (amount < 100_000)
    large = (amount >= 100_000) & (amount < 500_000)
    mega = amount >= 500_000

    add = orders["委托类型"].eq("A")
    cancel = orders["委托类型"].eq("D")
    buy_order = orders["委托代码"].eq("B")
    sell_order = orders["委托代码"].eq("S")

    bid_volume_columns = [
        f"申买量{i}" for i in range(1, 11)
    ]
    ask_volume_columns = [
        f"申卖量{i}" for i in range(1, 11)
    ]

    bid_depth = market[bid_volume_columns].sum(axis=1)
    ask_depth = market[ask_volume_columns].sum(axis=1)

    spread = market["申卖价1"] - market["申买价1"]
    imbalance = (
        (bid_depth - ask_depth)
        / (bid_depth + ask_depth).replace(0, np.nan)
    )
    full_book_imbalance = (
        (market["叫买总量"] - market["叫卖总量"])
        / (market["叫买总量"] + market["叫卖总量"])
        .replace(0, np.nan)
    )

    trade_intervals = (
        trades["time_ms"].sort_values().diff().dropna()
    )
    minute_count = trades.groupby(
        trades["time_ms"] // 60_000
    ).size()

    total_amount = amount.sum()
    open_30 = trades["time_ms"].between(
        9 * 3_600_000 + 30 * 60_000,
        10 * 3_600_000,
    )
    close_10 = trades["time_ms"].between(
        14 * 3_600_000 + 50 * 60_000,
        15 * 3_600_000,
    )

    return {
        "trade_count": len(trades),
        "total_trade_amount": float(total_amount),
        "average_trade_amount": float(amount.mean()),
        "active_buy_ratio": safe_ratio(
            buy_amount, buy_amount + sell_amount
        ),
        "active_sell_ratio": safe_ratio(
            sell_amount, buy_amount + sell_amount
        ),
        "active_net": safe_ratio(
            buy_amount - sell_amount,
            buy_amount + sell_amount,
        ),
        "small_trade_count_ratio": float(small.mean()),
        "small_trade_amount_ratio": safe_ratio(
            amount[small].sum(), total_amount
        ),
        "medium_trade_amount_ratio": safe_ratio(
            amount[medium].sum(), total_amount
        ),
        "large_trade_amount_ratio": safe_ratio(
            amount[large].sum(), total_amount
        ),
        "mega_trade_amount_ratio": safe_ratio(
            amount[mega].sum(), total_amount
        ),
        "cancel_event_share": safe_ratio(
            cancel.sum(), add.sum() + cancel.sum()
        ),
        "buy_share_of_cancels": safe_ratio(
            (cancel & buy_order).sum(), cancel.sum()
        ),
        "sell_share_of_cancels": safe_ratio(
            (cancel & sell_order).sum(), cancel.sum()
        ),
        "valid_two_sided_quote_ratio": float(
            market.attrs["valid_two_sided_quote_ratio"]
        ),
        "spread_mean": float(spread.mean()),
        "book_imbalance_mean": float(imbalance.mean()),
        "full_book_imbalance_mean": float(
            full_book_imbalance.mean()
        ),
        "within_100ms_trade_ratio": float(
            trade_intervals.lt(100).mean()
        ),
        "minute_count_cv": coefficient_of_variation(
            minute_count
        ),
        "raw_trade_interval_cv_diagnostic": (
            coefficient_of_variation(trade_intervals)
        ),
        "open_30_amount_ratio": safe_ratio(
            amount[open_30].sum(), total_amount
        ),
        "close_10_amount_ratio": safe_ratio(
            amount[close_10].sum(), total_amount
        ),
    }

features = build_level2_features(market, orders, trades)

for name, value in features.items():
    print(f"{name}: {value}")

raw_trade_interval_cv_diagnostic只用于展示未分时段计算造成的失真,不应直接作为建模特征。within_100ms_trade_ratio使用严格小于100毫秒的定义,当前样本结果约为65.35%。

4.3 实验结果

600009.SH20260626的成交规模统计如下:

指标 数值
成交笔数 29209
成交量 15024986股
成交金额 349450087.92元
平均每笔成交金额 11963.78元
主动买入金额占比 47.88%
主动卖出金额占比 52.12%
主动净方向 -4.24%

主动净方向略微为负,说明当日主动卖出金额略高于主动买入金额。该差异较小,整体仍接近双向均衡成交。

不同成交金额层级的结果如下:

成交层级 成交笔数 笔数占比 金额占比
小于4万元 27808 95.20% 53.17%
4万至10万元 963 3.30% 16.41%
10万至50万元 399 1.37% 21.07%
50万元以上 39 0.13% 9.35%

结果说明,小额成交贡献了95.20%的成交笔数,却只贡献53.17%的成交金额;50万元以上成交只有39笔,却贡献了9.35%的成交金额。

仅使用成交笔数容易让研究者认为市场几乎完全由小单主导。加入成交金额后,可以看到少数大额交易仍然具有显著影响。

委托事件统计如下:

指标 数值
新增委托 32101笔
撤销委托 12428笔
状态记录 4笔
撤单事件占全部委托事件 27.91%
买单撤销占全部撤单 59.73%
卖单撤销占全部撤单 40.27%

买单撤销数量高于卖单撤销数量。该结果可以描述订单流变化,但仍需结合撤单发生位置和撤单后的价格变化进行解释。

盘口统计如下:

指标 数值
有效一档盘口比例 99.94%
平均买卖价差 0.0119元
十档盘口平均失衡 -1.54%
全盘口总量平均失衡 -34.45%

十档失衡接近0,而叫买总量叫卖总量计算出的全盘口失衡明显为负。这证明十档挂单量与全盘口总量覆盖范围不同,两组字段具有互补信息。

时间结构统计如下:

指标 数值
开盘后30分钟成交金额占比 23.85%
收盘前10分钟成交金额占比 7.50%
相邻成交间隔小于100毫秒的占比 65.35%
每分钟成交笔数CV 0.667
原始成交间隔CV(诊断值) 46.08

原始成交间隔CV诊断值达到46.08,主要受到集合竞价、大量同时间戳成交和午间休市影响。这个结果说明,直接对全天成交时间计算间隔CV会产生严重失真,因此该值不进入默认建模特征。

更合理的处理方式是分别计算以下时间段:

text 复制代码
09:30:00至11:30:00
13:00:00至14:57:00
14:57:00至15:00:00

集合竞价成交也应单独统计,避免与连续竞价混合。

4.4 数据一致性验证

python 复制代码
trade_volume = trades["成交数量"].sum()
trade_amount = trades["trade_amount"].sum()
trade_count = len(trades)

raw_market = pd.read_csv(
    "20260626/600009.SH/行情.csv",
    encoding="gb18030",
)

print(raw_market["当日累计成交量"].iloc[-1], trade_volume)
print(raw_market["当日成交额"].iloc[-1], trade_amount)
print(raw_market["成交笔数"].iloc[-1], trade_count)

输出为:

text 复制代码
15024986      15024986
349450088     349450087.92
29209         29209

累计成交量、成交额和成交笔数均能与逐笔成交数据对应,说明三张表之间具有良好的一致性。

4.5 字段使用建议

字段等级 推荐字段 原因
第一优先级 成交价格、成交数量、BS标志 构造成交金额、主动方向和价格冲击
第一优先级 委托类型、委托代码、委托价格、委托数量 构造新增、撤单和订单流
第一优先级 十档买卖价量 构造价差、深度和盘口失衡
第一优先级 时间 构造交易节奏和时段特征
第二优先级 交易所委托号、叫买序号、叫卖序号 恢复订单生命周期
第二优先级 累计成交量、成交额、成交笔数 活跃度和数据校验
第二优先级 叫买总量、叫卖总量 完整盘口压力
第三优先级 开高低价、前收盘 日内收益和价格路径
删除候选 恒为0、全空或单一取值字段 无法提供样本区分信息

建模时应优先使用比例、差值、变化率、时间结构和交互特征。股票代码、日期、原始成交额等字段容易让模型记忆股票身份或流动性规模,需要谨慎处理。

五、总结

Level2数据由行情快照、逐笔委托和逐笔成交共同组成。行情快照描述订单簿状态,逐笔委托描述订单进入和退出市场的过程,逐笔成交描述买卖双方最终撮合的结果。

本文样本中最有价值的原始字段包括十档买卖价量、逐笔成交BS标志、成交价格、成交数量、委托类型、委托方向、委托价格、委托数量以及毫秒级时间。累计成交量、累计成交额和成交笔数与逐笔成交存在可推导关系,适合用于完整性校验。恒为0、全空和单一取值字段缺乏区分能力,可以在完成审计后删除。

数学指标能够把原始事件转换为可比较的行为特征。买卖价差描述交易成本,盘口失衡描述买卖深度差异,主动净方向描述成交发起方向,撤单事件占比描述委托事件构成,VWAP描述成交量加权均价,信息熵和变异系数描述交易节奏,短期有符号中间价响应描述成交方向与后续价格变化的一致程度。

Level2可以帮助研究者观察交易行为,却无法直接提供交易者身份。大额成交、小额高频、快速撤单和盘口失衡都需要结合市场状态进行解释。有效的Level2研究应从字段口径、交易机制和数据质量出发,再进入特征工程和模型训练。

参考文献

  1. Stoikov, S. The Micro-Price: A High Frequency Estimator of Future Prices. Quantitative Finance, 2018. DOI: https://doi.org/10.1080/14697688.2018.1489139
  2. Cont, R., Kukanov, A., & Stoikov, S. The Price Impact of Order Book Events. Journal of Financial Econometrics, 2014. https://arxiv.org/abs/1011.6402
  3. 上海证券交易所:《上海证券交易所交易规则(2023年修订)》。https://www.sse.com.cn/lawandrules/sselawsrules2025/trade/universal/c/c_20250612_10781695.shtml
  4. 深圳证券交易所:《深圳证券交易所交易规则(2026年修订)》。https://docs.static.szse.cn/www/lawrules/rule/trade/current/W020260424690713155663.pdf