200倍速!基于 HDF5 的证券数据存储
去年 15 日的笔记挖了个坑,给出了量化数据和因子的存储方案技术导图。这一篇笔记就开始填坑。
即使我们购买了在线数据服务,比如 tushare, 聚宽的账号,我们仍然要构建自己的本地存储,为什么?
原因如下:
- 在线数据服务一般都有 quota 限制(比如聚宽)。如果我们要频繁地进行回测,很容易用尽 quota。
- 如果我们在回测时,使用的数据要从远程服务器上获取,网络延时会大大影响到回测速度。
- 即使在线数据服务提供了因子库(比如 Alpha 101, Alpha 191 因子库),但是,真正能战胜市场的因子,必然来自于你自己的独特研究,所以, 我们也要解决自己提取的因子如何存储的问题,这也决定了我们仍然要构建自己的本地存储。
这一篇笔我们介绍 HDF5,这是最适合个人交易者的入门方案。HDF5 可以处理相当大容量的数据。
根据 Kesheng Wu 等人的研究,他们分析 2007 年到 2012 年 7 月间的期货交易数据,以构建 VPIN 因子时,处理的交易数据达 30 亿条,CSV 文件的大小达到了 140GB。当使用这么多数据进行 VPIN 计算时,使用 CSV 文件的时间是 142 秒;将其转换成 HDF5 格式后,同样的计算则只花了 0.4 秒,HDF5快了200多倍!
HDF(Hierarchical Data Format)是一种为存储和处理大容量科学数据设计的文件格式及相应库文件。最早由 NCSA (美国国家超级计算应用中心) 研究开发,现由非盈利组织 HDF Group 维护。最新的格式是第 5 版,因此就被称为 HDF5。HDF 最初提供了多种数据类型,比如光栅图像和注解等,而在第 5 版中,简化到只支持科学数据集(即同质多维数组)和群组两种类型。
HDF5 官方版本是基于 C/C++ 的,但也像 MATLAB、Java、Python、R 和 Julia 这些语言也有自己版本的 HDF5 API。
作为量化人,我们使用最多的是 h5py。
HDF5 的基本概念¶
HDF5 使用文件来存储数据。它包含两种对象,即数据集(这是一种类似于数组的数据集合)和群组(类似于文件夹,其下可包含数据集 datasets 和其它群组)。我们可以这样来理解 h5py 中的基本对象:
群组象字典一样工作,而数据集则像 Numpy 数组一样工作。
h5py 的 API 比较基础,用法并不复杂。但作为行情和因子数据库,我们需要自己构建一些功能。下面的代码,将演示我们如何存储行情数据,并实现每日更新:
1 2 3 4 5 6 7 |
|
在上述代码中,我们按行情的周期频率,分别创建了若干个 group。接下来的行情数据 就会以证券代码为 name,存储在这些 group 中。
1 2 3 4 5 6 |
|
我们使用 omicron 来获取行情数据。它返回的行情数据中,有一个 frame 字段,数据类型为 np.datetime64,是 h5py 不能处理的,因此我们将它转换为 epoch 时间来保存。h5py 也允许我们直接保存时间类型(以不透明的方式),但这样一来,就不再支持一些查询操作了。
我们使用下述方法来把新的数据追加到之前的数据集中:
1 2 3 4 5 6 7 8 9 10 11 |
|
这里还展示了获取数据集的方法 get,创建数据集的方法 create_dataset 和 resize 方法。为了支持 resize,我们在创建数据集时,还必须声明 chunks = True 以及 amxshape=(None,)。
添加新的数据是以切片语法来完成的。现在,我们来实现每日追加数据的功能:
1 2 3 4 5 |
|
最后,我们写一个显示其数据结构的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
我们将得到类似下面的输出:
1 2 3 4 5 6 |
|
这样,使用 hdf5 来存储行情数据的基本结构就完成了。我们可以用下面的方法进行查询:
1 2 |
|
我们模拟了一个 40 年分钟线的数据集,在进行上述查询时,需要执行 0.2 秒。
上述存储结构有一个不利之处,就是在查询横截面数据(比如,要查询某个时间点上所有个股的收盘价)时,速度会比较慢,因为循环难以避免。hdf5 提供了 virtual dataset 这一功能,但它并不适合可变长的数据集。
办法之一是,在存储行情数据时,把个股的代码也存储进来。当然,为了加快查询速度,我们需要将以字符串表示的证券代码,事先转换成为整数格式。比如,象 000001.XSHE 这样的代码,我们可以转换成为 100001,600000.XSHG 转换为 2600000:即使用 7 位数证券代码,第 1 位非零,为交易所编码。
但这种情况下,由于数据集变大,我们执行查询的速度也会变长。因此我们在设计时,要综合考虑两种场景的使用占比。
Need for speed!¶
觉得 h5py 还不够快?!那么,你可以考虑使用 Parallel HDF5。这是在 ubuntu 上安装它的步骤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
关于并行版本的 h5py 的使用,可以参考这个链接。无法查看外链的,可以 google 大富翁量化,在官网上查看同名文章和示例代码。
Need even more speed!¶
尽管 hdf5 提供了存储和访问海量数据的能力,但是,它的文档、及任何一本教程都不会告诉你,它不能提供基于索引的查询。随着你的数据集越来越大,查询速度将会以线性增加的方式变慢。
Tip
当我们进行技术选型时,我们总是希望全面了解一门技术的优势和缺陷。但是,官方文档常常只介绍它的优点,对其缺点则避而不谈。奇怪的是,第三方的教程也往往对它的缺陷也往往闪烁其辞。原因可能是,一旦他们谈及了这门技术的缺陷,就可能导致读者失去学习的兴趣,进而降低该教程的流量。这也许是你应该常读我的博文的原因 -- 我并不希望你因为我的文章,选择了并不恰当的技术,在浪费了一段时间后,不得不从头再来选择新的技术。
解决方案之一是将数据切分为多个dataset,每个 dataset 的名字与时间关联起来,这样在进行与时间相关的查询时,就可以获得较好的加速。另一个方法是使用 fastquery,它建立了一个基于 bitmap 的索引。不过我并没有看到现成的 python 库。
另一个方案可能是,仅仅将 hdf5 作为基础的持久化设施,而对它的查询操作,全部通过 modin -- 这是 pandas 的平替,可以载入远大于物理内存的文件--来操作数据集。
当然,从我们的测试来看,如果你只需要处理到日线级别的行情数据,那么直接使用无索引的查询,速度上是没有任何问题的。所以,这是对个人研究者而言,最简单的方式,就放在本系列第一篇中进行推荐。
如果你希望存储到分钟级的数据,还要保持非常好的查询速度,怎么办?这就 pyarrow + parquet 可以发力的地方,我们将在下一篇里介绍。
本文附有h5py使用示例代码。notebook可以在大富翁量化网站获得。如果🔗无法显示,可以google大富翁量化。
Tip
- hdf5是一种存储和处理大容量科学数据的文件格式及相应库
- hdf5的读写速度是csv的数百倍。在240M记录中进行查找,速度约为0.2秒。
- 读取hdf5文件的python库是h5py
- 行情数据应该按周期创建群组。子数据集以证券代码为key。
- 介绍了创建、查询和追加行情数据到hdf5文件中。
- 增强hdf5性能的几种方式。