Files
qlib/docs/DEV_PLAN_v2.md
T

256 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 下阶段开发计划(架构 v2 落地 · 2026-09 · 选股系统为主线)
> 依据:[ARCHITECTURE_v2.md](./ARCHITECTURE_v2.md)(新架构 v2)、[AGENT.md](../AGENT.md)(开发约束)、
> 两份 2026-09 只读调研(后端研究/量化层、前端/Agent/数据层)与 docs/ROADMAP.md 的 M0–M5 记录。
> 本文件回答:**M5(AI Agent Phase 5)之后,下一阶段做什么、按什么顺序做。**
> 2026-09 更新(用户定调):**下一阶段以「选股系统」为独立主线并提前执行**
> (原"研究层落地"后移为 M7 支撑),先交付可用的选股 MVP,再补因子层增强。
---
## 0. 当前状态与总判断
**已完成(M0–M5)**:数据层(Tushare 首选 + 新浪兜底 + 审计)→ ResearchSpec → 本地研究引擎
(9 个行情因子 / IC·分层评估 / 低频 TopN 等权回测)→ Job/Experiment 异步归档 → Web 六页 →
AI Agent(6 个受控 Tool)。**数据库已于 2026-09 从 SQLite 全量迁移至 MySQL**(见 §1)。
**对照 v2 的核心差距**(调研结论):
1. **选股没有独立成系统**:v2 强调 Factor Research / Composite Factor / **Selection** / Signal /
Portfolio 分层;当前"选股"只是回测器内部 TopN 逻辑,无法单独回答 v2 §8 的两个核心问题:
「2023-08-15 为什么选这只股票」「2026-09-08 当前有哪些股票满足策略」。
2. **因子定义 / 策略等研究元数据未入库**:DB 只有 8 张表(stock / stock_daily / adjust_factor /
financial_indicator / trading_calendar / sync_log / job / experiment);v2 §8 期望的
factor_definition / universe / selection_rule / selection_result / signal_rule / strategy 等全缺。
3. **口径与数据完整性风险待修**:研究链路不消费复权因子(stock_daily 混合 tushare 不复权与新浪前复权行);
universe 用"当前名称含 ST"近似、无历史成分/停牌;Job.stage 从未赋值、SSE 是查库轮询。
4. **前端/Agent 待做实**:Selection/Signal 页面不存在;Agent 工具仅 6 个,
v2 §24 期望的 screen_stocks / explain_selection 等依赖尚未存在的选股引擎。
**阶段命名**:**M6 选股系统(主线)→ M7 因子层落地(支撑)→ M8 引擎延伸与平台化**。
数据库专项单独记 M-DB。
---
## 1. 执行状态(2026-09:M6–M8 主干已交付)
> M6 选股系统、M7 因子层、M8(Signal/Portfolio/Strategy/Agent 工具/Web)均已完成并提交
> (ROADMAP 里程碑行含 commit;每阶段全量 pytest + ruff + tsc 验证通过;Alembic 迁移已应用 MySQL)。
> M8.4 Qlib 模型选股(Selection 模式 C)为「按需」项,未阻塞主线 —— 当前延后,
> 前置:qlib_adapter 特征/模型链路(Alpha158 + LightGBM walk-forward),启用时按 §5.4 推进。
## 2. M-DB:数据库 MySQL 迁移(已完成,本文件记录收尾项)
**已交付(2026-09)**:
- MySQL(192.168.1.10:3306,MariaDB 10.11,库 `qlib`,utf8mb4)建 8 张业务表 + alembic_version,
全部经 `alembic upgrade head`(迁移 e4d18825…53113c80…91c4e27a…d3f6c9a2),结构由既有 ORM 模型保证。
- `scripts/migrate_sqlite_to_mysql.py`:一次性工具 —— sqlite 直连 + pymysql chunk 多值 INSERT +
keyset 分页续传 + 幂等(已迁则跳过)+ `--verify-only` 逐表 COUNT 与抽样比对;支持 `--only <表>` 并行大表、
`--throttle-sec` 低配服务器节流。
- 配置:`config.yaml → database.mysql`(host/port/db/user/charset 明文),密码经 `.env` 的
`MYSQL_PASSWORD`(AGENT.md §33:密钥不进 config/git);URL 优先级
`DATABASE_URL 环境变量 > config.yaml database.mysql > sqlite:///./data/quant.db 兜底`;
实现集中在 `backend/app/core/config.py::_build_mysql_url`,业务层零改动(DAO/Repository 隔离兑现)。
- 测试隔离:`backend/tests/conftest.py` 强制每进程 `/tmp/qlib-pytest-<pid>.db` SQLite(测试绝不触 MySQL)。
- 驱动:`pymysql>=1.1` 已入 `backend/pyproject.toml`。迁移数据量 16,057,265 行,`--verify-only` 校验全部一致。
**遗留收尾(小任务,可随时做)**:
| 项 | 说明 | 建议 |
|---|---|---|
| `job/experiment.result_json` 用 MySQL `TEXT`(≤65,535B) | SQLite TEXT 无上限、测试测不出 | 日级回测结果变长后改 `MEDIUMTEXT`(新 Alembic 迁移 + Model 类型 variant) |
| Repository 批量 upsert 在 MySQL 的压测 | `tuple_.in_()` 行值、`add_all` 单事务无分块 | 全市场日线增量同步跑一次,观察锁等待/包大小 |
| MySQL 路径无自动化测试 | 测试全 SQLite 方言 | 视需要加一个「连 MySQL 的集成冒烟」开关(默认关,显式 env 开启) |
| `scripts/migrate_sqlite_to_mysql.py` 属一次性工具 | 保留供重建目标库 | 文档标注不可再对生产执行(除 --verify-only) |
| 前端/文档残留 "SQLite" 文案 | app-shell / page.tsx / sync.py docstring | 随各页面改动顺手清理 |
---
## 3. 下阶段总路线
```text
M6 选股系统(主线) Universe 选股范围 + Selection Engine(A条件/B评分)
│ + 结果落库/API + 回测共用引擎 + Web 选股页
↓
M7 因子层落地 因子定义入库 + Composite 模块化 + 研究口径修复(支撑选股增强)
↓
M8 引擎延伸与平台化 Signal Engine → Portfolio Engine → Strategy / Agent / Qlib 模型选股
```
设计红线(贯穿):v2 §25「历史回测与当前选股用同一套 Engine」、v2 §9 时间与未来函数防护、
v2 §26 Experiment 可复现、AGENT.md「简单优先 / 每阶段可运行 / 一个功能一个 commit /
先 Domain 后 UI / 带测试」。
---
## 4. M6:选股系统(下一阶段主线)
> 目标:交付一个**独立、可解释、支持当前与历史日期**的选股系统(v2 §14 Selection Engine),
> 让回测器与"现在该选什么"共用同一套引擎(v2 §25 一致性红线)。
> MVP 全部复用**现有数据与因子能力**(stock / financial_indicator / stock_daily + 9 个内置行情因子 +
> composite_score),不依赖 M7 因子层,因此可最先做、尽快见到"选股结果"。
### 3.0 选股输入输出契约(先定 Domain / DTO)
- Domain `SelectionSpec`(v2 §14.2):`universe 范围 + filters(条件) + scoring(因子/复合因子/权重) +
ranking(TopN / Top%) + as_of_date`;沿用并扩展现有 `ResearchSpec` 的 universe/factors/selection 结构,
新增 `as_of_date`(缺省 = 最近交易日)。
- Domain `SelectionResult`(v2 §14.3/§21.1):`as_of_date, universe, candidates[{symbol, rank, score,
factor_values, filter_status, selection_reason}], statistics` —— 作为前后端/Agent 统一 DTO。
- 核心用例:`selection_service.select(spec, as_of=...) -> SelectionResult`(历史/当前日期都可执行)。
- 验收:SelectionResult 进入 `frontend/web/lib/types.ts` 与后端 entity 一一对应。
### 3.1 Universe:选股范围(MVP 规则化)
- 把 `ResearchService.filter_stocks` 的过滤逻辑(market / exclude_st / min_listing_days /
delist_date < as_of / 可选 symbol 白名单)抽成可复用的 `UniverseFilter` 执行器,
输入结构化条件(Pydantic),输出 as_of 时点下的股票范围。
- MVP 不建 universe 大表:条件随 `SelectionSpec` 一起存入 `selection_snapshot`(快照 JSON,
可复现);`universe / universe_rule / index_constituent_history` 表推迟到 M8 需要历史成分时再落。
- 明确近似并写进结果:ST 按当前名称快照判定;停牌/退市边界显式标注(unimplemented 或近似说明)。
- 验收:同一组条件在"当前日"与"历史日"分别得到正确范围(含历史日上市/退市过滤);
现有回测 universe 语义不变(回归)。
### 3.2 Selection Engine:A 条件选股 + B 因子评分
- **A. 条件选股**:结构化条件过滤(示例:`roe > 15 且 pe… 且 close > ma60`),
条件作用于现有字段/派生指标:
- 静态/财务:`market / industry / eps / roe / total_revenue / net_profit / gross_margin…`
(财务值按 `announce_date ≤ as_of` 可见,防未来函数);
- 行情/技术:`close > ma20 / ma60、momentum_20>0、volume_ratio…`(复用 factors.py 的 `compute_factor`)。
- MVP 条件用**结构化 JSON**(字段 + 比较符 + 值,可 and/or 嵌套),不做自由表达式 parser(避免提前造轮子)。
- **B. 因子评分选股**:`score = composite_score(截面 zscore × 方向 × 权重)`(抽取 local_engine 现逻辑),
按 score 排序取 `top_n` / `top_pct`;同时支持阈值过滤。
- **C. 模型预测选股**(Qlib/LightGBM)留 M8.3,引擎接口先留 mode 字段占位。
- 每条候选输出 `selection_reason`(命中的条件 / 分数来源),支撑 v2「为什么选这只股票」。
- 验收:同 spec 下 A/B 模式输出与人工核对一致;条件/评分模式均可对历史 as_of 执行。
### 3.3 结果持久化与 API
- Model(→ Alembic → Repo,全部走 Model→Migration→Test):
- `selection_result`(v2 §8:selection_id / strategy_id 占位 / as_of_date / symbol / rank / score /
factor_values / filter_status / selection_reason / created_at);
- `selection_snapshot`(spec 与 universe/条件快照 JSON,供复现与"历史某日选了什么"查询)。
- `selection_rule`(可选的命名规则持久化,MVP 允许后置:snapshot 已含完整 spec)。
- API(业务对象导向,AGENT.md §17):`POST /api/selections`(提交 spec+as_of,同步或 Job 计算并落库)、
`GET /api/selections/{id}`、`GET /api/selections?as_of=&strategy_id=`(历史选股查询)。
- 验收:提交一次选股 → 结果落库可查;重建同 spec+as_of 得到相同结果(可复现)。
### 3.4 回测一致性改造(v2 §25 红线)
- `TopKBacktestRunner._rebalance` 改为**调用 Selection Engine**(同 spec、同 as_of)得到候选
再进入成交/仓位逻辑——保证"当前选股 = 历史回测每期选股"用同一套代码。
- 默认配置下回测数值与改造前**逐项一致**(回归测试锁住)。
- 验收(v2 §27):`select(strategy, 历史调仓日)` 与回测当日持仓选股逐 symbol 一致。
### 3.5 Web 选股页(可后端就绪后并行)
- 新路由 `/selection`(v2 §19/§21.1):选股条件面板(范围 + A 条件 + B 评分/权重/TopN)+
as_of 日期(当前/历史)→ SelectionResult 表格(rank/score/因子值/理由)→「一键以该 spec 发起回测」。
- `lib/types.ts` 增加 SelectionSpec / SelectionResult;nav(app-shell)加入"股票筛选"入口。
- 验收:页面完成"选范围 → 配条件/评分 → 当前/历史 TopN 结果 → 跳回测"闭环;tsc --noEmit 通过。
### 3.6 M6 验收(里程碑完成判定)
- [ ] `select(spec, as_of=历史日)` 与回测该日调仓选股逐 symbol 一致(自动化测试)
- [ ] A/B 两模式都可对"当前日 / 历史日"执行并落库,`/api/selections` 可查
- [ ] 选股结果带 factor_values 与 selection_reason(可解释)
- [ ] Web 选股页闭环可用;SelectionResult DTO 前后端一致
- [ ] 全量 pytest 通过;相关迁移测试覆盖新表
---
## 5. M7:因子层落地(支撑选股增强,紧随 M6)
> 目标:把"因子"从代码注册表变成**可入库、可版本化、可组合**的研究资产,并修掉混合口径风险,
> 为选股 B 模式(更多/自定义因子)、因子研究做实提供基础。
- **4.1 因子定义入库 + 版本化目录**:Model `factor_definition` + seed 现有 9 因子元数据;
`GET /api/factors` 改读库;自定义因子元数据 CRUD(计算仍走代码注册表)。
- **4.2 Composite Factor Engine 模块化**:抽出 `quant/composite.py`
(`CompositeSpec{components[name,weight,direction], method}` → score 面板);
`factor_composite(+component)` 表;选股 B 模式与回测共用同一实现。验收:默认 spec 回测结果与现一致。
- **4.3 研究读取口径修复(横切)**:Repository 读路径按 `source/adjust` 过滤或提供 qfq 查询;
ResearchSpec/SelectionSpec 增加 `price_adjustment` 字段并写入结果 config_snapshot。
验收:含新浪补缺行的股票回测/选股口径一致且可溯源。
- **4.4 因子测试结果结构化(可选)**:Model `factor_test`,多因子 spec 不再静默只测第一个。
---
## 6. M8:引擎延伸与平台化(Signal / Portfolio / Strategy / Agent / 模型选股)
- **5.1 Signal Engine(规则型)**:`signal_rule / signal_event{signal_date, symbol, signal_type,
trigger_reason, score, price}`;回测与选股都能产事件并落库;API `POST /api/signals`、历史查询。
验收:能回答「某策略某日为什么对这只股票给 BUY/SELL」。
- **5.2 Portfolio Engine**:把回测器 `_rebalance` 资金/仓位逻辑抽为 portfolio 模块
(等权起步 + 单股/行业上限/现金可选约束,约束默认关闭并如实标注 unimplemented)。
验收:默认配置回测数值与现一致。
- **5.3 Strategy 模型与 /api/strategies**:聚合 universe + selection(spec) + signal + portfolio +
rebalance;Experiment 归档 strategy_version;Agent 增加 `create_strategy`。
- **5.4 Qlib 模型链路 = 选股模式 C**(按需):Alpha158 特征 + LightGBM walk-forward →
预测分 → Selection 模式 C。
- **5.5 AI Agent 工具补齐**(依赖上述引擎):`inspect_factor / create_composite_factor /
screen_stocks / explain_selection / generate_signals / create_strategy / get_backtest_result /
create_experiment`,全部经白名单 + Job/Experiment 链路;前端加最小 AI 对话入口。
- **5.6 Web 页面做实**(前端主线,随后端里程碑推进):回测页做实(trades/年度收益/持仓演化/成本参数/
config_snapshot,零新 API)、因子研究做实(IC 时序/对比)、Signal 页、Job SSE 进度 UI
(需先补 executor stage 上报,见 §6)。
---
## 7. 基础设施专项(含本机 Redis 的定位)
**本机已探测:Redis 服务在 127.0.0.1:6379 正常响应(无 redis-cli,连接经 socket 验证)。**
v2 §22 与 AGENT.md §19/§38 的原则是"第一版不引复杂队列;复杂后再引入 Redis + Celery/RQ"。
据此把 Redis 放入**明确的触发点**而非一开始就用:
| 触发场景 | Redis 用途 | 何时启用 |
|---|---|---|
| Job 并发/排队:subprocess 双并发上限打满、丢任务需可恢复队列 | RQ/普通队列替代 FastAPI BackgroundTasks 调度 | M8 之后 / 出现排队丢失问题 |
| Job 细粒度 stage + SSE 长连:executor 每阶段写 stage 后广播 | Redis pub/sub 做 SSE 事件总线(替代 0.4s 查库轮询) | M8.6 前端切 SSE 时(可先行) |
| 行情/因子查询热点:全市场选股重复预热 | 因子结果 / 最新交易日缓存(key 带 as_of 失效) | M6 选股高频查询后按 profile 决定 |
近期只需做一件准备工作:`config.yaml` 预留 `redis.url_env: "REDIS_URL"` + 可选
`redis.url: "redis://127.0.0.1:6379/0"` 配置段,代码不改;真正启用时再落基础设施代码。
**其余基础设施待办**(低优先):Job `cancelled` 取消 API 与 `/api/jobs` 列表端点;executor 增加 stage
上报(data_loading/factor_calculation/backtesting…)——这是前端做 SSE 进度条的前提。
---
## 8. 建议的执行顺序(前 6 个 commit 序列)
按 AGENT.md「最小修改、每阶段可跑、一功能一 commit」,以选股 MVP 为起点的推荐顺序:
1. **M6.0 选股契约**:Domain `SelectionSpec / SelectionResult` + `selection_service.select(spec, as_of)`
骨架(先同步执行、复用 ResearchService 数据装配)。验收:对最近交易日返回 TopN 候选 + 理由。
2. **M6.1 Universe 过滤执行器**:抽 `UniverseFilter`(复用 filter_stocks 语义)。
验收:当前/历史日范围正确、回测回归不变。
3. **M6.2 Selection Engine A + B**:条件选股(结构化条件 JSON)+ 因子评分 TopN(复用因子与
composite_score)。验收:A/B 模式可执行、结果含 factor_values/reason。
4. **M6.3 落库与 API**:`selection_result / selection_snapshot` 表 + Repo + `POST/GET /api/selections`。
验收:迁移测试 + 提交可查、重建一致。
5. **M6.4 回测共用 Selection**:`TopKBacktestRunner` 改调 Selection Engine。
验收:默认 spec 回测结果逐项一致 + 历史日一致性测试。
6. **M6.5 Web 选股页**:`/selection` 路由 + SelectionResult 表 + 一键跳回测。
之后进入 M7(因子层落地)与 M8(Signal/Portfolio/Strategy/Agent/模型选股)与 §6 基础设施专项。
每步都遵循:Domain → Repository Protocol → Infra(Model→Migration→Repo) → Service → API → 前端 →
测试(相关模块)→ 更新文档;新增表一律 Model→Alembic→测试。
---
## 9. 风险清单(开发中持续检查)
- **历史一致性**:回测改用 Selection Engine 后默认结果必须与改造前一致(回归测试锁住,别让重构悄悄改策略语义)。
- **未来函数 / 幸存者偏差**:财务条件按 `announce_date ≤ as_of` 过滤;universe 的 ST/行业/成分历史
口径补齐前,结果须明示近似(ST 当前名称快照等)。
- **数据口径**:选股/回测所用行情口径(不复权 vs 前复权)必须一致并可溯源(M7.3 收口)。
- **范围控制**:M6 阶段不做自由表达式 parser / 不做模型选股(C 模式)/ 不建 universe 大表;
均留到后续里程碑,避免一次性跨层堆量。
- **MySQL 行为差异**:TEXT 上限、批量 upsert 锁等待、pymysql 流式读取 —— 见 §1 遗留收尾表。
- **前端契约**:SelectionResult/SignalResult 先定 DTO(后端 entity)再画页面,避免前后端口径漂移。