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倍。后来我直接把行业名称冗余到股票维度里,问题就解决了。

我的经验:如果维度表行数少于10万,直接冗余成星型。超过百万行且属性经常变化,再考虑雪花。

3.3 事实表设计:度量值的选择

事实表的设计,我踩过最大的坑就是「什么都往里放」。量化数据里,事实表分三种:

  • 事务事实表:记录每笔交易、每笔行情。行数最多,粒度最细。
  • 周期快照事实表:每天/每周汇总一次。比如每日持仓、每日风险指标。
  • 累积快照事实表:跟踪一个流程的完整生命周期。比如从下单到成交到撤单。

度量值的选择有个原则:可加性。成交量、成交金额是可加的,可以跨时间、跨标的汇总。收益率、波动率是不可加的,不能简单求和。

注意:不要把计算逻辑放在事实表里。比如「盈亏金额」应该由价格和数量计算得出,而不是直接存一个字段。否则数据口径一变,你就要全量重刷。

3.4 维度表设计:属性冗余的艺术

维度表的设计,说白了就是「怎么描述一个业务实体」。股票、日期、策略、交易员,这些都是典型的维度。

我习惯在维度表里放三类属性:

  1. 标识属性:代码、名称、ID。用于唯一识别。
  2. 分类属性:行业、板块、风格。用于分组和过滤。
  3. 描述属性:上市日期、总股本、市盈率。用于分析上下文。

举个例子,日期维度表:

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 会让维度表行数暴增。我曾经维护过一张股票维度表,5年下来从3000行涨到1.2万行。查询时一定要带上日期过滤,否则性能会崩。

对于量化数据,我建议:

  • 股票名称、行业、上市状态 → 用 Type 2
  • 策略参数、交易员归属 → 用 Type 2
  • 错误数据修正 → 用 Type 1
  • 交易所代码、货币类型 → 基本不变,Type 1 就够了

3.6 知识体系总览

下面这张图,是我对本章内容的总结。你可以把它当作一个快速索引。

量化数据模型设计知识体系 维度建模 星型模型 雪花模型 事实表设计 事务事实表 周期快照事实表 累积快照事实表 维度表设计 标识属性 分类属性 + 描述属性 缓慢变化维(SCD) Type 1 Type 2 Type 3

这张图把本章的核心内容串起来了。从维度建模出发,分支出模型类型、事实表、维度表、SCD四个方向。每个方向都有对应的设计策略和注意事项。

一句话总结:量化数据模型设计,就是用星型模型组织事实和维度,用SCD策略管理历史变化,让查询快、分析准、维护省心。

专注资料整理