项目

一般

简介

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

Huarui Lin, 2026-04-11 19:41

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