Wang's blog

基础 - 数据框架

Published on

简介

Qlib的数据框架提供了友好的API用于管理与检索数据,以及高性能的数据基础设施。它是专门为量化投资设计的,例如,用户可以轻松地建立公式化alpha。

下面是使用Qlib数据工作流的一个典型的例子:

  • 下载基础数据并转换为Qlib格式(后缀名为.bin)
  • 使用Qlib表达式引擎建立一些基本特征,例如Ref($close, 60) / $close表示最近60个交易日的回报率。这一步骤通常实现在数据处理器中的数据加载器组件内
  • 如果用户需要更加复杂的数据处理(如数据归一化),数据处理器模块支持用户自定义处理器。处理器可以实现表达式引擎难以支持的复杂数据处理方法
  • 基于预处理的数据生成模型所需的数据集

数据预处理

Qlib格式数据

Qlib专门设计了一种数据结构用于处理金融数据,此类数据被存储于.bin文件中。

Qlib提供了两个现成的数据集

数据集 美国市场 中国市场
Alpha360
Alpha158

同时Qlib也提供了一个高频数据集

Qlib格式数据集

Qlib提供脚本scripts/get_data.py用于下载数据集。使用如下命令下载中国市场股票数据集:

# 下载日线数据
python scripts/get_data.py qlib_data --target_dir ~/.qlib/qlib_data/cn_data --region cn

# 下载分钟线数据
python scripts/get_data.py qlib_data --target_dir ~/.qlib/qlib_data/qlib_cn_1min --region cn --interval 1min

也可以下载美国市场股票数据集:

python scripts/get_data.py qlib_data --target_dir ~/.qlib/qlib_data/us_data --region us

运行上述命令后,中国市场与美国市场股票数据集分别保存于~/.qlib/qlib_data/cn_data~/.qlib/qlib_data/us_data目录中。

Qlib也提供一个脚本scripts/data_collector帮助用户爬取最新数据并转换为Qlib格式。

自动更新数据

强烈建议用户先手动更新数据一次,之后设置为自动更新:

  • 手动更新数据
python scripts/data_collector/yahoo/collector.py update_data_to_bin --qlib_data_1d_dir <user data dir> --trading_date <start date> --end_date <end date>

其中参数trading_date为交易开始日期,end_date为交易结束日期(不包含)。

  • 在每个交易日自动更新数据(Linux)

执行命令crontab -e,之后添加定时任务:

* * * * 1-5 python <script path> update_data_to_bin --qlib_data_1d_dir <user data dir>

其中脚本路径script pathscripts/data_collector/yahoo/collector.py

将CSV格式转换为Qlib格式

Qlib提供脚本scripts/dump_bin.py将CSV格式数据转换为.bin格式。假设CSV格式数据放在~/.qlib/csv_data/my_data目录下,则可以使用如下命令进行转换:

python scripts/dump_bin.py dump_all --csv_path  ~/.qlib/csv_data/my_data --qlib_dir ~/.qlib/qlib_data/my_data --include_fields open,close,high,low,volume,factor

其中–include_fields参数指定的字段必须与CSV文件中的列名对应。

查询该命令的全部参数:

python dump_bin.py dump_all --help

CSV格式数据可以使用脚本scripts/get_data.py下载:

# 下载日线数据
python scripts/get_data.py download_data --file_name csv_data_cn.zip --target_dir ~/.qlib/csv_data/cn_data

# 下载分钟线数据
python scripts/data_collector/yahoo/collector.py download_data --source_dir ~/.qlib/stock_data/source/cn_1min --region CN --start 2021-05-20 --end 2021-05-23 --delay 0.1 --interval 1min --limit_nums 10

也可以使用自定义CSV格式数据,但是必须满足以下条件:

  • 文件名为某一标的名称,或者文件中包含一列表示标的名称,例如:

文件名为SH600000.csv或AAPL.csv等(大小写不敏感),或者文件内容为:

symbol close
SH600000 120

注意在格式转换时需要指定该列名称:

python scripts/dump_bin.py dump_all ... --symbol_field_name symbol
  • 文件中必需包含一列表示日期,例如:
symbol date close open volume
SH600000 2020-11-01 120 121 12300000
SH600000 2020-11-02 123 120 12300000

注意在格式转换时需要指定该列名称:

python scripts/dump_bin.py dump_all ... --date_field_name date

股票池(市场)

Qlib将股票池定义为股票列表与它们的日期范围。预定义的股票池(如csi300)可以使用如下方式导入:

python collector.py --index_name CSI300 --qlib_dir <user qlib data dir> --method parse_instruments

股票模式

目前Qlib中有两种股票模式:中国模式与美国模式,它们的不同设置如下:

模式 交易单位 涨跌阈值
中国 100 0.099
美国 1

使用每种模式前均需要在代码中进行初始化:

# 初始化中国模式
from qlib.constant import REG_CN
qlib.init(provider_uri='~/.qlib/qlib_data/cn_data', region=REG_CN)

# 初始化美国模式
from qlib.config import REG_US
qlib.init(provider_uri='~/.qlib/qlib_data/us_data', region=REG_US)

