无噪 logo
  1. qlib 速读指南
  2. 数据模型与接口

4. 数据模型与接口

本文详细介绍了 QLib 量化投资平台的数据模型设计理念、核心数据 API、数据加载器的使用方法以及数据处理器与特征工程的实践。通过本章内容,你将能够深入理解 QLib 数据层的架构设计,并掌握如何高效地获取、处理和转换量化投资所需的数据。

数据模型设计理念

QLib 的数据模型是整个平台的基础,设计之初就考虑了量化投资领域的特殊需求。与传统的金融数据存储方案相比,QLib 的数据模型具有以下几个显著特点:

面向时间序列的设计

金融市场数据本质上是时间序列数据,QLib 的数据模型从底层就为此进行了优化。每个金融工具(如股票)的历史数据被组织成按时间顺序排列的记录,这种结构使得时间序列分析操作(如计算移动平均线、收益率等)变得高效。

在 QLib 中,所有数据都带有时间戳,并且按照时间顺序存储。这种设计不仅符合金融数据的自然属性,也为后续的特征计算和模型训练提供了便利。

分层的数据架构

QLib 采用分层的数据架构,主要包括以下几个层次:

  1. 原始数据层:存储最基础的行情数据,如开盘价、收盘价、最高价、最低价、成交量等
  2. 特征层:基于原始数据计算得到的各种技术指标和因子
  3. 标签层:用于模型训练的目标变量,如未来收益率

这种分层架构使得数据的组织更加清晰,也方便了不同层次数据的管理和复用。

高效的存储格式

QLib 使用自定义的二进制格式(.bin 文件)来存储数据,这种格式相比传统的 CSV 格式具有以下优势:

  • 更高的读写效率:二进制格式可以直接映射到内存,减少了数据解析的开销
  • 更小的存储空间:通过压缩和高效编码,减少了磁盘占用
  • 更好的类型支持:原生支持金融数据中常见的各种数据类型

下面是一个展示 QLib 数据存储架构的示意图:

flowchart TD
    A[原始数据] -->|转换| B[QLib 二进制格式数据]
    B --> C[数据加载器]
    C --> D[数据处理器]
    D --> E[特征工程]
    E --> F[模型训练]

数据 API 详解

QLib 提供了丰富的数据 API,方便用户获取和处理金融数据。这些 API 可以分为数据检索 API、特征 API 和过滤 API 三大类。

数据检索 API

数据检索 API 用于从 QLib 数据存储中获取原始数据。最常用的是 qlib.data.get_price 函数,它可以获取指定金融工具的行情数据。

from qlib.data import get_price

# 获取沪深 300 指数成分股的行情数据
df = get_price(
    instruments='csi300',
    start_time='2010-01-01',
    end_time='2020-12-31',
    fields=['open', 'close', 'high', 'low', 'volume'],
    freq='day'
)
print(df.head())

这段代码会获取 2010 年到 2020 年沪深 300 指数成分股的日度行情数据,包括开盘价、收盘价、最高价、最低价和成交量。返回的是一个 pandas DataFrame 对象,方便进行后续的数据分析和处理。

特征 API

特征 API 允许用户基于原始数据计算各种技术指标和因子。QLib 提供了两种主要的特征构造方式:FeatureExpressionOps

Feature 用于直接获取原始数据字段,如 $close 表示收盘价,$volume 表示成交量等。

ExpressionOps 则允许用户通过表达式构造复杂的特征,例如:

from qlib.data.ops import EMA, RSI

# 计算 12 日和 26 日指数移动平均线
ema12 = EMA($close, 12)
ema26 = EMA($close, 26)

# 计算 RSI 指标
rsi = RSI($close, 14)

QLib 支持的运算符和函数非常丰富,包括各种移动平均线、动量指标、波动率指标等。用户还可以通过继承 Operator 类来实现自定义的特征计算逻辑。

过滤 API

过滤 API 用于根据特定条件筛选金融工具。QLib 提供了 NameDFilterExpressionDFilter 两种过滤器。

NameDFilter 基于金融工具的名称进行过滤,例如筛选出所有沪市股票:

from qlib.data.filter import NameDFilter

