项目

一般

简介

0001-项目章程 » 历史记录 » 版本 3

Huarui Lin, 2026-04-11 19:41

1 1 Huarui Lin
# 《基金量化定投选基系统》项目章程
2 2 Huarui Lin
3 1 Huarui Lin
**项目名称:** 基金量化定投选基系统
4
**项目编号:** 2026-04-11-01
5
**项目经理:** Henry Lin
6
**日期:** 2026-04-11
7 3 Huarui Lin
**文档状态:** v1.0.0
8 2 Huarui Lin
9 1 Huarui Lin
---
10
## 一、项目背景与立项理由
11 2 Huarui Lin
12 1 Huarui Lin
传统基金挑选依赖历史绝对收益排名或主观定性判断,严重忽略投资者在"周定投"场景下的真实盈利体验(浮盈回撤、止盈周期过长)。本系统旨在通过纯数据驱动与严密的时序逻辑,量化评估基金在"等额周定投"模式下的表现潜力。
13
立项核心理由:构建一套基于 LightGBM 的机器学习排序模型,精准识别能在 **150 周内以 DCA(简单平均成本法)实现 20% 止盈**的基金。解决"基金赚钱、基民不赚钱"的痛点,为业务端提供硬核的量化选基支撑,提升资产配置的科学性与客户持有体验。
14
## 二、项目目标
15
| 编号 | 目标项 | 具体描述 | 衡量标准 |
16
|------|--------|---------|---------|
17
| G-1 | 工程交付 | **8 个自然周**内完成端到端流水线 | 6 个 Step 全部通过验收 CheckList |
18
| G-2 | 技术底线 | 100% 遵守《最终设计规约 (v1.0)》铁律 | CI 强阻断触发率 = 0 |
19
| G-3 | 模型指标 | Top 20 推荐分桶显著跑赢等权基准 | 年化收益率、Sharpe、止盈成功率三指标均优于基准 |
20
| G-4 | 质量卡控 | 标签生成器 7 场景单测全过 | pytest 通过率 100% |
21 2 Huarui Lin
22 1 Huarui Lin
## 三、成功标准
23
项目达到"Done"状态的绝对验收条件:
24
- **规约符合度**:所有代码实现与 `/docs` 技术文档无任何偏离规约核心参数(150周、20%止盈、年化2.0%无风险利率、MAD边界3.0、共线性阈值 |r|>0.90 等)。
25
- **资源安全度**:在 64GB/36核 实体机上,端到端全量刷盘重训峰值内存 ≤ 50GB,CPU 峰值使用率 ≤ 80%,不触发 OOM。
26
- **产物完整性**:产出按年分区 Parquet 数据、SHA-256 数据血缘校验文件、标准化参数持久化文件、MLflow 实验记录、SHAP 归因图、四桶回测净值曲线。
27
- **流水线闭环**:在 Ubuntu 24 Docker 环境下,通过一条主指令完成"全量刷盘重训 → 自动评估 → 自动归档模型"全流程无人工干预运行。
28 2 Huarui Lin
29 1 Huarui Lin
## 四、关键干系人
30
| 角色 | 职责 | 核心关注点 |
31
|------|------|-----------|
32
| 项目经理(PM) | 规约守护者、交付卡控 | 红线不被违反,里程碑按期达成 |
33
| 算法/研发工程师 | 6 个 Step 模块实施 | 受限算力下实现高性能、合规代码 |
34
| 数据资产管理员 | 提供原始数据源 | 数据规模、质量与可用性 |
35
| 基础架构/运维 | Ubuntu 24 实体机与工具链 | Docker 部署、Gitea/Redmine/MLflow 可用性 |
36
## 五、项目范围与边界
37
### 包含在内(In Scope)
38
- 周频数据基础清洗(去重、异常值处理、长缺失截断、建仓期硬删、类型白名单过滤)
39
- 5 大类 40+ 维特征工程化计算、T-1 截面标准化、共线性剔除
40
- DCA 标签生成(20%止盈、150周窗口、样本平衡)
41
- LightGBM 模型训练(TimeSeriesSplit 5折、Optuna 超参搜索)
42
- 单基金查询与批量推荐推理服务(含 SHAP 归因与决策文本)
43
- Redmine 管理视图 + Gitea `/docs` 技术真相源双轨制文档建设
44
### 坚决不做(Out of Scope)
45
| 编号 | 边界项 | 理由 |
46
|------|--------|------|
47
| OB-1 | 日频/分钟频数据 | 仅处理周频,规约已锁定 |
48
| OB-2 | 增量更新机制 | 每周全量刷盘重训,规约 D-5 已锁定 |
49
| OB-3 | 真实交易闭环 | 不涉及资金自动扣款与报盘 |
50
| OB-4 | 费用扣除模拟 | `deduct_fee: false` 保持关闭 |
51
| OB-5 | UI 可视化前端 | 交付物为 API/脚本级推理能力 |
52
| OB-6 | DuckDB 滚动窗口计算 | FW-2 铁律严禁 |
53
| OB-7 | Pandas 依赖 | 全局禁止(LightGBM 内部除外) |
54
---
55
## 六、数据资源基线(已确认)
56
基于您提供的实际数据样例,已完成数据规模与内存安全评估:
57
### 6.1 净值表 `fund_net_info`
58
| 指标 | 数值 | 说明 |
59
|------|------|------|
60
| 单文件物理大小 | **920.1 MB** (CSV) | 全量原始数据 |
61
| 估算总行数 | **~2,300 万行** | 920.1MB ÷ 100行/4KB ≈ 23M |
62
| 单行字段 | `id`, `fund_id`, `cumulative_net_value`, `net_value_date` | `id` 为主键,清洗阶段丢弃 |
63
| 净值数据范围 | 2015-12-07 起 | 样例最早日期,覆盖约 10 年周频 |
64
| Parquet 预估大小 | **200-300 MB** | 列式压缩,通常为 CSV 的 1/3~1/4 |
65
| Arrow 内存预估 | **800 MB - 1.2 GB** | 3 有效列 × 23M 行 |
66
### 6.2 基金信息表 `fund_basic_info`
67
| 指标 | 数值 | 说明 |
68
|------|------|------|
69
| 总记录数 | **~26,000 条** | 全量基金信息 |
70
| 单文件物理大小 | **~4.2 MB** (CSV) | 10行/1.6KB × 2600 = ~4.16MB |
71
| 规约使用字段 | `fund_id`, `fund_name`, `fund_type`, `create_date` | 仅这 4 列 |
72
| **需丢弃字段** | `insert_datetime`, `update_datetime`, `insert_operator`, `update_operator` | **⚠️ 实际表比规约多 4 列,data_loader 必须显式丢弃** |
73
### 6.3 类型白名单覆盖率(基于样例推断)
74
| 样例中的 fund_type | 是否在白名单中 |
75
|-------------------|--------------|
76
| 混合型-灵活 | ✅ 保留 |
77
| 混合型-偏股 | ✅ 保留 |
78
| 债券型-混合二级 | ❌ 过滤 |
79
| 债券型-混合一级 | ❌ 过滤 |
80
| 指数型-股票 | ❌ 过滤 |
81
| 货币型-普通货币 | ❌ 过滤 |
82
> **推断**:26,000 只基金中,白名单 6 类(股票型、混合型-偏股、混合型-灵活、QDII-普通股票、QDII-混合偏股、QDII-混合灵活)预估保留 **5,000-10,000 只**。Step 2 数据基建完成后,数据质量审计日志需输出精确的白名单保留率,作为 Redmine M2 验收依据之一。
83
### 6.4 内存安全结论
84
| 处理阶段 | 预估内存占用 | 安全等级 |
85
|---------|-------------|---------|
86
| DuckDB 读取原始 CSV → 按年分区 Parquet | 5-10 GB | 🟢 安全 |
87
| DuckDB JOIN fund_basic_info(26K 行) | 增量 < 1 GB | 🟢 安全 |
88
| Polars 单年份 Arrow 转换(≤300万行 × 3列) | 1-2 GB | 🟢 安全 |
89
| Polars 单年份特征计算(≤300万行 × 40+特征) | 15-25 GB | 🟡 需串行 |
90
| **总计峰值(严格 FW-3 按年串行)** | **≤ 40 GB** | 🟢 低于 50GB 软限 |
91
> **核心防线**:严格执行 FW-3(按年份串行提取)+ DuckDB `memory_limit='40GB'` 硬限 + CI 环境 cgroup 50GB 压测。
92
---
93
## 七、高层级里程碑计划(8 周微步迭代)
94
```
95
Week 1          Week 2-3        Week 4-5         Week 6-7        Week 8
96
   │               │               │                 │               │
97
   ▼               ▼               ▼                 ▼               ▼
98
┌────────┐    ┌──────────┐   ┌────────────┐    ┌────────────┐   ┌──────────┐
99
│ Step 1 │───>│  Step 2  │──>│Step 3 & 4  │───>│   Step 5   │──>│  Step 6  │
100
│ 脚手架 │    │ 数据基建 │   │ 并行攻坚    │    │ 模型训练   │   │ 推理收口 │
101
└────────┘    └──────────┘   └────────────┘    └────────────┘   └──────────┘
102
   │               │            ┌──┴──┐              │               │
103
   ▼               ▼            ▼     ▼              ▼               ▼
104
 CI空跑       Parquet产出   特征引擎  标签器      MLflow记录     Production
105
 成功         审计日志通过   标准化   7场景单测     四桶回测       模型打标
106
              白名单覆盖率   参数输出  全绿         跑赢基准       文档闭合
107
              内存≤40GB                                         全部同步
108
```
109
### 各 Milestone 详细交付与 Done 标准
110
| MS | 周次 | 交付物 | Done 标准(硬卡点,缺一不可) |
111
|----|------|--------|------------------------------|
112
| **M1** | W1 | `pyproject.toml`、目录树、`config.yaml`、`config.py`、`logging.py`、`hash.py` | ① CI 空跑流水线在 Gitea 触发成功,无 Error;② `config.yaml` 参数与规约第七节完全一致 |
113
| **M2** | W2-W3 | `data_loader.py` + 按年分区 Parquet + DuckDB VIEW + 数据质量审计日志 | ① Parquet 文件产出,Schema 仅含 `fund_id`/`net_value_date`/`cumulative_net_value`;② 实体机压测峰值内存 ≤ 40GB;③ 审计日志包含:白名单保留率、缺失率、异常率、建仓期剔除数、存活期过滤数;④ `fund_basic_info` 多余 4 列已显式丢弃 |
114
| **M3a** | W4-W5 | `feature_engineering.py` + `features.parquet` + `standardization_params.parquet` + `final_feature_list.json` | ① 特征数 ≥ 40 维且全部为**长表形态**(严禁 Pivot 宽表);② 首日(数据集第一天)所有标准化特征输出 NULL(T-1 Shift 验证);③ `standardization_params.parquet` Schema 与规约 4.3 节一致;④ CI 强阻断通过 |
115
| **M3b** | W4-W5 | `label_generator.py` + `labels.parquet` + `test_label_generator.py` | ① 7 个必覆盖场景单测 100% 全绿;② label 值域严格为 [0, 149];③ 正样本单基金上限 50 条、负样本上限 15 条;④ 等距抽样使用 `np.linspace` |
116
| **M4** | W6-W7 | `trainer.py` + 模型文件 + 回测报告 + SHAP 图 + MLflow 记录 | ① AUC、NDCG 指标记录完整;② Top 20 桶年化收益率、Sharpe、止盈成功率均优于全标的池等权基准;③ MLflow Production 标签打标成功;④ SHAP summary plot 与四桶回测净值曲线产出 |
117
| **M5** | W8 | `inference.py`(单基金 + 批量) | ① 单基金输出:得分(0-100) + Top5 正负特征 + 决策文本;② 批量接口返回排序 DataFrame;③ 得分区间文案与规约 6.3 节完全一致;④ Gitea `/docs` 与 Redmine 状态 100% 同步 |
118
### 里程碑前置依赖(Redmine 强制卡控)
119
```
120
Step 1 ──✓──> Step 2 ──✓──> Step 3 ──┐
121
                              ──✓──> Step 4 ──┤──✓──> Step 5 ──✓──> Step 6
122
                                              └───────────────────┘
123
```
124
- Step 5 任务开启 **必须** 等待 Step 3 和 Step 4 在 Redmine 中状态均闭合
125
- Step 6 任务开启 **必须** 等待 Step 5 闭合
126
---
127
## 八、模型训练时间规划(关键算力约束)
128
> **已确认决策**:Optuna 采用 **策略 A(单折搜索)**——Optuna 仅在 TimeSeriesSplit 最后一个 Train/Val 折上搜索超参,找到最优后用该超参在其余折做最终评估。搜索与评估解耦,兼顾效率与质量。
129
### Step 5 整体时间拆解(纯 CPU / 36核)
130
| 阶段 | 预估耗时 | 说明 | 时间属性 |
131
|------|---------|------|---------|
132
| ① 数据加载与 JOIN | 2-5 分钟 | Polars 读取 features + labels | 固定 |
133
| ② TimeSeriesSplit 数据切分 | < 1 分钟 | 纯索引操作 | 固定 |
134
| ③ 每折:截面标准化 + 共线性筛选 | 10-20 分钟(×5折) | 串行执行,每折独立 | 固定 |
135
| **④ Optuna 超参搜索(单折)** | **≤ 60 分钟** | ⚠️ **1小时硬限仅此阶段** | 硬限 |
136
| ⑤ 用最优参数 5 折重训最终模型 | 5-10 分钟 | 单次训练 | 固定 |
137
| ⑥ 评估:AUC / NDCG / 四桶回测 | 15-30 分钟 | 含回测净值曲线计算 | 固定 |
138
| ⑦ SHAP 归因分析 | 10-20 分钟 | TreeExplainer | 固定 |
139
| ⑧ MLflow 日志写入 | < 2 分钟 | Artifact 序列化 | 固定 |
140
| **Step 5 总计** | **约 2-3.5 小时** | 可设在非交易时段执行 | — |
141
**关键保障措施**:
142
- Optuna 配置 `early_stopping_rounds=50`,加速无效试验的提前终止
143
- 1 小时到达时通过 `optuna.study.Study.optimize(timeout=3600)` 强制截断,取已有最优超参
144
- 最终模型质量仍通过完整 5 折交叉验证评估,搜索与评估完全解耦
145
---
146
## 九、预算与资源分配
147
### 9.1 计算资源
148
| 资源项 | 规格 | 使用策略 |
149
|--------|------|---------|
150
| CPU | **36 核**(实体机) | Polars 重度计算节点显式指定 `n_threads=24`,预留 12 核给 OS / DuckDB IO 缓冲 |
151
| 内存 | **64 GB** DDR | DuckDB 硬限 `memory_limit='40GB'`;Polars 按年串行处理;整体峰值 ≤ 50GB |
152
| GPU | **无** | 不依赖,LightGBM 纯 CPU 训练 |
153
| 存储 | **SSD(容量充足)** | Parquet 分区 + MLflow Artifact 归档 + 日志 |
154
| 操作系统 | **Ubuntu 24** | Docker 部署,隔离运行环境 |
155
### 9.2 工具链
156
| 工具 | 用途 | 核心约束 |
157
|------|------|---------|
158
| Gitea | 代码仓库 + 技术真相源 `/docs` | 所有技术文档 Markdown 存放于仓库 `/docs` 目录 |
159
| Redmine | 任务管理 + 规约追踪中枢 | 规约条款写入自定义字段-验收标准 |
160
| MLflow | 实验追踪 + 模型版本管理 | Production/Archived 标签管理 |
161
| Docker | 运行环境隔离 + CI/CD | 质量红线强阻断/软预警 |
162
| DuckDB | Parquet IO / JOIN / 过滤 | 严禁滚动窗口(FW-2) |
163
| Polars | 滚动窗口 / 特征计算 / 标签生成 | 按年份串行(FW-3) |
164
| LightGBM | 模型训练 | 唯一允许隐式调 Pandas 的环节 |
165
| Optuna | 超参搜索 | 单折搜索,1小时硬限 |
166
---
167
## 十、关键风险预警
168
| 编号 | 风险项 | 触发条件 | 影响 | 应对策略(规约级防御) |
169
|------|--------|---------|------|----------------------|
170
| R-1 | 内存溢出 (OOM) | 违反 FW-3 多年份并发 Arrow 转换 | 🔴 致命 | ① DuckDB 硬限 40GB;② CI 设 cgroup 限 50GB 压测;③ 代码 Review 重点检查按年循环 |
171
| R-2 | Optuna 在 CPU 下搜索不充分 | 36 核 CPU、大训练集 | 🟡 可控 | 1小时硬限截断取最优;`early_stopping_rounds=50`;单折搜索策略最大化试验次数 |
172
| R-3 | T-1 未来数据泄露 | 标准化未执行 `.shift(1)` | 🔴 致命 | ① 单测强制覆盖首日输出 NULL;② CI 加专项断言;③ 代码 Review 最高优检查项 |
173
| R-4 | 8 周紧凑致规约妥协 | 并行开发时省略 Type Hints / 单测 | 🟡 可控 | CI 强阻断红线,PR 触发 Error 则绝对禁止合并 |
174
| R-5 | fund_basic_info 多余字段污染 | data_loader 未丢弃 4 列非规约字段 | 🟢 可防 | ① `data_loader.py` 显式指定列投影 `SELECT fund_id, fund_name, fund_type, create_date`;② M2 审计日志输出实际列名 |
175
| R-6 | 白名单覆盖率异常低 | 实际数据中白名单类型基金占比过低 | 🟡 待观测 | Step 2 审计日志必须输出精确的白名单保留率;若 < 15%,PM 组织专项评审 |
176
---
177
## 十一、项目经理职权
178
| 编号 | 职权 | 说明 |
179
|------|------|------|
180
| A-1 | 一票否决权 | 任何违背《最终设计规约 v1.0》的代码提交或设计妥协,直接打回 |
181
| A-2 | 流程阻断权 | Redmine 前置依赖未达"产物正确+单测全过+CI不报错"标准时,锁定下游任务 |
182
| A-3 | 资源调度权 | 64GB 内存约束下,随时要求研发调整 Polars 并发度或暂停非核心进程 |
183
| A-4 | 技术债登记权 | 客观原因导致临时偏离规约时,强制责任方在 Redmine 录入技术债单并设定清理 Deadline |
184
---
185
**项目经理签字:**
186
Henry Lin
187
188
**日期:**
189
2026-04-11