You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Python存储L2逐笔数据的方案探讨

针对你处理L2 tick数据遇到的存储和后续关联分析问题,结合行业通用做法和你的技术栈(h5py/vaex),给你几个实用的解决方案:

1. 嵌套列存储(对齐KDB的思路,保留快照完整性)

这和你提到的KDB存储方式完全契合,利用HDF5支持嵌套/可变长度数组的特性,把每个订单簿快照作为一行,其中bid/ask的档位数据以数组形式存储在对应列中。

用h5py实现的话,可以定义复合dtype来描述快照结构:

import h5py
import numpy as np

# 定义包含嵌套数组的复合dtype
book_dtype = np.dtype([
    ("book_time", "datetime64[us]"),
    ("sym", "U8"),
    ("bid_times", "O"),  # 存储可变长度的时间数组
    ("bid_prices", "O"),
    ("bid_sizes", "O"),
    ("ask_times", "O"),
    ("ask_prices", "O"),
    ("ask_sizes", "O"),
])

# 构造单条快照数据
snapshot = np.array(
    (
        np.datetime64("2017-11-06T14:57:08.532152"),
        "EURUSD",
        np.array(["20171106-14:57:08.528", "20171106-14:57:08.428"], dtype="<U21"),
        np.array([1.30699, 1.30698]),
        np.array([100000.0, 250000.0]),
        np.array(["20171106-14:57:08.528"], dtype="<U21"),
        np.array([1.30709]),
        np.array([100000.0]),
    ),
    dtype=book_dtype
)

# 写入HDF5
with h5py.File("l2_book.h5", "a") as f:
    if "book_snapshots" not in f:
        f.create_dataset("book_snapshots", data=[snapshot], dtype=book_dtype, maxshape=(None,))
    else:
        ds = f["book_snapshots"]
        ds.resize(ds.shape[0] + 1, axis=0)
        ds[-1] = snapshot

vaex可以直接读取这种嵌套结构,后续做asof join时,以book_time为关联键即可,完全满足你的需求。这种方式的优势是能完整保留订单簿快照的结构,适合做订单簿回放类分析。

2. 长表格式(行业主流,最适合关联分析)

这是量化领域处理L2数据最常用的方式:把每个档位拆成单独的一行,用book_time标识所属的快照,level标记档位序号,side区分买卖盘。数据结构如下:

book_timesymsidelevelquote_timepricesize
2017-11-06 14:57:08.532152EURUSDbid12017-11-06 14:57:08.5281.30699100000.0
2017-11-06 14:57:08.532152EURUSDbid22017-11-06 14:57:08.4281.30698250000.0
2017-11-06 14:57:08.532152EURUSDask12017-11-06 14:57:08.5281.30709100000.0

用vaex实现追加写入非常简单:

import vaex

# 构造单快照展开后的行数据
update_rows = [
    {
        "book_time": np.datetime64("2017-11-06T14:57:08.532152"),
        "sym": "EURUSD",
        "side": "bid",
        "level": 1,
        "quote_time": np.datetime64("2017-11-06T14:57:08.528"),
        "price": 1.30699,
        "size": 100000.0
    },
    {
        "book_time": np.datetime64("2017-11-06T14:57:08.532152"),
        "sym": "EURUSD",
        "side": "bid",
        "level": 2,
        "quote_time": np.datetime64("2017-11-06T14:57:08.428"),
        "price": 1.30698,
        "size": 250000.0
    },
    {
        "book_time": np.datetime64("2017-11-06T14:57:08.532152"),
        "sym": "EURUSD",
        "side": "ask",
        "level": 1,
        "quote_time": np.datetime64("2017-11-06T14:57:08.528"),
        "price": 1.30709,
        "size": 100000.0
    }
]

# 追加写入HDF5
df = vaex.from_records(update_rows)
df.export_hdf5("l2_long_format.h5", mode="a")

这种格式的最大优势是完美适配列存储和asof join:后续和交易数据关联时,直接用vaex.join.asof()按book_time和sym关联即可,逻辑清晰且性能优异。而且不需要处理不规则数组,数据结构简单易维护,是绝大多数量化系统的首选。

3. 固定长度数组+掩码(折中方案,兼顾性能和空间)

如果既想保留快照的行式结构,又不想用嵌套数组,可以用固定长度数组(比如20档)存储所有档位,再用掩码列标记有效档位:

import h5py
import numpy as np

# 定义带掩码的固定长度dtype
fixed_book_dtype = np.dtype([
    ("book_time", "datetime64[us]"),
    ("sym", "U8"),
    ("bid_times", "datetime64[us]", (20,)),
    ("bid_prices", "f8", (20,)),
    ("bid_sizes", "f8", (20,)),
    ("bid_valid", "?", (20,)),  # 标记该档位是否有效
    ("ask_times", "datetime64[us]", (20,)),
    ("ask_prices", "f8", (20,)),
    ("ask_sizes", "f8", (20,)),
    ("ask_valid", "?", (20,)),
])

# 构造快照数据,无效档位用NaN填充,掩码设为False
snapshot = np.zeros(1, dtype=fixed_book_dtype)[0]
snapshot["book_time"] = np.datetime64("2017-11-06T14:57:08.532152")
snapshot["sym"] = "EURUSD"
# 填充bid档位
snapshot["bid_times"][:2] = [np.datetime64("2017-11-06T14:57:08.528"), np.datetime64("2017-11-06T14:57:08.428")]
snapshot["bid_prices"][:2] = [1.30699, 1.30698]
snapshot["bid_sizes"][:2] = [100000.0, 250000.0]
snapshot["bid_valid"][:2] = True
# 填充ask档位
snapshot["ask_times"][0] = np.datetime64("2017-11-06T14:57:08.528")
snapshot["ask_prices"][0] = 1.30709
snapshot["ask_sizes"][0] = 100000.0
snapshot["ask_valid"][0] = True

# 写入HDF5
with h5py.File("l2_fixed_mask.h5", "a") as f:
    if "book_snapshots" not in f:
        f.create_dataset("book_snapshots", data=[snapshot], dtype=fixed_book_dtype, maxshape=(None,))
    else:
        ds = f["book_snapshots"]
        ds.resize(ds.shape[0] + 1, axis=0)
        ds[-1] = snapshot

这种方案空间开销可控(20档的空值占用很小),数据结构规整,h5py/vaex处理性能优异,适合对速度要求较高的场景。

总结与选型建议

  • 如果你的核心需求是和交易数据做asof join关联分析,优先选长表格式,这是行业通用的最优解,操作简单且性能拉满。
  • 如果需要频繁还原完整订单簿快照(比如做订单簿回放、流动性分析),选嵌套列存储,和KDB的思路对齐,能完整保留快照结构。
  • 如果追求极致性能且档位波动不大,选固定长度数组+掩码,兼顾空间和速度。

不需要拆成120列那么繁琐,以上三种方案都能完美解决你的问题。

内容的提问来源于stack exchange,提问作者mkst

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 14:32:58