第一章:暗池交易数据获取——数据源介绍与清洗实战

大家好,我是老张。在量化交易这行摸爬滚打了十几年,暗池交易一直是我最着迷的领域。今天咱们开始《暗池交易对手分析实战》的第一章,聊聊数据获取这件事。

说实话,很多人一上来就问我策略怎么写、模型怎么搭。但我得泼盆冷水——没有干净的数据,再牛的策略也是白搭。我自己就吃过这个亏,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是秒级,自建数据可能是微秒级。

怎么对齐?我一般用两种方法:

  1. 按分钟聚合:把所有数据都聚合到分钟级别。适合做日内策略。
  2. 按事件对齐:以某只股票的大单成交为基准,对齐其他数据。适合做事件驱动策略。

举个例子,你想分析某只股票在暗池和公开市场的价差。公开市场数据是逐笔的,暗池数据是分钟级的。怎么办?

# 将公开市场数据按分钟聚合
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的流式查询,问题就解决了。

本章知识体系

下面这张图是我自己画的,把本章的核心逻辑串起来了:

暗池交易数据获取流程 FINRA数据 TRACE数据 自建数据 数据清洗与预处理 缺失值处理 → 异常值剔除 → 去重 → 时间戳校正 时间对齐与聚合 分钟聚合 / 事件对齐 / VWAP计算 数据存储方案 原始层(Parquet) → 清洗层(DuckDB) → 特征层(HDF5) 公众号:蓝海资料掘金营

这张图把整个流程串起来了。从数据源到清洗,再到对齐和存储,每一步都有坑。但只要你按这个流程走,至少能避开80%的常见问题。

好了,第一章就到这里。数据是暗池分析的基石,这块打牢了,后面讲策略、讲对手分析才能站得住脚。下一章咱们聊聊如何从清洗后的数据里提取有用的特征。


公众号:蓝海资料掘金营,微信deep3321