filter = NameDFilter(pattern='^SH')

ExpressionDFilter 则基于表达式进行过滤,例如筛选出收盘价大于开盘价的股票:

from qlib.data.filter import ExpressionDFilter

filter = ExpressionDFilter(rule_expression='$close > $open')

这些过滤器可以在数据加载过程中使用,从而只加载符合条件的数据,提高数据处理效率。

数据加载器使用方法

数据加载器(Data Loader)是 QLib 中负责从数据源加载原始数据的组件。QLib 提供了多种数据加载器,以适应不同的数据来源和格式。

QlibDataLoader

QlibDataLoader 是 QLib 的默认数据加载器,用于加载 QLib 格式的二进制数据。使用方法如下:

from qlib.data.dataset.loader import QlibDataLoader

# 定义要加载的字段
fields = ['$open', '$close', '$high', '$low', '$volume']

# 创建数据加载器
loader = QlibDataLoader(fields=fields)

# 加载数据
data = loader.load(instruments='csi300', start_time='2010-01-01', end_time='2020-12-31')

StaticDataLoader

StaticDataLoader 允许用户从 pandas DataFrame 或 CSV 文件加载静态数据。这对于使用自定义数据或外部数据非常有用:

from qlib.data.dataset.loader import StaticDataLoader
import pandas as pd

# 从 CSV 文件加载数据
df = pd.read_csv('custom_data.csv', index_col=0, parse_dates=True)

# 创建静态数据加载器
loader = StaticDataLoader(data=df)

# 加载数据
data = loader.load()

自定义数据加载器

如果内置的数据加载器不能满足需求,用户还可以通过继承 DataLoader 基类来实现自定义的数据加载器。自定义数据加载器需要实现 load 方法:

from qlib.data.dataset.loader import DataLoader

class CustomDataLoader(DataLoader):
    def __init__(self, custom_param):
        self.custom_param = custom_param
        super().__init__()

    def load(self, instruments=None, start_time=None, end_time=None):
        # 实现自定义数据加载逻辑
        pass

数据处理器与特征工程

数据处理器(Processor)是 QLib 中负责数据预处理和特征工程的组件。数据处理器可以串联起来形成一个处理管道,对原始数据进行一系列转换。

常用数据处理器

QLib 提供了多种常用的数据处理器,包括:

  • DropnaProcessor: 删除包含缺失值的样本
  • ZscoreNorm: 对数据进行 Z 分数标准化
  • MinMaxNorm: 对数据进行最小-最大标准化
  • CSZScoreNorm: 对横截面数据进行 Z 分数标准化
  • Fillna: 填充缺失值

下面是一个使用数据处理器的示例:

from qlib.data.dataset.processor import ZscoreNorm, Fillna, DropnaProcessor
from qlib.data.dataset.handler import DataHandlerLP

# 定义处理器管道
processors = [
    Fillna(fill_value=0),  # 用 0 填充缺失值
    ZscoreNorm(),  # Z 分数标准化
    DropnaProcessor()  # 删除仍然包含缺失值的样本
]

# 创建数据处理器
handler = DataHandlerLP(
    instruments='csi300',
    start_time='2010-01-01',
    end_time='2020-12-31',
    processors=processors,
    infer_processors=True
)

# 获取处理后的数据
data = handler.fetch()

自定义数据处理器

除了内置的处理器,用户还可以通过继承 Processor 基类来实现自定义的数据处理器。自定义处理器需要实现 fittransform 方法:

from qlib.data.dataset.processor import Processor
import numpy as np

class LogReturnProcessor(Processor):
    def fit(self, df):
        # 拟合阶段,这里不需要学习任何参数
        return self

    def transform(self, df):
        # 计算对数收益率
        df = df.copy()
        df['log_return'] = np.log(df['close'] / df['close'].shift(1))
        return df

特征工程实践

在量化投资中,特征工程是构建有效模型的关键步骤。QLib 提供了强大的特征工程能力,支持多种特征构造方式。

下面是一个使用 QLib 进行特征工程的完整示例:

from qlib import init
from qlib.data.dataset.handler import DataHandlerLP
from qlib.contrib.data.handler import Alpha158

