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