第一章:暗池交易数据获取——数据源介绍与清洗实战
大家好,我是老张。在量化交易这行摸爬滚打了十几年,暗池交易一直是我最着迷的领域。今天咱们开始《暗池交易对手分析实战》的第一章,聊聊数据获取这件事。
说实话,很多人一上来就问我策略怎么写、模型怎么搭。但我得泼盆冷水——没有干净的数据,再牛的策略也是白搭。我自己就吃过这个亏,2018年做美股暗池分析时,因为数据没对齐,白白浪费了三个月。
1.1 数据源介绍:FINRA、TRACE、自建数据
暗池交易的数据源,说白了就三大类。我一个个说。
1.1.1 FINRA数据
FINRA(美国金融业监管局)是官方数据源。它提供的是暗池交易的汇总数据,比如每天的总成交量、加权平均价。注意,是汇总数据,不是逐笔数据。
核心特点:
- 公开免费,每天更新
- 包含ATS(另类交易系统)的日度汇总
- 数据粒度:按股票、按天
- 延迟:T+1
我个人习惯用FINRA数据做宏观分析。比如判断某只股票在暗池里的交易占比是否异常。但你要做精细的对手分析,光靠这个不够。
1.1.2 TRACE数据
TRACE是债券市场的。嗯,你可能觉得债券跟暗池没关系。但别忘了,很多暗池也交易债券衍生品。TRACE提供的是公司债和市政债的交易报告。
| 数据源 | 覆盖范围 | 更新频率 | 费用 |
|---|---|---|---|
| FINRA | 美股暗池 | T+1 | 免费 |
| TRACE | 公司债/市政债 | 实时/日度 | 部分免费 |
| 自建数据 | 自定义 | 实时 | 高 |
TRACE数据有个坑——它只报告公开交易。暗池里的债券交易,很多是私下协商的,TRACE不一定能抓到。我在做跨资产套利时吃过这个亏,后来学乖了。
1.1.3 自建数据
这才是重头戏。自建数据,说白了就是你自己去接交易所的原始数据流,或者从券商、数据商那里买。
为什么需要自建?因为FINRA和TRACE都是汇总数据,你看不到对手是谁。自建数据可以做到逐笔级别,甚至能看到订单的路径。
我的建议:刚开始别急着自建。先用FINRA免费数据跑通流程,等策略验证了再投入资源。我见过太多人一上来就花几十万买数据,结果策略根本跑不通。
1.2 数据清洗与预处理
数据拿到手,第一件事不是分析,是清洗。我敢说,80%的量化新手都栽在这一步。
暗池数据常见的脏数据有:
- 缺失值:某天某只股票没有交易记录
- 异常值:成交量突然暴增100倍
- 重复记录:同一笔交易被上报两次
- 时间戳错误:凌晨3点的交易记录(美股交易时间是9:30-16:00)
我曾经遇到过一个案例:某只股票在FINRA数据里显示暗池成交量是正常值的50倍。查了半天,原来是数据商把某笔大额交易重复上报了3次。如果不清洗,你的策略会完全跑偏。
下面是我常用的清洗代码:
import pandas as pd
import numpy as np
def clean_darkpool_data(df):
# 1. 删除时间戳异常的数据
df = df[(df['timestamp'].dt.hour >= 9) &
(df['timestamp'].dt.hour <= 16)]
# 2. 处理缺失值:用前一天的均值填充
df['volume'] = df['volume'].fillna(method='ffill')
# 3. 剔除异常值:超过3倍标准差
mean_vol = df['volume'].mean()
std_vol = df['volume'].std()
df = df[df['volume'] <= mean_vol + 3 * std_vol]
# 4. 去重
df = df.drop_duplicates(subset=['ticker', 'timestamp'])
return df
注意:清洗不是越狠越好。我曾经把一些真实的机构大单当成异常值剔除了,结果策略信号全没了。建议先做可视化,看看数据分布再决定阈值。
1.3 时间对齐与聚合
暗池数据的时间戳是个大问题。不同数据源的时间精度不一样:FINRA是日度数据,TRACE是秒级,自建数据可能是微秒级。
怎么对齐?我一般用两种方法:
- 按分钟聚合:把所有数据都聚合到分钟级别。适合做日内策略。
- 按事件对齐:以某只股票的大单成交为基准,对齐其他数据。适合做事件驱动策略。
举个例子,你想分析某只股票在暗池和公开市场的价差。公开市场数据是逐笔的,暗池数据是分钟级的。怎么办?
# 将公开市场数据按分钟聚合
public_minute = public_data.resample('1T', on='timestamp').agg({
'price': 'mean',
'volume': 'sum'
})
# 将暗池数据也按分钟聚合
dark_minute = dark_data.resample('1T', on='timestamp').agg({
'price': 'vwap', # 成交量加权平均价
'volume': 'sum'
})
# 合并
aligned = pd.merge(public_minute, dark_minute,
left_index=True, right_index=True,
suffixes=('_public', '_dark'))
这里有个细节:暗池的加权平均价(VWAP)比简单平均价更有意义。因为暗池里大单多,简单平均会被小单带偏。
1.4 数据存储方案
数据清洗完了,存哪儿?这是个被严重低估的问题。
我见过有人把所有数据存成CSV文件,结果查询一次要等10分钟。也见过有人直接用Excel,打开文件就卡死。
我的建议是分层存储:
| 层级 | 存储方式 | 用途 | 示例 |
|---|---|---|---|
| 原始层 | Parquet + 压缩 | 备份,不常访问 | FINRA原始数据 |
| 清洗层 | SQLite / DuckDB | 日常分析 | 清洗后的分钟数据 |
| 特征层 | NumPy数组 / HDF5 | 模型训练 | 计算好的技术指标 |
我个人最常用的是DuckDB。它轻量、快,而且支持SQL查询。对于几千万条级别的暗池数据,查询速度比Pandas快10倍以上。
import duckdb
# 创建数据库连接
con = duckdb.connect('darkpool.duckdb')
# 创建表并导入数据
con.execute('''
CREATE TABLE dark_trades AS
SELECT * FROM read_parquet('cleaned_data/*.parquet')
''')
# 查询某只股票某天的暗池交易
result = con.execute('''
SELECT timestamp, volume, price
FROM dark_trades
WHERE ticker = 'AAPL'
AND timestamp BETWEEN '2024-01-01' AND '2024-01-31'
ORDER BY timestamp
''').fetchdf()
避坑指南:千万别把所有数据都塞进内存。暗池数据量很大,一天的美股暗池数据就有几个GB。我刚开始做的时候,直接把所有数据load进Pandas,结果内存爆了。后来改用DuckDB的流式查询,问题就解决了。
本章知识体系
下面这张图是我自己画的,把本章的核心逻辑串起来了:
这张图把整个流程串起来了。从数据源到清洗,再到对齐和存储,每一步都有坑。但只要你按这个流程走,至少能避开80%的常见问题。
好了,第一章就到这里。数据是暗池分析的基石,这块打牢了,后面讲策略、讲对手分析才能站得住脚。下一章咱们聊聊如何从清洗后的数据里提取有用的特征。
公众号:蓝海资料掘金营,微信deep3321