3. 因子数据模型设计:因子数据的特征、抽象与存储
做量化平台,最核心的活儿是什么?
我个人觉得,不是策略有多牛,也不是回测跑得多快。而是——数据模型。
你想想看,因子数据要是没设计好,后面所有分析都是空中楼阁。我在早期做因子平台时,就吃过这个亏。当时图省事,把所有因子值塞进一个大宽表里,结果查询慢得像蜗牛,扩展新因子还得改表结构……那叫一个痛苦。
今天我们就来聊聊,因子数据到底该怎么建模。
3.1 因子数据的两个核心特征
因子数据,说白了就两种形态:时间序列和截面数据。
3.1.1 时间序列
每个股票,每个因子,随时间变化的值。比如“过去5日收益率”,今天一个值,明天又一个值。
特征很明显:
- 每个股票独立一条线
- 时间间隔通常是固定的(日频、周频)
- 天然有序,不能乱
我遇到过最坑的事,就是有人把时间戳当字符串存,排序时按字典序排,结果“2024-01-02”排在了“2024-01-10”后面……嗯,血的教训。
3.1.2 截面数据
同一个时间点,所有股票的因子值。比如今天收盘后,全市场5000只股票的“市盈率”。
截面数据的特点是:
- 时间固定,股票维度展开
- 常用于选股、分层、中性化
- 数据量 = 股票数 × 因子数
说白了,时间序列是“纵向看”,截面数据是“横向看”。两者结合,才构成完整的因子数据立方体。
核心认知: 因子数据本质是一个三维结构——时间 × 股票 × 因子。任何数据模型,都要能高效支持这三个维度的切片和聚合。
3.2 数据模型抽象:三个核心概念
我习惯把因子数据抽象成三个对象:Factor、FactorValue、FactorUniverse。
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_name 也存到因子值表中,结果改个因子名称要更新几亿条记录……那酸爽,谁用谁知道。
3.5 小结
因子数据模型设计,说白了就三件事:
- 认清特征:时间序列和截面数据,是因子数据的两个基本维度
- 抽象到位:Factor、FactorValue、FactorUniverse 三个概念,各司其职
- 存储落地:长表 + 分区 + 精简索引,兼顾灵活和性能
这套模型,我在多个因子平台项目中验证过。从几百只股票到全市场5000+只,从几十个因子到上千个因子,都能撑得住。
嗯,下一节我们聊聊因子计算引擎的设计。不过那是后话了,先把数据模型搞扎实,后面的路才好走。