数据API

数据检索

用户可以使用qlib.data的API进行数据检索,下面是一些基本的例子:

载入指定时间范围与交易频率的交易日历:

>> from qlib.data import D
>> D.calendar(start_time='2010-01-01', end_time='2017-12-31', freq='day')[:2]
[Timestamp('2010-01-04 00:00:00'), Timestamp('2010-01-05 00:00:00')]

将市场名称解析为股票池配置:

>> from qlib.data import D
>> D.instruments(market='all')
{'market': 'all', 'filter_pipe': []}

载入指定股票池在指定时间范围内的所有标的:

>> from qlib.data import D
>> instruments = D.instruments(market='csi300')
>> D.list_instruments(instruments=instruments, start_time='2010-01-01', end_time='2017-12-31', as_list=True)[:6]
['SH600036', 'SH600110', 'SH600087', 'SH600900', 'SH600089', 'SZ000912']

载入一个市场的标的并根据名称进行过滤:

>> from qlib.data import D
>> from qlib.data.filter import NameDFilter
>> nameDFilter = NameDFilter(name_rule_re='SH[0-9]{4}55')
>> instruments = D.instruments(market='csi300', filter_pipe=[nameDFilter])
>> D.list_instruments(instruments=instruments, start_time='2015-01-01', end_time='2016-02-15', as_list=True)
['SH600655', 'SH601555']

载入一个市场的标的并根据表达式进行过滤:

>> from qlib.data import D
>> from qlib.data.filter import ExpressionDFilter
>> expressionDFilter = ExpressionDFilter(rule_expression='$close>2000')
>> instruments = D.instruments(market='csi300', filter_pipe=[expressionDFilter])
>> D.list_instruments(instruments=instruments, start_time='2015-01-01', end_time='2016-02-15', as_list=True)
['SZ000651', 'SZ000002', 'SH600655', 'SH600570']

载入指定标的在指定时间范围内的特征:

>> from qlib.data import D
>> instruments = ['SH600000']
>> fields = ['$close', '$volume', 'Ref($close, 1)', 'Mean($close, 3)', '$high-$low']
>> D.features(instruments, fields, start_time='2010-01-01', end_time='2017-12-31', freq='day').head().to_string()
'                           $close     $volume  Ref($close, 1)  Mean($close, 3)  $high-$low
... instrument  datetime
... SH600000    2010-01-04  86.778313  16162960.0       88.825928        88.061483    2.907631
...             2010-01-05  87.433578  28117442.0       86.778313        87.679273    3.235252
...             2010-01-06  85.713585  23632884.0       87.433578        86.641825    1.720009
...             2010-01-07  83.788803  20813402.0       85.713585        85.645322    3.030487
...             2010-01-08  84.730675  16044853.0       83.788803        84.744354    2.047623'

载入指定股票池在指定时间范围内的特征:

>> from qlib.data import D
>> from qlib.data.filter import NameDFilter, ExpressionDFilter
>> nameDFilter = NameDFilter(name_rule_re='SH[0-9]{4}55')
>> expressionDFilter = ExpressionDFilter(rule_expression='$close>Ref($close,1)')
>> instruments = D.instruments(market='csi300', filter_pipe=[nameDFilter, expressionDFilter])
>> fields = ['$close', '$volume', 'Ref($close, 1)', 'Mean($close, 3)', '$high-$low']
>> D.features(instruments, fields, start_time='2010-01-01', end_time='2017-12-31', freq='day').head().to_string()
'                              $close        $volume  Ref($close, 1)  Mean($close, 3)  $high-$low
... instrument  datetime
... SH600655    2010-01-04  2699.567383  158193.328125     2619.070312      2626.097738  124.580566
...             2010-01-08  2612.359619   77501.406250     2584.567627      2623.220133   83.373047
...             2010-01-11  2712.982422  160852.390625     2612.359619      2636.636556  146.621582
...             2010-01-12  2788.688232  164587.937500     2712.982422      2704.676758  128.413818
...             2010-01-13  2790.604004  145460.453125     2788.688232      2764.091553  128.413818'

在构建复杂表达式时,使用字符串在一行内实现整个表达式是比较困难的,例如:

>> from qlib.data import D
>> data = D.features(["sh600519"], ["(($high / $close) + ($open / $close)) * (($high / $close) + ($open / $close)) / (($high / $close) + ($open / $close))"], start_time="20200101")

此时可以不使用字符串,而使用代码实现表达式,下面是与上面例子等价的实现:

>> from qlib.data.ops import *
>> f1 = Feature("high") / Feature("close")
>> f2 = Feature("open") / Feature("close")
>> f3 = f1 + f2
>> f4 = f3 * f3 / f3

>> data = D.features(["sh600519"], [f4], start_time="20200101")
>> data.head()

特征 Feature

Qlib提供FeatureExpressionOps以获取数据特征。

  • Feature:从数据源加载数据时,用户可以直接获取特征, 如:$high$low$open$close等。它们需要与转换数据格式时的–include_fields参数对应
  • ExpressionOps:使用Operator构建特征,支持用户自定义

