高频交易数据与流动性特征
各位同学,今天我们来聊聊债券交易中最底层、也最刺激的东西——高频数据。
说实话,我刚入行那会儿,觉得债券交易就是看看收益率曲线,听听宏观消息。直到有一次,我在做日内策略回测时,发现模型在上午10点和下午3点的表现天差地别。这才意识到,流动性在一天之内是活的,不是死的。
这一章,我们就来拆解Tick级数据、逐笔成交与委托、日内流动性模式,以及那些让人头疼的数据清洗问题。
Tick级数据结构:最细颗粒度的战场
什么是Tick级数据?说白了,就是每一笔成交或每一次报价变动,都记录下来的数据。在债券市场,尤其是国债期货和活跃券,Tick数据能精确到毫秒甚至微秒。
我习惯把Tick数据分成两类:
- 成交Tick:每一笔实际成交的价格、数量、时间
- 报价Tick:买卖盘口的每一次变动,包括最优买卖价和深度
一个典型的成交Tick长这样:
时间戳 价格 数量 方向
2024-01-15 09:30:01.123 102.35 1000 B
2024-01-15 09:30:01.456 102.36 500 S
2024-01-15 09:30:02.001 102.35 2000 B
注意那个时间戳,精度到毫秒。为什么这么重要?因为在高频世界里,谁先看到数据,谁就赢了一半。
核心要点:Tick数据是流动性的“心电图”。每一笔成交都在告诉你,当前市场是愿意买还是愿意卖,愿意以什么价格成交多少量。
逐笔成交与逐笔委托:看穿市场的“底牌”
逐笔成交数据,就是上面说的成交Tick。但逐笔委托数据,是更高级的东西——它告诉你谁在挂单,挂了多少,什么时候撤单。
举个例子。假设你看到逐笔成交显示:
- 09:30:01 成交1000手,价格102.35
- 09:30:02 成交2000手,价格102.35
光看这个,你可能觉得“嗯,有人在买”。但如果你同时看逐笔委托:
- 09:30:00 买方挂单5000手,价格102.35
- 09:30:01 吃掉1000手,还剩4000手
- 09:30:02 又吃掉2000手,还剩2000手
这时候你就能判断:这个买方是真心想买,不是虚晃一枪。我曾经在交易中吃过亏,只看成交不看委托,结果被大单诱多给骗了。
我的经验:逐笔委托数据能帮你识别“虚假流动性”。有些做市商会在盘口挂大单,但一有人吃就撤单。这种“幽灵单”在Tick数据里根本看不出来,只有逐笔委托才能暴露。
日内流动性模式:U型曲线
这是债券交易员必须刻在脑子里的东西。
什么叫U型曲线?就是流动性在开盘和收盘时最好,中午最差。画出来像个字母U。
为什么会这样?
- 开盘:隔夜信息集中释放,交易员们急着调整仓位
- 中午:大家都在吃饭、休息,交易量骤降
- 收盘:机构要平头寸、做估值,交易再次活跃
我做过一个统计,在活跃国债期货上,开盘后30分钟和收盘前30分钟的交易量,占了全天的40%以上。中间那两小时,买卖价差能扩大一倍。
下面这张图,是我用真实数据画的日内流动性模式:
注意:U型曲线不是一成不变的。在数据发布日、央行议息日,流动性模式会被彻底打乱。我曾经在非农数据公布那天,看到中午流动性突然飙升——因为大家都在等数据。
事件驱动下的流动性突变
说到事件驱动,这是高频交易里最刺激的部分。
流动性突变,通常发生在以下几种情况:
- 宏观数据发布:CPI、GDP、PMI,数据一出,流动性瞬间消失或暴增
- 央行操作:降息、降准、公开市场操作,市场情绪瞬间逆转
- 突发事件:黑天鹅事件,比如疫情、战争、违约
我记得有一次,某只信用债突然传出违约消息。前一秒还挂着的买盘,后一秒全撤了。买卖价差从2个bp直接跳到50个bp。如果你还在用之前的流动性模型做交易,那真是要亏到姥姥家。
怎么应对?我的做法是:在事件窗口期,降低交易频率,或者干脆暂停。等流动性恢复稳定了再进场。
数据清洗与预处理实战
好了,前面讲了这么多理论,现在来点实际的。高频数据清洗,是每个量化交易员的必修课。
我见过太多人,模型建得漂漂亮亮,结果数据一进去就崩了。为什么?因为原始数据太脏了。
常见的坑有:
| 问题类型 | 具体表现 | 我的处理方法 |
|---|---|---|
| 时间戳错乱 | 后一笔成交的时间戳比前一笔还早 | 按时间排序,删除异常点 |
| 价格异常 | 成交价偏离市场均价超过3个标准差 | 用中位数过滤,或者直接剔除 |
| 重复数据 | 同一笔成交被记录两次 | 按时间+价格+数量去重 |
| 缺失数据 | 某段时间内完全没有成交 | 用前一笔报价填充,或者标记为缺失 |
下面是我常用的一个清洗流程,用Python写的:
import pandas as pd
import numpy as np
def clean_tick_data(df):
"""
清洗Tick级成交数据
"""
# 1. 排序
df = df.sort_values('timestamp')
# 2. 去重
df = df.drop_duplicates(subset=['timestamp', 'price', 'volume'])
# 3. 过滤价格异常
mean_price = df['price'].median()
std_price = df['price'].std()
df = df[(df['price'] > mean_price - 3*std_price) &
(df['price'] < mean_price + 3*std_price)]
# 4. 填充缺失时间戳(可选)
df = df.set_index('timestamp')
df = df.resample('1S').ffill() # 按秒填充
return df
避坑指南:我曾经在清洗数据时,直接用均值填充缺失值,结果把流动性低谷期的数据全给抹平了。后来改用前向填充,才保留了真实的流动性特征。记住:不要用统计量去填充高频数据,要用市场行为去填充。
最后说一句,数据清洗不是一次性的工作。每次拿到新数据,我都会先跑一遍可视化,看看有没有明显的异常。比如价格跳空、成交量突变,这些在图表上一眼就能看出来。
好了,这一章的内容就到这里。Tick数据、逐笔委托、U型曲线、事件驱动、数据清洗——这些是高频交易的基础,也是你理解债券流动性的起点。下一章,我们会深入讨论如何用这些数据构建交易信号。