3. 因子数据模型设计:因子数据的特征、抽象与存储

做量化平台,最核心的活儿是什么?

我个人觉得,不是策略有多牛,也不是回测跑得多快。而是——数据模型

你想想看,因子数据要是没设计好,后面所有分析都是空中楼阁。我在早期做因子平台时,就吃过这个亏。当时图省事,把所有因子值塞进一个大宽表里,结果查询慢得像蜗牛,扩展新因子还得改表结构……那叫一个痛苦。

今天我们就来聊聊,因子数据到底该怎么建模。

3.1 因子数据的两个核心特征

因子数据,说白了就两种形态:时间序列截面数据

3.1.1 时间序列

每个股票,每个因子,随时间变化的值。比如“过去5日收益率”,今天一个值,明天又一个值。

特征很明显:

  • 每个股票独立一条线
  • 时间间隔通常是固定的(日频、周频)
  • 天然有序,不能乱

我遇到过最坑的事,就是有人把时间戳当字符串存,排序时按字典序排,结果“2024-01-02”排在了“2024-01-10”后面……嗯,血的教训。

3.1.2 截面数据

同一个时间点,所有股票的因子值。比如今天收盘后,全市场5000只股票的“市盈率”。

截面数据的特点是:

  • 时间固定,股票维度展开
  • 常用于选股、分层、中性化
  • 数据量 = 股票数 × 因子数

说白了,时间序列是“纵向看”,截面数据是“横向看”。两者结合,才构成完整的因子数据立方体。

核心认知: 因子数据本质是一个三维结构——时间 × 股票 × 因子。任何数据模型,都要能高效支持这三个维度的切片和聚合。

3.2 数据模型抽象:三个核心概念

我习惯把因子数据抽象成三个对象:FactorFactorValueFactorUniverse

3.2.1 Factor(因子定义)

描述因子是什么。比如:

{
    "factor_id": "PE_TTM",
    "name": "滚动市盈率",
    "category": "估值类",
    "frequency": "daily",
    "data_type": "float",
    "description": "过去12个月净利润 / 总市值"
}

这里要注意,factor_id 一旦确定就别改了。我曾经为了图方便,把因子ID和数据库字段名绑定,结果重构时改ID导致历史数据全乱了……

3.2.2 FactorValue(因子值)

这是最核心的数据实体。一条记录代表:某个股票、某个时间、某个因子的值。

{
    "stock_code": "000001.SZ",
    "trade_date": "2024-01-15",
    "factor_id": "PE_TTM",
    "value": 15.23,
    "quality": 1   // 数据质量标记
}

为什么要有 quality 字段?

因为因子值不总是可靠的。比如停牌日、新股上市初期、数据源异常……这些情况需要标记出来,后续分析时可以过滤掉。

3.2.3 FactorUniverse(因子计算域)

这个很多人会忽略。它定义了:在某个时间点,哪些股票是“可计算的”

举个例子:

  • 2024-01-01,全市场5300只股票
  • 但其中300只是ST股,50只刚上市不足60天
  • 你的因子可能只适用于“正常交易且上市满60天”的股票

所以,FactorUniverse 就是一张“白名单”表:

{
    "trade_date": "2024-01-01",
    "stock_code": "000001.SZ",
    "in_universe": true
}

这样做的好处是:计算因子时,只对 in_universe = true 的股票算,避免无效计算。我在项目中就靠这个优化,把因子计算时间缩短了40%。

3.3 数据存储结构设计

讲完抽象,咱们聊聊落地。存储结构怎么选?

3.3.1 宽表 vs 长表

对比项 宽表 长表
结构 每列一个因子 每行一个因子值
查询单因子 快(直接取列) 慢(需过滤)
新增因子 需改表结构 直接加行
存储空间 大(空值多) 小(稀疏存储)
典型场景 因子数量固定 因子频繁增减

我个人建议:用长表

为什么?因为因子平台的核心是“灵活”。你今天有50个因子,明天可能就变成80个。宽表每次加因子都要改表结构,生产环境上改表有多麻烦,你懂的。

3.3.2 分区策略

长表虽然灵活,但数据量大了之后查询会慢。怎么办?分区。

我常用的分区方式:

  • 按时间分区:每天一个分区。查询某段时间的数据时,直接剪枝。
  • 按因子ID分区:如果某个因子特别热门(比如每天被查询上万次),可以单独分区。
  • 按股票代码哈希分区:适合做全量截面分析。

实战技巧: 我一般用“时间 + 因子ID”组合分区。比如先按月份分区,再在每个分区内按因子ID做二级分区。这样既能快速定位时间范围,又能高效提取单个因子。

3.3.3 索引设计

索引是查询加速的关键。对于因子长表,核心索引就两个:

-- 索引1:按股票+时间查因子值
CREATE INDEX idx_stock_date ON factor_values (stock_code, trade_date);

-- 索引2:按时间+因子查截面
CREATE INDEX idx_date_factor ON factor_values (trade_date, factor_id);

嗯,就这两个。别加太多索引,写入会变慢。我见过有人给每个字段都加索引,结果写入速度掉了10倍……

3.4 整体架构图

说了这么多,咱们用一张图把整个数据模型串起来:

因子数据模型架构 Factor(因子定义层) factor_id | name | category | frequency | data_type 描述因子是什么,不存储具体数值 FactorValue(因子值层) stock_code | trade_date | factor_id | value | quality 核心数据实体,存储每个股票每个时间每个因子的值 采用长表结构,按时间+因子ID分区 FactorUniverse(因子域层) trade_date | stock_code | in_universe 定义每个时间点哪些股票参与因子计算 核心原则:定义与存储分离,时间与截面兼顾,灵活与性能平衡

避坑提醒: 千万不要把因子定义和因子值混在一个表里。我见过有人把 factor_name 也存到因子值表中,结果改个因子名称要更新几亿条记录……那酸爽,谁用谁知道。

3.5 小结

因子数据模型设计,说白了就三件事:

  1. 认清特征:时间序列和截面数据,是因子数据的两个基本维度
  2. 抽象到位:Factor、FactorValue、FactorUniverse 三个概念,各司其职
  3. 存储落地:长表 + 分区 + 精简索引,兼顾灵活和性能

这套模型,我在多个因子平台项目中验证过。从几百只股票到全市场5000+只,从几十个因子到上千个因子,都能撑得住。

嗯,下一节我们聊聊因子计算引擎的设计。不过那是后话了,先把数据模型搞扎实,后面的路才好走。

专注资料整理