(P)oint-(I)n-(T)ime 数据库

简介

在进行任何类型的历史市场分析时,点-in-时间(Point-in-time)数据都是一个非常重要的考虑因素。

例如,假设我们正在回测一个交易策略,并使用过去五年的历史数据作为输入。我们的模型假设每天交易一次,在收盘时进行交易,假设我们在回测中计算 2020 年 1 月 1 日的交易信号。此时,我们只能拥有截至 2020 年 1 月 1 日、2019 年 12 月 31 日、2019 年 12 月 30 日等日期的数据。

在金融数据(尤其是财务报告)中,同一项数据可能会随着时间推移被多次修正。如果我们仅使用最新版本的数据进行历史回测,就会导致数据泄露。点-in-时间数据库正是为了解决这一问题而设计的,确保用户在任意历史时间戳下都能获取对应时刻的正确数据版本,从而使实盘交易与历史回测的表现保持一致。

数据准备

Qlib 提供了一个爬虫工具帮助用户下载财务数据,然后通过转换器将数据转为 Qlib 格式。请参考 scripts/data_collector/pit/README.md 中的说明来下载和转换数据。此外,你也可以在那里找到一些额外的使用示例。

PIT 数据的基于文件的设计

Qlib 为 PIT 数据提供了一种基于文件的存储方式。

对于每个特征,包含四列:date(日期)、period(周期)、value(值)和 _next(下一位置)。每一行对应一份财务报表。

文件名类似 XXX_a.data 的特征含义如下:

  • date:该报表的发布日期。

  • period:报表所对应的会计期间。(例如,在大多数市场中为季度频率)
    • 如果是年度期间,则为表示年份的整数。

    • 如果是季度期间,则为形如 <年份><季度索引> 的整数。其中最后两位十进制数字代表季度索引,其余部分代表年份。

  • value:所描述的数值。

  • _next:该字段下一次出现的字节索引位置。

除了特征数据外,还包含一个索引文件 XXX_a.index,用于加快查询性能。

这些报表按 date 字段从小到大升序排列,从文件开头开始存储。

# the data format from XXXX.data
array([(20070428, 200701, 0.090219  , 4294967295),
       (20070817, 200702, 0.13933   , 4294967295),
       (20071023, 200703, 0.24586301, 4294967295),
       (20080301, 200704, 0.3479    ,         80),
       (20080313, 200704, 0.395989  , 4294967295),
       (20080422, 200801, 0.100724  , 4294967295),
       (20080828, 200802, 0.24996801, 4294967295),
       (20081027, 200803, 0.33412001, 4294967295),
       (20090325, 200804, 0.39011699, 4294967295),
       (20090421, 200901, 0.102675  , 4294967295),
       (20090807, 200902, 0.230712  , 4294967295),
       (20091024, 200903, 0.30072999, 4294967295),
       (20100402, 200904, 0.33546099, 4294967295),
       (20100426, 201001, 0.083825  , 4294967295),
       (20100812, 201002, 0.200545  , 4294967295),
       (20101029, 201003, 0.260986  , 4294967295),
       (20110321, 201004, 0.30739301, 4294967295),
       (20110423, 201101, 0.097411  , 4294967295),
       (20110831, 201102, 0.24825101, 4294967295),
       (20111018, 201103, 0.318919  , 4294967295),
       (20120323, 201104, 0.4039    ,        420),
       (20120411, 201104, 0.403925  , 4294967295),
       (20120426, 201201, 0.112148  , 4294967295),
       (20120810, 201202, 0.26484701, 4294967295),
       (20121026, 201203, 0.370487  , 4294967295),
       (20130329, 201204, 0.45004699, 4294967295),
       (20130418, 201301, 0.099958  , 4294967295),
       (20130831, 201302, 0.21044201, 4294967295),
       (20131016, 201303, 0.30454299, 4294967295),
       (20140325, 201304, 0.394328  , 4294967295),
       (20140425, 201401, 0.083217  , 4294967295),
       (20140829, 201402, 0.16450299, 4294967295),
       (20141030, 201403, 0.23408499, 4294967295),
       (20150421, 201404, 0.319612  , 4294967295),
       (20150421, 201501, 0.078494  , 4294967295),
       (20150828, 201502, 0.137504  , 4294967295),
       (20151023, 201503, 0.201709  , 4294967295),
       (20160324, 201504, 0.26420501, 4294967295),
       (20160421, 201601, 0.073664  , 4294967295),
       (20160827, 201602, 0.136576  , 4294967295),
       (20161029, 201603, 0.188062  , 4294967295),
       (20170415, 201604, 0.244385  , 4294967295),
       (20170425, 201701, 0.080614  , 4294967295),
       (20170728, 201702, 0.15151   , 4294967295),
       (20171026, 201703, 0.25416601, 4294967295),
       (20180328, 201704, 0.32954201, 4294967295),
       (20180428, 201801, 0.088887  , 4294967295),
       (20180802, 201802, 0.170563  , 4294967295),
       (20181029, 201803, 0.25522   , 4294967295),
       (20190329, 201804, 0.34464401, 4294967295),
       (20190425, 201901, 0.094737  , 4294967295),
       (20190713, 201902, 0.        ,       1040),
       (20190718, 201902, 0.175322  , 4294967295),
       (20191016, 201903, 0.25581899, 4294967295)],
      dtype=[('date', '<u4'), ('period', '<u4'), ('value', '<f8'), ('_next', '<u4')])
# - each row contains 20 byte


# The data format from XXXX.index.  It consists of two parts
# 1) the start index of the data. So the first part of the info will be like
2007
# 2) the remain index data will be like information below
#    - The data indicate the **byte index** of first data update of a period.
#    - e.g. Because the info at both byte 80 and 100 corresponds to 200704. The byte index of first occurance (i.e. 100) is recorded in the data.
array([         0,         20,         40,         60,        100,
              120,        140,        160,        180,        200,
              220,        240,        260,        280,        300,
              320,        340,        360,        380,        400,
              440,        460,        480,        500,        520,
              540,        560,        580,        600,        620,
              640,        660,        680,        700,        720,
              740,        760,        780,        800,        820,
              840,        860,        880,        900,        920,
              940,        960,        980,       1000,       1020,
             1060, 4294967295], dtype=uint32)

已知限制:

  • 目前,PIT 数据库主要针对季度或年度因子设计,适用于处理大多数市场的财报基本面数据。

  • Qlib 利用文件名来识别数据类型。文件名如 XXX_q.data 表示季度数据,文件名如 XXX_a.data 表示年度数据。

  • 当前 PIT 数据的计算方式并非最优,PIT 数据计算性能仍有很大的提升空间。