# 初始化 QLib
init()

# 使用内置的 Alpha158 特征集
handler = Alpha158(
    start_time='2010-01-01',
    end_time='2020-12-31',
    fit_start_time='2010-01-01',
    fit_end_time='2015-12-31',
    instruments='csi300'
)

# 获取特征数据
features = handler.fetch(col_set='feature')
# 获取标签数据
labels = handler.fetch(col_set='label')

print('特征数据形状:', features.shape)
print('标签数据形状:', labels.shape)

Alpha158 是 QLib 提供的一个包含 158 个特征的特征集,这些特征基于传统的技术指标和量价分析构建,在多个量化任务中表现良好。

特征选择

在实际应用中,并非所有特征都对模型有贡献。过多的特征可能导致维度灾难和过拟合。因此,特征选择是量化投资中的重要步骤。

QLib 提供了多种特征选择方法,例如基于特征重要性的选择:

from qlib.contrib.model.gbdt import LGBModel
from qlib.model.selection import feature_importance

# 训练一个 LightGBM 模型
model = LGBModel()
model.fit(features, labels)

# 计算特征重要性
importance = feature_importance(model, features, labels)

# 选择重要性最高的 50 个特征
selected_features = importance.head(50).index.tolist()

# 使用选择后的特征
features_selected = features[selected_features]

通过特征选择,我们可以减少特征数量,提高模型的泛化能力和解释性。

数据缓存机制

为了提高数据处理效率,QLib 实现了完善的数据缓存机制。缓存机制可以避免重复计算,显著提高数据加载和处理的速度。

内存缓存

QLib 默认使用内存缓存来存储频繁访问的数据,如交易日历、金融工具列表和特征数据。内存缓存由 MemCache 类实现,可以通过以下方式访问:

from qlib.data.cache import H

# 获取交易日历缓存
calendar = H['c']
# 获取金融工具缓存
instruments = H['i']
# 获取特征缓存
features = H['f']

磁盘缓存

对于计算成本较高的特征和数据集,QLib 提供了磁盘缓存机制。磁盘缓存可以将计算结果保存到磁盘,下次使用时直接加载,无需重新计算。

下面是一个使用磁盘缓存的示例:

from qlib.data.cache import DiskExpressionCache

# 创建磁盘缓存
cache = DiskExpressionCache(cache_path='./cache')

# 定义一个复杂的特征表达式
expr = 'Mean($close, 5) - Mean($close, 10)'

# 从缓存中获取特征,如果缓存不存在则计算并保存
feature = cache.get(expr, instruments='csi300', start_time='2010-01-01', end_time='2020-12-31')

缓存的实际应用

在实际应用中,合理使用缓存可以显著提高工作效率。下面是一个完整的示例,展示如何在 QLib 中使用数据缓存:

from qlib import init
from qlib.data.dataset.handler import DataHandlerLP
from qlib.utils import init_instance_by_config
import pickle

# 初始化 QLib
init()

# 定义数据处理器配置
handler_config = {
    'class': 'Alpha158',
    'module_path': 'qlib.contrib.data.handler',
    'kwargs': {
        'start_time': '2010-01-01',
        'end_time': '2020-12-31',
        'fit_start_time': '2010-01-01',
        'fit_end_time': '2015-12-31',
        'instruments': 'csi300',
    }
}

# 创建数据处理器
handler = init_instance_by_config(handler_config)

# 将处理器保存到磁盘
with open('handler_cache.pkl', 'wb') as f:
    pickle.dump(handler, f)

# 下次使用时直接加载
with open('handler_cache.pkl', 'rb') as f:
    handler = pickle.load(f)

# 使用加载的处理器获取数据
features = handler.fetch(col_set='feature')

通过这种方式,我们可以避免重复的数据预处理工作,特别是在特征计算成本较高的情况下,可以节省大量时间。

数据质量检查

数据质量是量化投资的基础,低质量的数据可能导致错误的模型和投资决策。QLib 提供了数据质量检查工具,帮助用户发现和处理数据中的问题。

数据检查工具

