周四 09:15。某上海私募 200 亿规模的多空基金,风控研究员发现:实盘 PnL 比昨晚研究端对当日的回测 投影 落后 47 bp。同样的标的池、同样的持仓、同样的执行切片。差距太干净,不像噪声。数据团队的第一动作不是去翻策略代码、不是去看执行层、不是去查券商成交回报——而是查 数据血缘 图:回测看到的每个输入是哪个版本?实盘看到的每个输入是哪个版本?查询 30 秒内返回:回测读到的 fundamentals_pit 行带 cleaning_pipeline_version = v3.2.1;实盘读到的带 v3.2.0。清洗管线昨夜做了一次升级;回测在新版本上重跑;实盘已在老版本上执行完毕。从告警到定位根因总耗时:四分钟。没有 血缘 图,同一事件复盘需要团队连续 3 天 SQL 取证。本课是本模块的 capstone——把前三课的词汇转化为下游每一个 Track-4 量化课程都依赖的「基础设施基底」的仓库架构。数据湖(raw + 永久)、列式仓库(清洗 + 关联 + 快)、时点视图(双时间 + 重述感知)、血缘图(每一行 可追溯)、权限治理 层(每一次读取 都写审计日志)、SLA(07:00 PIT 就绪、loud 失败、5 个工作日内复盘)。
三层仓库架构
任何一家现代买方机构的仓库都有三层——物理隔离、运营独立——围绕一条规则组织:「raw 湖 永生;清洗 仓库 可从湖重建;PIT 视图 是研究面向的表面」。2024 年 CN 买方默认:
| tier | technology | responsibility | canonical user |
| data lake | S3 / Aliyun OSS / Tencent COS / MinIO + Parquet | immortal raw vendor drops + audit copies; the rebuildable source | data team operates; researchers query via ad-hoc lake-readers |
| query layer | Snowflake / BigQuery / ClickHouse / Databricks / MaxCompute / StarRocks for production research + Athena / DuckDB / Spark-SQL for ad-hoc | cleaned + joined production datasets with predictable performance | every research desk query |
| point-in-time view | SCD-2 / Iceberg time-travel / Datomic-style append-only EAV | bitemporal serving of restate-able data — fundamentals + alt-data | every backtest + signal evaluation that depends on historical accuracy |
data lake 层。 廉价对象存储 持有 每一次供应商投递的不可变副本,按 lake/<vendor>/<dataset>/dt=YYYY-MM-DD/file.parquet 分区。CN 一侧:阿里云 OSS 是主流(MaxCompute 体系),腾讯云 COS(CDW 体系),华为云 OBS(DWS 体系),MinIO 用于私有云 / 本地化部署。Parquet 是规范化格式——列式(每列独立 block,支持列级压缩 + projection 下推)、schema-on-read(消费方指定要哪几列;写方 schema 在 footer)、压缩(基本面 + 另类数据 较 CSV 小 ~10 倍;Snappy 是默认 codec;ZSTD 用更高 CPU 换更高压缩比)、可切分(一个大 Parquet 文件可被 N 个 reader 并行处理)。审计副本 按合规要求保留——A 股 受监管数据 7 年起,未监管另类数据 3 年,自有衍生数据集 永久。raw 湖 永不删除;生产仓库 *可从湖重建*——清洗管线 可端到端重跑、把任意历史时点的仓库表 重新 reconstruct。分区主键:日期是顶层分区;超大量数据集(实时 行情)追加 hour 或 symbol-prefix 子分区。
query layer 层。 三个子层反映成本 / 性能权衡。(1) Ad-hoc 查询——研究员对湖直接做探索性查询。Aliyun DLA(湖 分析)/ DuckDB(笔记本上跑列式 SQL,「现代 SQLite for analytics」)/ Spark-SQL(分布式;当湖超出笔记本规模)——这些都能直接读 OSS / COS / S3 上的 Parquet。场景:探索、一次性回填、血缘 diff 查询。成本:按查询付费或按运行时付费,偶发使用便宜,生产规模昂贵。(2) 生产研究——研究台从一个可预测性能的列式仓库读取。MaxCompute(阿里云) 是 CN 买方机构的事实主流;Snowflake-via-Aliyun-marketplace 可用但受 PIPL 跨境限制;ClickHouse 自建 在中频 私募 里广泛使用;StarRocks(CN 开源列式仓库)2023-2024 急速兴起;Hologres / AnalyticDB / CDW(腾讯云)/ DWS(华为云) 各家都在打湖仓 一体(lakehouse)牌。场景:每一次生产查询(因子回测、信号评估、组合构建、风控)。成本:可预测的存储 + 按查询;仓库是大部分 CN 买方机构 单一最大 的基础设施成本项。(3) 低延迟 tick + 分钟线——TimescaleDB / KDB+ / QuestDB。4.1.1 配套层;基本面 + 另类数据 通常不需要这一延迟。
point-in-time view 层。 任何会被重述的数据——基本面、一致预期、另类信号——的研究面向表面。每一行同时携带 value_date(数值关于的日期)与 knowledge_date(数值被知晓的日期)——L1 的词汇从概念上升到 schema。
SCD-2 fundamentals_pit 表 schema
规范化的 SCD-2(Slowly Changing Dimension Type 2)模式:每一次基本面数值变化写一行新行,附带 effective_from + effective_to + is_current + 双时间对 + 数据列 + 用于血缘的 provenance JSON:
-- every fundamental table is bitemporal; SCD-2 with effective_from/effective_to is the canonical pattern
CREATE TABLE fundamentals_pit (
symbol VARCHAR,
value_date DATE,
knowledge_date DATE,
effective_from TIMESTAMP,
effective_to TIMESTAMP DEFAULT '9999-12-31',
is_current BOOLEAN DEFAULT TRUE,
revenue NUMERIC,
net_income NUMERIC,
eps NUMERIC,
total_assets NUMERIC,
shareholders_equity NUMERIC,
provenance JSON,
PRIMARY KEY (symbol, value_date, knowledge_date)
);
在 快照 S 上读:WHERE knowledge_date <= S AND effective_from <= S AND effective_to > S。新重述到来时,写一条新行,同时把上一行的 effective_to = NOW() 并 is_current = FALSE。SCD-2 模式被 MaxCompute / Snowflake / BigQuery / Postgres / SQL Server / Redshift / ClickHouse 原生支持——它是 80% 场景的默认。
双时间 P/E 查询
为 600519 贵州茅台 计算「2022-12-31 快照下的 P/E」:盈利数字必须取「快照已知 vintage」,价格必须取「快照之前」——两侧使用相同 snapshot 才能避免 look-ahead-bias:
-- compute P/E with both prices and earnings observed in their as-of-snapshot vintage; both sides must use the same snapshot date to avoid look-ahead-bias
WITH pit_view AS (
SELECT symbol, value_date, eps
FROM fundamentals_pit
WHERE knowledge_date <= '2022-12-31'
AND effective_from <= '2022-12-31'
AND effective_to > '2022-12-31'
),
prices AS (
SELECT symbol, date AS price_date, close
FROM bars_1d
WHERE date <= '2022-12-31'
)
SELECT p.symbol, p.price_date, p.close, v.eps,
(p.close / v.eps) AS p_e
FROM prices p
JOIN LATERAL (
SELECT eps FROM pit_view
WHERE symbol = p.symbol AND value_date <= p.price_date
ORDER BY value_date DESC
LIMIT 1
) v ON TRUE;
LATERAL JOIN 对每个价格日期挑出「截至当时最近一个季度」的 EPS——也即实盘策略在快照日真正能看到的那个数。市场数据层(bars_1d)以单时间形式参与连接,因为 tick 不会被重述,时间戳本身就是 knowledge date。
PIT 三种实现模式
1. SCD-2 / Slowly Changing Dimension Type 2 — every change is a new row with effective_from + effective_to + is_current + bitemporal pair; SQL-native; choose for default 80% of cases
2. Iceberg / Delta time-travel — append-only at the file level; SELECT * FROM tbl FOR SYSTEM_TIME AS OF '<snapshot>'; choose when the warehouse natively supports it and the workload is read-heavy with deep history
3. Datomic-style fully-append-only EAV — every fact is [entity, attribute, value, tx_id]; bitemporal-by-default; choose only when bitemporal-by-default + extreme lineage-fidelity is worth the operational cost
生产规则:基本面 + 另类数据 必须 是双时间的(重述 + 迟到 + 修正);行情数据 可以 是单时间的(tick 来即来,不重述——时间戳就是 knowledge date)。
SCD-2 是任意 SQL 原生仓库(Snowflake / BigQuery / Postgres / Redshift / MaxCompute / ClickHouse)的默认。Iceberg / Delta time-travel 在文件层工作:表只追加、每次写产生 snapshot-id,读用 SELECT * FROM tbl FOR SYSTEM_TIME AS OF '<snapshot>'(Iceberg / SQL:2011 语法)或 SELECT * FROM tbl VERSION AS OF <snapshot_id>(Delta)。劣势:snapshot-id 是公司 自己 的时间观,不是供应商的时间观——你还得保留 knowledge_date 列才能区分「供应商发布版」与「公司存储版」。MaxCompute 与 StarRocks 都支持 Iceberg / Hudi。Datomic-style EAV(Datomic、XTDB / 旧名 Crux)保真度最高;劣势是运营成本(查询模型陌生、生态小、单位字节成本高)——只在「双时间是杀手级特性」的小型专精机构使用;CN 一侧生产中基本不见。
数据血缘 与 provenance 四元组
每一行研究表必须可追溯回它的原始供应商输入。OpenLineage(Linux 基金会项目)定义了 dataset-derivation 事件的 JSON event schema;现代编排器(Airflow / dbt / Dagster / Spark / DataWorks)都会发出 OpenLineage 事件。公司内规范:每张研究表上有一个 provenance 列,承载四元组:
1. vendor_drop_id — the unique identifier of the specific vendor file or batch that originated this data — typically <vendor>.<dataset>.<vintage_id>
2. ingestion_batch_id — the L3-pipeline batch identifier that landed the vendor drop in the lake
3. cleaning_pipeline_version — the semantic version of the cleaning + transform code (bumped on every meaningful logic change)
4. transform_job_id — the specific job-run identifier that produced this row (for retro-diagnosis)
生产规则:每个研究数据集都通过 provenance 四元组回溯到它的原始供应商输入;当回测与实盘 PnL 不一致时,第一动作是 diff 血缘——bug 几乎总是「供应商 vintage 差异」(回测读了 current-vintage;实盘读了 PIT-as-of-then)或「管线版本回归」(清洗管线 上周升级到 v2,回测在 v2 上重跑、实盘已用 v1 处理过历史)。本课开篇的 4 分钟定位 就是这条查询。前向指引:4.5.1(回测方法论)正式化「回测 vs 实盘」对账纪律。
权限治理 与审计日志
供应商许可证把公司绑定在 账户 / IP / 业务单元 / 机构 维度规则上——参见 L3。数据团队的工作:每家供应商一个共享服务账号(一个 Wind 万得 server 账号,一个 Bloomberg B-PIPE 账号,一个 CRSP / WRDS 机构 账号);通过权限治理 组中转内部访问(阿里云 RAM 角色 / MaxCompute 角色 / OpenFGA / Casbin 策略——选择匹配仓库的技术)。每一次对权限受限数据集的读取都写日志 (user, dataset, columns_read, row_count, timestamp)——这是公司给供应商的「许可证被遵守」的证据。Wind / Bloomberg / CRSP / WRDS 都会做周期性的许可证合规审计;公司的审计日志是首要证据;数据团队拥有响应。SOX 风格的审计追踪纪律是更宽广的类比——审计日志之于数据团队,相当于交易审计日志之于合规团队。
SLA 与故障响应 runbook
数据团队把仓库作为生产服务运营,不是研究产物:
| sla | timing | scope | ownership |
| vendor reconciliation | completes by 06:00 local | manifest-verify + checksum match all overnight ingestions | data team owns; alerts on miss |
| PIT view | query-ready by 07:00 local | bitemporal-as-of queries return correct values for snapshots up to 06:55 | data team owns; staleness probe at 07:05 alerts on miss |
| lineage | queryable in real-time | the lineage graph reflects every ingestion + transform job within 5 minutes of completion | data team owns |
| morning research pipeline | runs at 07:30 | consumes the PIT view + market-data tier + alt-data tier; fails LOUD on missing dataset, never silent | research-team-owned but data-team-supported |
| incident response | page within 5 minutes of staleness alert | runbook: detect -> page -> diagnose-via-lineage -> roll-forward-or-back -> post-mortem within 5 business days | data team owns |
本地时间用北京时间(CST,UTC+8);告警通道是飞书 + 钉钉机器人,叠加内部值班排班系统。故障响应 runbook,五步:(i) 通过 PIT-view 陈旧 探针 检测;(ii) 呼叫值班数据工程师;(iii) 用 血缘 图 + 入库账本 诊断——起手查询 SELECT * FROM ingestion_log WHERE status != 'complete' AND scheduled_at > <last-known-good>;(iv) 上游解决后 roll forward 重跑失败的入库作业;或若上游短期无解,roll back 激活上一个 SCD-2 vintage;(v) 5 个工作日内复盘,把修复落到 schema 注册表、清洗管线 或 SLA 配置中的某一处。
端到端 capstone:贵州茅台 600519 跨 2022Q4 一次假设性 重述
L3 SFTP 入库把 Wind 当日基本面文件 wind_qf_20221115.zip 落到 lake/wind/fundamentals_quarterly/dt=2022-11-15/。manifest 通过;schema 注册表 通过;文件晋升到湖的 raw 分区,vendor_drop_id = wind.fundamentals_quarterly.20221115。清洗管线(版本 v3.2.0)读 raw 文件、归一化字段、计算衍生(TTM EPS),向 fundamentals_pit 写新 SCD-2 行——600519 的 2022Q3 营业收入,新行携带 value_date = 2022-09-30, knowledge_date = 2022-11-15, effective_from = 2022-11-15 03:00, effective_to = 9999-12-31, is_current = TRUE, revenue = 36298000000;provenance 列携带 {vendor_drop_id: "wind.fundamentals_quarterly.20221115", ingestion_batch_id: "batch-2022-11-15-001", cleaning_pipeline_version: "v3.2.0", transform_job_id: "job-9f12a"}。
2023Q4 Wind 推送了一次对 600519 2022Q3 的小幅收入确认 修正:营业收入 从 36298000000 改为 36285000000。新入库写一行新行 value_date = 2022-09-30, knowledge_date = 2023-11-08, effective_from = 2023-11-08 03:00, revenue = 36285000000;上一行 effective_to 被更新到 2023-11-08 03:00、is_current = FALSE。
2023-06-15 上以快照 2023-06-15 跑的回测正确读到 revenue = 36298000000(快照时点已知值,早于 2023-11-08 重述)。以快照 2024-01-15 跑的回测读到 revenue = 36285000000。当前 vintage 版本(不带双时间过滤)无条件读到 36285000000——正是 L1 那个 20-50% 回测高估的来源。权限治理 检查给出审计日志条目:{user: 'zhang_san', dataset: 'fundamentals_pit', columns_read: ['symbol', 'value_date', 'revenue'], row_count: 412, timestamp: '2023-06-15T09:14:22+08:00'}——公司给 Wind 的「许可证被遵守」证据。
五句话的纪律收尾:仓库是公司的记忆;双时间存储是回测「会被重述的数据」的唯一诚实方式;血缘 是「回测 vs 实盘」分歧 的唯一对账方式;权限治理 + 审计日志 是 守住 供应商许可证 的唯一方式;SLA + 故障 runbook 是把这个仓库当作生产服务而非研究产物来运行的唯一方式。
证监会 监管的 A 股 在 T+1 结算 节拍下运行;本仓库为下游的因子模型 + 夏普比率 / 信息比率 / Alpha 衰减 评估 提供 PIT-合规的输入。
练习(capstone)
Exercise
你为一家 200 亿规模、30 名研究员、12 条覆盖股票 + 期货生产策略的 CN 私募 量化 团队 设计生产仓库。给出四段交付。
(i) 三层拓扑图——底层 raw 湖(对象存储 上的 Parquet),中层列式仓库(用于生产研究),顶层 PIT 视图(研究面向);用 ASCII 草图或一页架构图。
(ii) 覆盖沪深300 标的池的 fundamentals_pit 表 SCD-2 schema——列出双时间对、SCD-2 生命周期列、两个示例基本面指标、provenance JSON 列、主键。
(iii) 还原「2022Q4 as-of 视图」下 600519 贵州茅台 EPS 的双时间 SELECT 模式——给出含 bind variables 的精确 SQL。
(iv) fundamentals_pit 中一行的血缘条目形态——写出 JSON 文档,把 provenance 四元组填上真实样本值。
(v) 一段 SLA + 故障响应 声明——覆盖 vendor recon、PIT 视图就绪、血缘 可查询、晨间管线失败模式、五步故障 runbook。
提示
提示
{"vendor_drop_id": "wind.fundamentals_quarterly.20221115", "ingestion_batch_id": "batch-...", "cleaning_pipeline_version": "v3.2.0", "transform_job_id": "..."}。SLA:recon 06:00;PIT 07:00;血缘 实时;runbook 五步。Formula Explorer
\text{P/E}_{\text{as-of}}(S) = \frac{\text{close}(S)}{\text{EPS}_{\text{TTM}}(\text{value\_date} \leq S,\ \text{knowledge\_date} \leq S)}本课组装清单
本课按顺序组装的可核对件:
- Inline-code table——三层仓库架构映射到 技术 / 职责 / 主要使用者。
- Fenced ```sql 代码块——
fundamentals_pitSCD-2 schema(含value_date+knowledge_date+effective_from+effective_to+is_current+provenance)。 - Fenced ```sql 代码块——双时间 as-of P/E 查询(SCD-2 PIT 视图 与
bars_1d的 LATERAL JOIN)。 - Inline-code listing——三种 PIT 实现模式(
SCD-2、Iceberg / Delta time-travel、Datomic-style fully-append-only EAV)与选择规则。 - Inline-code listing——血缘 四元组(
vendor_drop_id、ingestion_batch_id、cleaning_pipeline_version、transform_job_id)与 OpenLineage 标准引用。 - Inline-code table——五条仓库 SLA(
vendor reconciliation、PIT view、lineage、morning research pipeline、incident response)与五步故障 runbook。 - 练习——capstone 五段交付,含两条递进式 Hint。
- FormulaExplorer——双时间 过滤 下的 as-of P/E 表达式。
五句话的纪律收尾:仓库是公司的记忆;双时间存储是回测「会被重述的数据」的唯一诚实方式;血缘 是「回测 vs 实盘」分歧 的唯一对账方式;权限治理 + 审计日志 是 守住 供应商许可证 的唯一方式;SLA + 故障 runbook 是把这个仓库当作生产服务而非研究产物来运行的唯一方式。
下一课
PIT 仓库 + 血缘 + 权限治理 + SLA 都到位之后,alpha 研究模块(4.2)与 回测模块(4.5.1)可以在它之上 正确性 前置条件 已满足 地继续 编排。后续每一个 Track-4 课程都默认这套基础设施已经存在。本模块的四节课——基本面数据产品面与 PIT 纪律(L1)、另类数据分类与评估(L2)、四种入库模式(L3)、本课的仓库 capstone(L4)——共同 定义了「量化的 好 数据基础设施」是什么。你下个季度计算的、读取 fundamentals_pit 与 bars_1d 的价值策略夏普比率之所以可信,是因为本模块的四节课把它造成可信的。