过滤器 Filter

Qlib提供NameDFilterExpressionDFilter用于过滤标的:

  • NameDFilter:根据名称对标的进行过滤,需要一个名称的正则表达式
  • ExpressionDFilter:根据表达式对标的进行过滤,需要一个表达式规则
    • 基础特征过滤器:$close/$open>5
    • 横截面特征过滤器:$rank($close)<10
    • 时序特征过滤器:$Ref($close, 3)>100

下面是在工作流的配置文件中使用过滤器的例子:

filter: &filter
    filter_type: ExpressionDFilter
    rule_expression: "Ref($close, -2) / Ref($close, -1) > 1"
    filter_start_time: 2010-01-01
    filter_end_time: 2010-01-07
    keep: False

data_handler_config: &data_handler_config
    start_time: 2010-01-01
    end_time: 2021-01-22
    fit_start_time: 2010-01-01
    fit_end_time: 2015-12-31
    instruments: *market
    filter_pipe: [*filter]

数据加载器 Data Loader

数据加载器用于从数据源加载原始数据,它会被数据处理器模块载入并使用。Qlib提供了数据加载器的基类DataLoader,并实现了以下数据加载器类:

  • QlibDataLoader:用于从Qlib数据源加载原始数据
  • StaticDataLoader:用于从文件等数据源加载原始数据

数据处理器 Data Handler

数据处理器模块用于处理被多数模型使用的通用数据处理方法,用户可以通过qrun在自动工作流中使用数据处理器。

可学习数据处理器 DataHandlerLP

Qlib提供了可学习数据处理器类DataHandlerLP,该类包含多个可以学习参数(如zscore归一化参数)的处理器Processor。当新数据到来时,已训练的处理器就可以处理新数据,从而使高效地处理实时数据成为可能。

处理器 Processor

处理器模块的作用是进行数据处理,它被设计为是可训练的。Qlib提供以下处理器:

  • DropnaProcessor:丢弃N/A特征
  • DropnaLabel:丢弃N/A标签
  • TanhProcess:使用tanh处理噪声数据
  • ProcessInf:使用平均值替换无穷值
  • Fillna:用0或给定数值填充N/A值
  • MinMaxNorm:最大值-最小值归一化
  • ZscoreNorm:zscore归一化
  • RobustZScoreNorm:稳健zscore归一化
  • CSZScoreNorm:横截面zscore归一化
  • CSRankNorm:横截面rank归一化
  • CSZFillna:横截面N/A值填充

用户可以通过继承基类Processor以建立自己的处理器。

例子

Qlib实现了一个数据处理器Alpha158,下面的例子展示了如何将其作为独立模块运行:

import qlib
from qlib.contrib.data.handler import Alpha158

data_handler_config = {
    "start_time": "2008-01-01",
    "end_time": "2020-08-01",
    "fit_start_time": "2008-01-01",
    "fit_end_time": "2014-12-31",
    "instruments": "csi300",
}

if __name__ == "__main__":
    qlib.init()
    h = Alpha158(**data_handler_config)

    # 获取数据所有列
    print(h.get_cols())

    # 获取所有标签
    print(h.fetch(col_set="label"))

    # 获取所有特征
    print(h.fetch(col_set="feature"))

数据集 Dataset

数据集模块的作用是为模型训练与推理准备数据。设计该模块的动机是想要为不同的模型处理适合自己的数据提供最大的灵活性。该模块使每个模型可以使用独立的方式处理数据,例如,GBDT等模型可以在包含nan或None的数据上正常运行,但是MLP等神经网络模型不能在此类数据上正常运行。

如果用户的模型需要使用特殊的方式处理数据,可以继承Dataset类以实现自己的数据集类。如果数据处理方式并不特殊,可以直接使用预置的DatasetH类,它是带有数据处理器的数据集。

缓存 Cache

缓存是一个可选模块,它通过将常用的数据储存在缓存中以提高数据读取速度。Qlib提供了以下缓存类:

全局内存缓存 Memcache

该类由三个MemCacheUnit实例组成,分别用于缓存日期、标的与特征。该全局变量在cache.py中被定义为H,用户可以使用H['c']H['i']H['f']来获取/设置缓存。

表达式缓存 ExpressionCache

该类用于缓存表达式(如Mean($close, 5)),用户可以通过如下步骤继承该基类以实现自己的表达式缓存机制:

  • 重写self._uri方法以定义如何生成缓存文件路径
  • 重写self._expression方法以定义哪些数据会被缓存以及如何缓存

目前Qlib实现了DiskExpressionCache类,它将表达式数据存储在硬盘上。

数据集缓存 DatasetCache

该类用于缓存数据集,用户可以通过如下步骤继承该基类以实现自己的数据集缓存机制:

  • 重写self._uri方法以定义如何生成缓存文件路径
  • 重写self._dataset方法以定义哪些数据会被缓存以及如何缓存

目前Qlib实现了DiskDatasetCache类,它将数据集数据存储在硬盘上。