项目

一般

简介

0004-标签生成器 » 历史记录 » 版本 3

Huarui Lin, 2026-04-13 11:23

1 1 Huarui Lin
## 标签生成器
2
3
由于 Step 4 业务逻辑高度内聚且规约给出了标准伪代码,一周时间安排相对紧凑,核心攻坚集中在向量化实现与边界防御。
4 2 Huarui Lin
5 1 Huarui Lin
| 周次 | Story ID | Story 描述 | 规约硬性映射 | 验收标准 | 工时 |
6
|:---:|:---|:---|:---|:---|:---:|
7
| **W4** | **S4-01** | **DCA 标签向量化核心引擎**<br>实现基于 Polars `group_by` + NumPy 的标签计算逻辑。 | 规约 3.2 (算法伪代码);CR-001 (segment_id隔离) | ① 按 `group_by(['fund_id', 'segment_id'])` 提取极小数组,**严禁跨 segment 滑动窗口**;<br>② 有效窗口 `len < 2` 时输出 `label = 0`;<br>③ 严格使用 `np.cumsum` 与 `np.arange` 计算简单平均成本;<br>④ 使用 `np.argmax(mask) + 1` 计算耗费周数,映射为 `150 - weeks`;<br>⑤ **性能卡控**:全量数据跑批耗时需在可接受范围内(预估 10-20 分钟)。 | 2天 |
8
| **W4** | **S4-02** | **样本平衡与落盘机制**<br>实现正负样本的等距抽样限制与长表 Parquet 写入。 | 规约 3.3 (等距抽样/上限50/15);规约 7.4 (严禁Pivot宽表) | ① 正样本(label>0)单基金上限 50 条;<br>② 负样本(label=0)单基金上限 15 条(`50 * 0.3`);<br>③ **强制使用** `np.linspace(0, len(samples)-1, N, dtype=int)` 进行等距抽样;<br>④ 产出 `labels.parquet`,Schema 为长表:`fund_id`, `net_value_date`, `segment_id`, `label` (Int32);<br>⑤ 产出 `data/metadata/labels_hash.json`。 | 1天 |
9
| **W4** | **S4-03** | **7 场景极限防御单测与 CI 闭环**<br>针对规约第九节的 7 个场景编写单测,并补充 segment 边界测试。 | 章程 M3b Done标准;规约第九节;架构推演延伸 | ① 规约 7 个场景单测 100% 全绿,断言精确匹配期望值(如场景 5 必须等于 149);<br>② **补充单测(PM 强制)**:构造一只基金包含 2 个 segment 的 Mock 数据,断言 segment 0 末尾的标签计算绝不使用 segment 1 的数据;<br>③ `pytest tests/test_label_generator.py -v` 纳入 CI 强阻断红线。 | 2天 |
10
---
11
12
## 可优化与可复用逻辑提示
13 2 Huarui Lin
14 1 Huarui Lin
### 1. “有效窗口长度 < 2” 的防御性考量
15 2 Huarui Lin
16 1 Huarui Lin
规约伪代码写道:“若有效窗口长度 < 2 → label = 0”。
17
在实际工程中,如果由于 Step 2 的截断导致某个时间点 t 之后只剩下 1 周的数据(即 `t+1` 存在,但 `t+2` 不存在),`np.cumsum` 依然可以运行(长度为 1 的数组),但这不符合“定投”的语义(至少要投入 2 期才能算成本)。因此,**强制拦截 `< 2` 是极其正确的防御,单测场景 7(建仓期后首周)刚好能覆盖此边界**。研发在实现时切勿将判断条件放宽为 `< 1`。
18 2 Huarui Lin
19 1 Huarui Lin
### 2. `np.argmax(mask)` 的陷阱防御
20 2 Huarui Lin
21 1 Huarui Lin
NumPy 的 `argmax` 在 `mask` 全为 `False` 时会返回 `0`。虽然伪代码中用了 `any(mask)` 做前置判断,但在向量化实现(如使用 `np.where` 或底层 NumPy 循环)时,极易引入隐式 Bug。
22
**建议实现**:不要过度追求炫技的纯矩阵运算,规约已允许“对每只基金处理极小数组”,直接在 `map_elements` 或 Python UDF 中使用规约给定的标准 `if-else` 伪代码逻辑是最安全的,性能瓶颈不在 Python 层,而在 Polars 的 `group_by` 调度。
23 2 Huarui Lin
24 1 Huarui Lin
---
25
## 新增/修改文件清单
26 2 Huarui Lin
27 1 Huarui Lin
| 操作 | 文件路径 | 简介与企业级约束 |
28
|:---:|:---|:---|
29
| **新增** | `src/label_generator.py` | **核心模块**。包含 2 个公开函数:<br>`generate_labels(df: pl.DataFrame) -> pl.DataFrame` — 主流程(按 segment 隔离 + DCA 计算);<br>`balance_samples(df: pl.DataFrame) -> pl.DataFrame` — 样本平衡(linspace 抽样)。**严禁出现 150、0.20 等魔法数字**。 |
30
| **新增** | `tests/test_label_generator.py` | **防线级单测**。必须包含规约第九节全部 7 个场景的测试用例,输入数据需精心构造净值序列(如场景 5 需构造首周涨幅超 20% 的极端数据),并附加 segment 边界隔离测试。 |
31
| **修改** | `docs/data_contract.md` | 新增《标签矩阵接口契约单》:明确 `labels.parquet` 的 Schema(重点标注包含 `segment_id`),以及 `label` 字段的值域约束 `[0, 149]`、数据类型 `Int32`。 |
32
| **修改** | `.gitea/workflows/ci.yml` | 无需额外修改,已在 Step 3 阶段配置了全局 `pytest tests/` 触发逻辑。 |
33
| **产出** | `data/labels/labels.parquet` | 运行时产物。长表形态,经过样本平衡后的标签数据集。 |
34
| **产出** | `data/metadata/labels_hash.json` | 运行时产物。基于 `src/utils/hash.py` 计算。 |
35 3 Huarui Lin
36
---
37
### 下级目录
38
39 1 Huarui Lin
---