3. 数据获取与清洗:用Python搞定Level-2数据
做订单流分析,数据就是你的弹药库。弹药不行,枪法再好也白搭。
我刚开始接触Level-2数据时,踩过不少坑。有一次回测策略表现特别好,我差点就实盘上了。结果仔细一查,发现是时间戳没对齐,把未来的数据当成了历史信号。嗯,那感觉就像考试抄答案抄错了行。
今天咱们就聊聊,怎么用Python把交易所的Level-2数据拿下来,再把它收拾得服服帖帖。
3.1 Level-2数据长什么样?
先搞清楚我们要处理什么。Level-2数据比普通行情丰富得多,主要包括这几类:
| 数据类型 | 字段示例 | 更新频率 |
|---|---|---|
| 逐笔成交 | 时间、价格、成交量、买卖方向 | 每笔成交即更新 |
| 十档行情 | 买1~买10价量、卖1~卖10价量 | 约3秒一次快照 |
| 委托队列 | 买1/卖1的前50笔委托明细 | 随盘口变化更新 |
| 逐笔委托 | 委托时间、价格、数量、委托编号 | 每笔委托即更新 |
我个人习惯把逐笔成交和十档行情作为核心数据源。为什么?因为订单流分析最依赖的就是这两类数据——成交数据告诉你资金在动,盘口数据告诉你资金在哪儿等着。
3.2 数据获取:从交易所到本地
获取Level-2数据主要有两条路:
- 直接接入交易所:上交所用L1/L2行情网关,深交所用MDGW。这条路门槛高,适合机构。
- 通过数据服务商:比如万得、聚宽、Tushare、AkShare等。个人开发者首选。
我用AkShare比较多,免费且接口丰富。下面是一个获取逐笔成交数据的例子:
import akshare as ak
import pandas as pd
# 获取某只股票的逐笔成交数据
# 注意:这里需要你有Level-2权限
stock_code = "600519" # 贵州茅台
trade_date = "2024-01-15"
# 获取逐笔成交
df_tick = ak.stock_individual_tick_em(
symbol=stock_code,
start_date=trade_date,
end_date=trade_date
)
print(df_tick.head())
print(f"数据量:{len(df_tick)} 条")
输出大概长这样:
时间 价格 成交量 买卖方向
0 09:30:00.123 1680.00 100 买
1 09:30:00.456 1680.02 200 卖
2 09:30:00.789 1680.01 150 买
...
3.3 时间戳对齐:最容易被忽视的坑
为什么时间戳对齐这么重要?
你想想看,订单流分析本质上是在做"时间序列上的因果推断"。如果逐笔成交的时间戳和盘口快照的时间戳对不上,你可能会把成交归到错误的盘口状态上。
举个例子:
- 09:30:01.000 盘口显示买1是1000手
- 09:30:01.500 成交了500手
- 09:30:02.000 盘口显示买1变成了500手
如果时间戳没对齐,你可能会以为成交发生在盘口更新之后,那就完全搞反了因果关系。
对齐的方法其实不复杂:
# 假设 df_trade 是逐笔成交,df_quote 是盘口快照
# 统一时间戳精度到毫秒
# 1. 把时间列转为datetime类型
df_trade['time'] = pd.to_datetime(df_trade['时间'], format='%H:%M:%S.%f')
df_quote['time'] = pd.to_datetime(df_quote['时间'], format='%H:%M:%S.%f')
# 2. 设置时间索引
df_trade.set_index('time', inplace=True)
df_quote.set_index('time', inplace=True)
# 3. 使用merge_asof进行"最近邻"对齐
# 注意:direction='backward'表示成交对齐到最近的盘口快照
df_aligned = pd.merge_asof(
df_trade,
df_quote,
left_index=True,
right_index=True,
direction='backward',
tolerance=pd.Timedelta('3s') # 最多允许3秒偏差
)
print(f"对齐后数据量:{len(df_aligned)} 条")
print(f"缺失值数量:{df_aligned.isnull().sum().sum()}")
3.4 去重:别让重复数据毁了你的分析
Level-2数据在传输过程中,偶尔会出现重复推送。尤其是网络不稳定的时候,同一个成交记录可能收到两次。
去重的方法很简单,但要注意选对去重字段:
# 逐笔成交去重:用"时间+价格+成交量+买卖方向"作为唯一标识
df_trade_dedup = df_trade.drop_duplicates(
subset=['时间', '价格', '成交量', '买卖方向'],
keep='first'
)
# 盘口快照去重:用"时间"作为唯一标识
df_quote_dedup = df_quote.drop_duplicates(
subset=['时间'],
keep='last' # 保留最后一条,因为盘口数据是快照
)
print(f"去重前:{len(df_trade)} 条")
print(f"去重后:{len(df_trade_dedup)} 条")
print(f"去除了 {len(df_trade) - len(df_trade_dedup)} 条重复数据")
我曾经遇到过一种特殊情况:同一个成交记录,价格和成交量都一样,但买卖方向不同。这种情况不是重复,而是真实的"多空对决"。所以去重时一定要把买卖方向也考虑进去。
3.5 缺失值处理:填还是不填?
缺失值在Level-2数据里很常见。比如盘口快照更新频率是3秒一次,但成交可能每秒发生几十笔。对齐之后,很多成交记录对应的盘口字段就是空的。
处理策略取决于你要做什么:
| 场景 | 推荐策略 | 原因 |
|---|---|---|
| 计算订单流不平衡 | 向前填充(ffill) | 用最近的盘口状态来估算 |
| 回测策略信号 | 删除缺失行 | 避免使用不完整的数据做决策 |
| 实时监控 | 插值填充 | 保持数据连续性 |
代码实现:
# 向前填充:用上一个盘口数据填充当前缺失
df_aligned['买1价'].fillna(method='ffill', inplace=True)
df_aligned['卖1价'].fillna(method='ffill', inplace=True)
# 或者直接删除有缺失的行
df_clean = df_aligned.dropna(subset=['买1价', '卖1价', '价格'])
print(f"填充前缺失:{df_aligned.isnull().sum().sum()}")
print(f"填充后缺失:{df_aligned.fillna(method='ffill').isnull().sum().sum()}")
3.6 完整的数据清洗流水线
把上面这些步骤串起来,就是一个完整的数据清洗流水线:
def clean_level2_data(df_trade, df_quote):
"""
清洗Level-2数据流水线
"""
# 1. 时间戳标准化
df_trade['time'] = pd.to_datetime(df_trade['时间'], format='%H:%M:%S.%f')
df_quote['time'] = pd.to_datetime(df_quote['时间'], format='%H:%M:%S.%f')
# 2. 去重
df_trade = df_trade.drop_duplicates(subset=['时间', '价格', '成交量', '买卖方向'])
df_quote = df_quote.drop_duplicates(subset=['时间'])
# 3. 排序(时间戳对齐前必须排序)
df_trade.sort_values('time', inplace=True)
df_quote.sort_values('time', inplace=True)
# 4. 时间戳对齐
df_aligned = pd.merge_asof(
df_trade,
df_quote,
left_index=True,
right_index=True,
direction='backward',
tolerance=pd.Timedelta('3s')
)
# 5. 缺失值处理
df_aligned.fillna(method='ffill', inplace=True)
# 6. 删除开盘集合竞价数据(通常不需要)
df_aligned = df_aligned[df_aligned['time'] >= '09:30:00']
return df_aligned
# 使用示例
df_clean = clean_level2_data(df_trade, df_quote)
print(f"清洗完成,共 {len(df_clean)} 条有效数据")
3.7 数据清洗的核心逻辑
下面这张图展示了整个数据清洗的流程,我建议你保存下来,以后做数据清洗时对照着看:
数据清洗这件事,说白了就是"垃圾进,垃圾出"。你花再多时间在策略上,如果数据本身有问题,结果都是白费。
我个人习惯在清洗完数据后,做一个简单的质量检查:统计一下缺失率、重复率、时间戳间隔分布。如果发现异常,宁可重新获取数据,也不要凑合着用。
公众号:蓝海资料掘金营,微信deep3321