3. 量化数据模型设计:维度建模基础、星型模型与雪花模型、事实表与维度表设计、缓慢变化维处理策略
3.1 维度建模:我为什么偏爱它
做量化数据仓库,说白了就是要把海量的交易、行情、因子数据组织起来,让分析师能快速查询。我个人习惯用维度建模,因为它最贴近业务思维。
维度建模的核心就两样东西:事实表和维度表。事实表记录「发生了什么」,比如一笔成交、一次撤单。维度表描述「谁、什么时间、在哪个标的、用什么策略」。
我在项目中遇到过,有人把维度建模和范式建模搞混。范式建模追求数据无冗余,但查询时得关联七八张表,慢得让人抓狂。维度建模允许适当冗余,换来了查询性能。你想想看,量化分析最怕什么?最怕等结果。行情数据晚一秒,策略信号就废了。
3.2 星型模型 vs 雪花模型
这两种模型,我刚开始做时也纠结过。后来踩了坑才明白,没有绝对的好坏,只有合不合适。
星型模型
维度表直接挂在事实表周围,像星星一样。查询时只需要一次关联,速度极快。
-- 星型模型示例:成交事实表
CREATE TABLE trade_fact (
trade_id BIGINT,
date_key INT, -- 指向日期维度
stock_key INT, -- 指向股票维度
strategy_key INT, -- 指向策略维度
volume DECIMAL(20,4),
price DECIMAL(10,4),
amount DECIMAL(20,4)
);
CREATE TABLE dim_date (
date_key INT PRIMARY KEY,
date DATE,
year INT,
month INT,
quarter INT,
is_trading_day BOOLEAN
);
CREATE TABLE dim_stock (
stock_key INT PRIMARY KEY,
ts_code VARCHAR(10),
name VARCHAR(50),
exchange VARCHAR(10),
industry VARCHAR(30)
);
我建议,80%的场景用星型模型就够了。量化数据里,行情、交易、持仓这些核心事实表,我都用星型。
雪花模型
维度表进一步规范化,拆成多张子表。比如股票维度拆出交易所维度、行业维度。
-- 雪花模型:维度表拆解
CREATE TABLE dim_stock (
stock_key INT PRIMARY KEY,
ts_code VARCHAR(10),
name VARCHAR(50),
exchange_key INT, -- 指向交易所维度
industry_key INT -- 指向行业维度
);
CREATE TABLE dim_exchange (
exchange_key INT PRIMARY KEY,
exchange_code VARCHAR(10),
exchange_name VARCHAR(30),
timezone VARCHAR(20)
);
CREATE TABLE dim_industry (
industry_key INT PRIMARY KEY,
industry_code VARCHAR(10),
industry_name VARCHAR(30),
sector VARCHAR(30)
);
雪花模型节省存储,但查询要关联更多表。我记得有一次,分析师跑一个跨行业统计,雪花模型比星型慢了3倍。后来我直接把行业名称冗余到股票维度里,问题就解决了。
3.3 事实表设计:度量值的选择
事实表的设计,我踩过最大的坑就是「什么都往里放」。量化数据里,事实表分三种:
- 事务事实表:记录每笔交易、每笔行情。行数最多,粒度最细。
- 周期快照事实表:每天/每周汇总一次。比如每日持仓、每日风险指标。
- 累积快照事实表:跟踪一个流程的完整生命周期。比如从下单到成交到撤单。
度量值的选择有个原则:可加性。成交量、成交金额是可加的,可以跨时间、跨标的汇总。收益率、波动率是不可加的,不能简单求和。
3.4 维度表设计:属性冗余的艺术
维度表的设计,说白了就是「怎么描述一个业务实体」。股票、日期、策略、交易员,这些都是典型的维度。
我习惯在维度表里放三类属性:
- 标识属性:代码、名称、ID。用于唯一识别。
- 分类属性:行业、板块、风格。用于分组和过滤。
- 描述属性:上市日期、总股本、市盈率。用于分析上下文。
举个例子,日期维度表:
CREATE TABLE dim_date (
date_key INT PRIMARY KEY,
date DATE,
year INT,
month INT,
day INT,
week_of_year INT,
quarter INT,
is_weekend BOOLEAN,
is_month_end BOOLEAN,
is_quarter_end BOOLEAN,
is_year_end BOOLEAN,
trading_day_count INT -- 当年第几个交易日
);
嗯,这里要注意:日期维度表最好一次性生成10年甚至20年的数据。我见过有人每天动态生成,结果查询时日期维度表还没建好,导致ETL失败。
3.5 缓慢变化维:历史数据怎么追
量化数据里,维度属性是会变的。比如股票改名字、行业分类调整、策略参数更新。如果不处理,历史分析就会出错。
缓慢变化维(SCD)有三种策略:
| 策略 | 做法 | 适用场景 |
|---|---|---|
| SCD Type 1 | 直接覆盖旧值 | 错误修正、不重要的属性 |
| SCD Type 2 | 新增一条记录,标记生效时间 | 需要追溯历史的属性 |
| SCD Type 3 | 增加一列存旧值 | 只关心当前和上一个值 |
我曾经在股票维度上用过Type 2。某只股票从「科技」行业调到「消费」行业,如果不处理,回测2019年的策略时,行业分类用的是2023年的,结果完全不对。
-- SCD Type 2 示例:股票行业变更
CREATE TABLE dim_stock_scd2 (
stock_key INT,
ts_code VARCHAR(10),
name VARCHAR(50),
industry VARCHAR(30),
effective_date DATE,
end_date DATE, -- 9999-12-31 表示当前有效
is_current BOOLEAN,
PRIMARY KEY (stock_key, effective_date)
);
-- 查询某只股票在特定日期的行业
SELECT industry
FROM dim_stock_scd2
WHERE ts_code = '000001.SZ'
AND '2020-06-01' BETWEEN effective_date AND end_date;
对于量化数据,我建议:
- 股票名称、行业、上市状态 → 用 Type 2
- 策略参数、交易员归属 → 用 Type 2
- 错误数据修正 → 用 Type 1
- 交易所代码、货币类型 → 基本不变,Type 1 就够了
3.6 知识体系总览
下面这张图,是我对本章内容的总结。你可以把它当作一个快速索引。
这张图把本章的核心内容串起来了。从维度建模出发,分支出模型类型、事实表、维度表、SCD四个方向。每个方向都有对应的设计策略和注意事项。