QLib 提供了 check_data_health.py 脚本,用于检查数据的健康状况。该脚本可以检查以下内容:

  • 数据中是否存在缺失值
  • OHLCV 数据是否存在异常跳变
  • 是否缺少必要的列(如开盘价、收盘价等)
  • 是否缺少因子列

使用方法如下:

# 检查日度数据
python scripts/check_data_health.py check_data --qlib_dir ~/.qlib/qlib_data/cn_data

# 检查分钟数据
python scripts/check_data_health.py check_data --qlib_dir ~/.qlib/qlib_data/cn_data_1min --freq 1min

自定义数据检查

除了使用内置工具,用户还可以编写自定义的数据检查代码。例如,检查数据中是否存在异常值:

import pandas as pd
from qlib.data import get_price

# 获取数据
df = get_price(
    instruments='csi300',
    start_time='2010-01-01',
    end_time='2020-12-31',
    fields=['open', 'close', 'high', 'low', 'volume'],
    freq='day'
)

# 检查异常值
for col in df.columns:
    # 使用 IQR 方法检测异常值
    q1 = df[col].quantile(0.25)
    q3 = df[col].quantile(0.75)
    iqr = q3 - q1
    lower_bound = q1 - 1.5 * iqr
    upper_bound = q3 + 1.5 * iqr
    outliers = df[(df[col] < lower_bound) | (df[col] > upper_bound)]
    if not outliers.empty:
        print(f'Column {col} has {len(outliers)} outliers')

通过数据质量检查,我们可以及时发现数据中的问题,并采取相应的处理措施,如删除异常值、填充缺失值等,从而提高模型的可靠性。

数据处理性能优化

在处理大规模金融数据时,性能往往是一个挑战。QLib 提供了多种性能优化方法,帮助用户高效地处理海量数据。

数据复用

数据复用是提高性能的有效方法。QLib 允许用户在内存中缓存处理后的数据,避免重复处理:

from qlib.data.dataset.handler import DataHandlerLP
from qlib.model.trainer import task_train

# 创建数据处理器
handler = DataHandlerLP(...)

# 复用处理后的数据进行多次训练
for i in range(5):
    task = {
        'dataset': {
            'kwargs': {
                'handler': handler,  # 直接使用已处理的 handler
                ...
            },
            ...
        },
        ...
    }
    task_train(task)

并行计算

QLib 支持并行计算,可以利用多核 CPU 提高数据处理速度。用户可以通过设置环境变量 QLIB_WORKER 来启用并行计算:

# 使用 4 个工作进程
export QLIB_WORKER=4

数据类型优化

在处理大规模数据时,选择合适的数据类型可以显著减少内存占用和提高计算速度。例如,使用 float32 代替 float64 可以减少一半的内存占用:

# 将数据类型转换为 float32
features = features.astype('float32')

选择性加载

只加载需要的数据可以减少 I/O 操作和内存占用。QLib 允许用户指定需要加载的字段和金融工具:

# 只加载需要的字段
fields = ['$close', '$volume']

# 只加载特定的金融工具
instruments = ['SH600000', 'SH600036', 'SH601318']

# 加载数据
data = get_price(
    instruments=instruments,
    start_time='2010-01-01',
    end_time='2020-12-31',
    fields=fields,
    freq='day'
)

通过这些性能优化方法,用户可以显著提高数据处理效率,特别是在处理大规模数据集时,可以节省大量时间和资源。

小结

本章详细介绍了 QLib 的数据模型与接口,包括数据模型的设计理念、核心数据 API、数据加载器的使用方法、数据处理器与特征工程、数据缓存机制、数据质量检查以及性能优化方法。

通过本章的学习,读者应该能够:

  1. 理解 QLib 数据模型的设计思想和架构
  2. 熟练使用 QLib 的数据 API 获取和处理金融数据
  3. 掌握数据加载器和数据处理器的使用方法
  4. 进行特征工程和特征选择
  5. 利用缓存机制提高数据处理效率
  6. 检查和保证数据质量
  7. 优化数据处理性能

这些知识是使用 QLib 进行量化投资研究和实践的基础,后续章节将在此基础上介绍更高级的模型和策略。