chore(task): archive 08-29-integrate-b1-scoring
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
{"file":".trellis/spec/backend/selection.md","reason":"复核七个子信号、历史截断、批次状态和重跑语义未被评分改变。"}
|
||||
{"file":".trellis/spec/backend/http-api-contracts.md","reason":"复核新增评分响应与后端 HTTP 测试。"}
|
||||
{"file":".trellis/spec/backend/error-handling.md","reason":"复核评分失败隔离、去敏原因和选股失败语义。"}
|
||||
{"file":".trellis/spec/backend/quality-guidelines.md","reason":"执行后端格式、lint、strict type-check 与全量测试。"}
|
||||
{"file":".trellis/spec/frontend/type-safety.md","reason":"复核评分 TypeScript 契约无 any 或不安全断言。"}
|
||||
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"执行前端格式、lint、类型、测试和构建门禁。"}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"检查数据库到 UI 的评分字段与状态全链路一致。"}
|
||||
{"file":".trellis/tasks/08-29-integrate-b1-scoring/research/scoring-analysis.md","reason":"核对实际代码权重、十案例、阈值、缓存风险和 parity 目标。"}
|
||||
@@ -0,0 +1,162 @@
|
||||
# 知行 B1 图形相似度评分集成设计
|
||||
|
||||
## 目标与边界
|
||||
|
||||
在不改变知行 B1 七个子信号公式、命中状态和稳定身份的前提下,为每只已命中的股票计算一次 0–100 完美图形相似度。评分使用原项目实际代码中的十个案例、25 日窗口、四维特征、权重、容忍参数和 60 分阈值,并修正为真正生效的 FastDTW 曲线对齐,在选股结果页展示匹配案例与分项。
|
||||
|
||||
本设计不包含图片生成、视觉模型、1–5 主观评分、`PASS/WATCH/FAIL`、自动交易、评分独立重跑、多评分器并存或跨策略通用评分平台。评分只属于 `selection` bounded context。
|
||||
|
||||
## 当前与目标数据流
|
||||
|
||||
当前执行链:
|
||||
|
||||
```text
|
||||
POST selection run
|
||||
-> load qfq histories in batches
|
||||
-> evaluate zhixing_b1 masks
|
||||
-> SelectionRunItem + category signals
|
||||
-> PostgreSQL
|
||||
-> GET results
|
||||
-> stocks[].signals[]
|
||||
```
|
||||
|
||||
目标执行链:
|
||||
|
||||
```text
|
||||
POST selection run
|
||||
-> load the ten versioned case windows once for this run
|
||||
-> build an immutable in-memory case library
|
||||
-> load candidate qfq histories in existing batches
|
||||
-> evaluate zhixing_b1 masks
|
||||
-> if selected: score the stock once against all cases
|
||||
-> SelectionRunItem(score) + unchanged category signals
|
||||
-> PostgreSQL
|
||||
-> GET results with stocks[].score + stocks[].signals[]
|
||||
```
|
||||
|
||||
评分在公式评估之后执行。`no_signal`、`insufficient_history`、`missing_target_bar` 和 `data_error` 不运行评分;评分异常只影响该股票的评分状态,不改变 `SelectionRunItem.status`、signals 或批次的选股成功状态。
|
||||
|
||||
## 领域模型与模块边界
|
||||
|
||||
在 `modules/selection/domain/` 增加纯领域评分模块,负责案例定义、特征提取、四维匹配和结果值对象。该模块只依赖 NumPy/Pandas 与显式注入的评分配置,不导入 FastAPI、PostgreSQL 或 infrastructure。
|
||||
|
||||
建议领域类型包括:
|
||||
|
||||
- `PatternCaseDefinition`:案例 ID、名称、规范化 `ts_code`、突破日和窗口长度。
|
||||
- `PatternFeatures`:趋势、KDJ、量能和价格形态四组不可变特征。
|
||||
- `PatternScoreBreakdown`:四个 0–100 有限分项。
|
||||
- `PatternScore`:状态、原始总分、阈值、最佳案例、breakdown、版本和安全原因。
|
||||
- `ZhixingB1PatternScorer`:对一个 `StockHistory` 与不可变案例库执行确定性评分。
|
||||
|
||||
评分状态与选股状态分离,使用 `not_executed`、`matched`、`below_threshold` 和 `failed`。`matched` 表示最高分大于等于 60;`below_threshold` 表示计算成功但原 pipeline 不会 enrichment;`failed` 表示评分实际执行但输入、案例库或算法失败。选股失败仍只使用已有 evaluation status。
|
||||
|
||||
应用层增加评分用例或端口,由 `RunZhixingB1` 注入。每次 run 开始时加载一次案例库,每批复用已有候选 `StockHistory`,只给 `selected` 股票评分。相同股票命中的多个 category 共享一个股票级评分,不重复计算。
|
||||
|
||||
infrastructure 负责从 PostgreSQL 读取十个案例在各自 `breakout_date` 之前的 qfq 行情。查询必须参数化、升序、严格 `< breakout_date`,每个案例取最后 25 条。生产运行不访问旧项目 CSV、旧缓存或 Tushare。
|
||||
|
||||
## 算法兼容契约
|
||||
|
||||
版本一使用固定标识 `zhixing_b1_pattern_fastdtw_v1`。以下任何变化都必须升级版本:案例集合或突破日、窗口长度、特征公式、权重、容忍参数、FastDTW 半径或距离函数、阈值或非有限值处理。
|
||||
|
||||
版本一保留原运行代码的事实值:
|
||||
|
||||
| 项目 | 契约 |
|
||||
| --- | --- |
|
||||
| 案例数 | 10,保持缺少 `case_005` 的既有定义 |
|
||||
| 候选/案例窗口 | 25 个升序交易日;案例不包含突破日 |
|
||||
| 分项 | `trend_structure`、`kdj_state`、`volume_pattern`、`price_shape` |
|
||||
| 权重 | 0.10、0.20、0.25、0.45 |
|
||||
| 总分 | `round(weighted_sum * 100, 2)` |
|
||||
| 阈值 | 60.0,比较使用 `>=` |
|
||||
| 曲线距离 | 真正生效的 FastDTW;一维曲线使用标量欧氏距离,显式 `radius=1` |
|
||||
| 最佳案例 | 十个案例中总分最高者;稳定同分时按案例定义顺序 |
|
||||
|
||||
原文档中的 30% 趋势/25% 价格权重和 YAML 中未生效的动态权重不进入 v1。实现应把实际生效常量集中在版本化配置中,不能继续保留“配置看似可改但运行时忽略”的状态。
|
||||
|
||||
实施门禁已验证原代码的 `fastdtw(one_dimensional_curve, ..., dist=scipy.spatial.distance.euclidean)` 稳定抛出 `AxisError`,随后由 `_shape()` 回退 `_simple_dtw`。用户明确选择修正为真正生效的 FastDTW,因为允许局部时间对齐更符合评分要求。实现使用适配一维标量的欧氏距离并显式固定 `radius=1`,不依赖 SciPy 的向量函数;这会改变旧历史分数和阈值命中集合,因此必须使用新的 `zhixing_b1_pattern_fastdtw_v1` 版本,并以新 golden 锁定结果。
|
||||
|
||||
所有领域输出必须是有限数。对原 25 日窗口造成的非有限中间特征,通过 compatibility helper 复现旧 matcher 的最终比较结果,但不允许 `NaN`/`Infinity` 进入 dataclass、JSONB 或 HTTP。固定 fixture 必须覆盖该路径;没有证据证明兼容时,评分返回 `failed`,不伪造分数。
|
||||
|
||||
## 案例库构建与一致性
|
||||
|
||||
只迁移十条案例定义,不迁移原 `data/cache/b1_pattern_library_cache.json`。该缓存未被 Git 跟踪、没有失效协议且已与行情漂移,不能作为部署事实源。
|
||||
|
||||
每次 selection run 从 PostgreSQL 构建一次小型内存案例库,读取规模约为 250 行,避免跨 run 的磁盘缓存失效问题。十个案例必须全部成功、各有 25 条有效 qfq OHLCV,才将案例库标记为 ready;缺任一案例时本 run 的评分统一不可用,但选股照常执行。这个原子完整性检查是对旧项目“静默使用部分案例库”的有意收紧,避免同一个版本标识对应不同分母和结果。
|
||||
|
||||
案例特征使用数据库中当前最新修订的 qfq,符合现有历史分析语义。已落盘评分不会因后续 qfq 修订自动改变;用户显式重跑后允许得到基于最新修订数据的新分数。`market_sync_batch_id`、评分版本和案例定义共同提供解释上下文。
|
||||
|
||||
固定案例是评分模板,而不是目标交易日当时可知的市场事实。历史日期可能使用后来定义的案例,因此该分数解释为“使用 `zhixing_b1_pattern_fastdtw_v1` 模板对历史候选做相似度评价”,不能解释为无前视偏差的历史交易信号。
|
||||
|
||||
## 持久化设计
|
||||
|
||||
评分是每股一次的结果,存入 `selection_run_item`,不复制到 `selection_signal.details`。新增列建议为:
|
||||
|
||||
- `score_status VARCHAR(32) NOT NULL DEFAULT 'not_executed'`
|
||||
- `score_value NUMERIC(5,2) NULL`
|
||||
- `score_threshold NUMERIC(5,2) NULL`
|
||||
- `score_version VARCHAR(64) NULL`
|
||||
- `match_case_id VARCHAR(32) NULL`
|
||||
- `match_case_name VARCHAR(128) NULL`
|
||||
- `match_case_breakout_date DATE NULL`
|
||||
- `match_breakdown JSONB NULL`
|
||||
- `score_reason TEXT NULL`
|
||||
|
||||
约束保证总分和四个分项位于 0–100,`matched` 必须具备完整分数、案例、breakdown、阈值和版本;`below_threshold` 可以保留内部原始分数用于审计,但 HTTP 默认只表达“未达到 60”而不把它当作匹配结果;`failed` 不保存数值或案例,只保存去敏后的有限长度原因。旧 run 通过默认 `not_executed` 与空字段保持兼容。
|
||||
|
||||
增加 `(run_id, score_value DESC, ts_code)` 索引,为数据库级评分排序提供稳定分页。重跑仍删除旧 `selection_run` 并依赖级联清除 item/signal;不新增独立评分表,也不双写 signal details。
|
||||
|
||||
若未来需要同股多评分器、多个评分版本同时存在或评分独立重跑,再把 item 上的单份结果迁移到 `(run_id, ts_code, scorer, version)` 的独立表;当前需求不提前引入该复杂度。
|
||||
|
||||
## HTTP 与前端契约
|
||||
|
||||
`SelectionStockResponse` 增加可空的股票级 `score`:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "matched",
|
||||
"value": 86.4,
|
||||
"threshold": 60.0,
|
||||
"version": "zhixing_b1_pattern_fastdtw_v1",
|
||||
"case": {
|
||||
"id": "case_001",
|
||||
"name": "华纳药厂",
|
||||
"breakout_date": "2025-05-12"
|
||||
},
|
||||
"breakdown": {
|
||||
"trend_structure": 71.2,
|
||||
"kdj_state": 83.0,
|
||||
"volume_pattern": 88.0,
|
||||
"price_shape": 90.1
|
||||
},
|
||||
"reason": null
|
||||
}
|
||||
```
|
||||
|
||||
旧 run、未执行评分或字段全空时返回 `score: null`。评分失败返回 `status: failed` 与安全原因,但现有 signals 仍完整显示;不得把评分失败放入顶层 `failures[]`,该列表继续只表示选股评估失败。
|
||||
|
||||
结果查询增加可选 `sort=code|score_desc|score_asc`,默认 `code` 保持当前行为。排序和分页必须在 PostgreSQL 完成,稳定次级键为 `ts_code`;前端不能只排序当前页。评分筛选、只导出高分代码和独立排名暂不纳入 MVP。
|
||||
|
||||
前端在每只股票卡片/行的股票级区域展示总分、最佳案例和四个分项,七个 signal 继续展示各自原有 details。`below_threshold` 显示“未匹配到 60 分以上案例”,`failed` 显示“评分暂不可用”,两者都不能遮挡选股信号。页面提供按评分升降序的可访问控件,并保留默认代码排序。
|
||||
|
||||
## 失败、性能与并发
|
||||
|
||||
评分复用已加载的候选历史,只额外读取一次十个案例窗口。复杂度约为 `命中股票数 × 10 × 25` 的特征比较,且只对 selected 股票执行;不得为每个 category 或每个候选单独查询案例数据。
|
||||
|
||||
案例库初始化失败是 run 级评分不可用,不是选股批次失败。单股评分异常只将该股 `score_status` 置为 `failed`,其他股票继续。边界日志只记录 run ID、股票代码、评分版本和异常类型,不输出数据库连接、凭据或原始异常对象。
|
||||
|
||||
现有 FastAPI 进程内 background task 仍是执行边界;本任务不引入队列。实现必须测量新增评分耗时并写入结构化 run 日志,确认没有显著放大现有批次时长或连接池使用。
|
||||
|
||||
## 发布与回滚
|
||||
|
||||
迁移为向后兼容的可空列与索引。增加 `ZHIXING_SELECTION_PATTERN_SCORING_ENABLED` 配置,默认启用;紧急情况下可关闭评分,选股链恢复原行为,新 run 的 score 为 `not_executed`。
|
||||
|
||||
发布顺序为先执行数据库 upgrade,再发布同时理解新列的后端,最后发布前端。旧前端会忽略新增 JSON 字段;新前端对 `score: null` 安全降级。回滚应用时保留新增列不会影响旧代码,只有确认不再需要已保存评分时才执行 destructive downgrade。
|
||||
|
||||
## 主要风险与控制
|
||||
|
||||
- 原项目没有数值 golden:先冻结最小旧数据 fixture 和期望值,再实现迁移。
|
||||
- 原缓存漂移:不迁移缓存,每次 run 从 PostgreSQL 构建完整案例库。
|
||||
- 25 日窗口与 114 日指标产生非有限中间值:兼容 helper + 有限值断言 + golden 覆盖。
|
||||
- FastDTW 语义:使用标量欧氏距离与固定 `radius=1`,通过新 golden 锁定;不得把旧 `_simple_dtw` 期望值当作兼容目标,也不能在异常时静默退回另一种算法。
|
||||
- 同股多 category:评分只存 item 并在股票级响应展示,signal 身份和详情不变。
|
||||
- 历史模板前视解释:在 UI/文档中明确分数是当前版本模板相似度,不是历史收益承诺。
|
||||
@@ -0,0 +1,12 @@
|
||||
{"file":".trellis/spec/backend/index.md","reason":"后端规格入口与开发前检查。"}
|
||||
{"file":".trellis/spec/backend/directory-structure.md","reason":"保持 selection bounded context 的 domain/application/infrastructure/presentation 边界。"}
|
||||
{"file":".trellis/spec/backend/configuration-and-runtime.md","reason":"评分开关必须通过 Settings 与 ZHIXING_ 配置注入。"}
|
||||
{"file":".trellis/spec/backend/selection.md","reason":"保护 B1 目标交易日、qfq、七子信号、批次持久化和重跑契约。"}
|
||||
{"file":".trellis/spec/backend/http-api-contracts.md","reason":"新增 stocks[].score 时同步稳定 Pydantic 与同源 API 契约。"}
|
||||
{"file":".trellis/spec/backend/error-handling.md","reason":"评分失败需隔离并在边界安全表达,不能吞掉选股错误。"}
|
||||
{"file":".trellis/spec/backend/quality-guidelines.md","reason":"Python 3.12、Ruff、Pyright strict 与 pytest 实施要求。"}
|
||||
{"file":".trellis/spec/frontend/index.md","reason":"前端 selection feature 与跨层字段变更入口。"}
|
||||
{"file":".trellis/spec/frontend/type-safety.md","reason":"为评分响应定义严格 TypeScript 类型并同步 API 契约。"}
|
||||
{"file":".trellis/spec/frontend/component-guidelines.md","reason":"在现有股票结果 UI 中以可访问方式展示评分状态和分项。"}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"评分字段贯穿后端、API、query、类型和页面测试。"}
|
||||
{"file":".trellis/tasks/08-29-integrate-b1-scoring/research/scoring-analysis.md","reason":"原评分算法、案例资产、缓存风险与当前集成接缝的源码证据。"}
|
||||
@@ -0,0 +1,105 @@
|
||||
# 知行 B1 图形相似度评分实施计划
|
||||
|
||||
## 实施前门禁
|
||||
|
||||
- [ ] 用户明确批准本次最终规划摘要;批准前不运行 `task.py start`,不修改产品代码。
|
||||
- [ ] 使用 `trellis-before-dev` 加载 backend、frontend 与跨层规格。
|
||||
- [ ] 确认当前工作区只包含用户已有修改和本任务规划文件,记录不可覆盖的改动。
|
||||
- [x] 在 Python 3.12 下点验原 FastDTW 调用:依赖可安装/导入,但一维曲线配合 SciPy 欧氏距离稳定抛出 `AxisError`,原实现实际回退 `_simple_dtw`。
|
||||
- [x] 用户确认版本一采用真正生效的 FastDTW,接受与旧 `_simple_dtw` 分数不兼容;版本固定为 `zhixing_b1_pattern_fastdtw_v1`、标量欧氏距离、`radius=1`。
|
||||
|
||||
## 1. 冻结兼容基线
|
||||
|
||||
- [ ] 从原项目十个案例 CSV 中提取严格早于 breakout date 的最小 25 日窗口,并选取代表性的候选窗口,写入 `zhixing-server/tests/fixtures/selection/zhixing_b1/pattern_scoring/`;不复制完整生产数据。
|
||||
- [ ] 使用原项目实际特征、权重和容忍参数,以及修正后的 FastDTW 路径离线生成期望的案例特征、四个分项、最佳案例和总分 JSON;测试运行时不导入原项目。
|
||||
- [ ] fixture 覆盖最高分大于等于 60、低于 60、同分稳定顺序、窗口不足、空案例、非有限中间特征和十案例完整性。
|
||||
- [ ] 记录原实现中被保留的行为及有意收紧的行为:实际权重优先于过时文档;完整案例库失败时不使用部分库;持久化和 HTTP 禁止非有限值。
|
||||
|
||||
回滚点:如果无法生成稳定的有限期望值,停止实现并回到规划,不猜测算法结果。
|
||||
|
||||
## 2. 实现纯领域评分
|
||||
|
||||
- [ ] 在 `modules/selection/domain/` 增加版本化案例定义、评分值对象、特征提取器、经确认的 DTW matcher 与 `ZhixingB1PatternScorer`;公开类型写完整 docstring、参数、返回值、异常与设计原因。
|
||||
- [ ] 迁移十个案例、25 日窗口、四维特征、`0.10/0.20/0.25/0.45` 权重、原容忍参数、60 分阈值和稳定 best-match 规则。
|
||||
- [ ] 集中实现有限值兼容 helper,保证领域对象从不包含 `NaN` 或 `Infinity`。
|
||||
- [ ] 增加领域单元/golden 测试,证明固定输入与旧实现期望一致且多次运行确定。
|
||||
- [ ] 更新 `pyproject.toml` 与 `uv.lock`,只引入实际运行所需依赖。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv run pytest tests/unit/selection -q
|
||||
uv run pyright
|
||||
uv run ruff check .
|
||||
```
|
||||
|
||||
## 3. 构建 PostgreSQL 案例库适配器
|
||||
|
||||
- [ ] 定义 selection application/domain 所需的 case history port,不让领域层依赖 psycopg。
|
||||
- [ ] 在 selection infrastructure 中实现参数化批量查询:规范化 `ts_code`、`source_adj='qfq'`、严格 `< breakout_date`、升序、每案例最后 25 行。
|
||||
- [ ] 每个 run 只读取和构建一次完整案例库;验证十个案例各有 25 条有效 OHLCV,禁止静默部分成功。
|
||||
- [ ] 用 fake connection 测试 SQL 参数、日期边界、排序、代码映射、缺失案例和数据库错误转换;有测试库时补 PostgreSQL 集成测试。
|
||||
|
||||
回滚点:案例库 adapter 独立合入前不得改变现有 selection run 结果。
|
||||
|
||||
## 4. 接入选股应用编排
|
||||
|
||||
- [ ] 给 `RunZhixingB1` 注入 scorer/case-library loader;在 run 开始时准备库,在已有 evaluator 返回 `selected` 后复用对应 `StockHistory` 评分一次。
|
||||
- [ ] 扩展 `SelectionRunItem` 承载股票级 score;保留 signals、`signal_count`、选股 status 和 reason 的原语义。
|
||||
- [ ] 评分 `failed` 或 `below_threshold` 不进入现有失败计数,不改变 run 的 `success/partial_success/failed` 聚合。
|
||||
- [ ] 单元测试 selected/no-signal/评估失败/案例库失败/单股评分失败/同股七 category 只评分一次/批次继续执行。
|
||||
- [ ] 增加 feature flag,并通过 `Settings`、依赖注入和 Compose 环境变量统一配置;业务代码不直接读取环境。
|
||||
|
||||
## 5. 扩展数据库与仓储
|
||||
|
||||
- [ ] 新建 Alembic migration,为 `selection_run_item` 增加评分状态、数值、版本、案例、breakdown、原因及排序索引;同时更新声明式 schema。
|
||||
- [ ] 增加数据库 check constraints,拒绝越界或不完整 matched 结果;旧行安全回填 `not_executed`。
|
||||
- [ ] 更新 batch upsert、run loader、重跑级联和查询对象,保持 item 与 signal 同事务落盘。
|
||||
- [ ] 增加 `code|score_desc|score_asc` 的白名单排序,数据库分页使用 `score_value` 与 `ts_code` 稳定排序;不得拼接用户原始 SQL。
|
||||
- [ ] 仓储测试覆盖 round-trip breakdown、旧行空 score、排序分页、category 过滤仍返回全部 signals、重跑清理和 migration upgrade/downgrade SQL。
|
||||
|
||||
回滚点:迁移为 additive;应用回滚时保留列。执行 downgrade 前必须确认已保存评分允许删除。
|
||||
|
||||
## 6. 扩展 HTTP 与前端
|
||||
|
||||
- [ ] 后端增加具名 Pydantic score/case/breakdown 响应模型,在 `stocks[].score` 返回股票级结果;`failures[]` 继续只表示选股评估失败。
|
||||
- [ ] HTTP 测试覆盖 matched、below-threshold、failed、旧 run `score: null`、多 category、三种排序和分页稳定性。
|
||||
- [ ] 同步更新 `selection.types.ts`、API query 参数和 React Query key,保持同源 `/api/v1` 请求。
|
||||
- [ ] 在 selection workbench 的股票级区域展示总分、案例、分项、低于阈值与评分失败状态;signals 原详情不变。
|
||||
- [ ] 增加可访问的评分排序控件,默认仍为代码排序;测试用户可见文本、控件行为和分页请求参数。
|
||||
|
||||
## 7. 全量验证与发布检查
|
||||
|
||||
- [ ] 后端执行格式、lint、strict type-check、全量测试、migration offline SQL;设置 `ZHIXING_TEST_DATABASE_URL` 时执行 PostgreSQL 集成测试。
|
||||
- [ ] 前端执行格式、lint、type-check、测试和 build。
|
||||
- [ ] 根目录执行完整门禁,并记录实际结果,不能用计划命令冒充已验证。
|
||||
- [ ] 用固定 run fixture 或本地测试库核对:选中股票与七个 signals 在开关前后完全一致,只有股票级评分字段新增。
|
||||
- [ ] 核对一次 run 只读取一次案例库、每股只评分一次、没有按 category 重复计算;记录评分耗时和数据库查询数。
|
||||
- [ ] 验证关闭 `ZHIXING_SELECTION_PATTERN_SCORING_ENABLED` 后旧流程仍成功、HTTP 安全返回空 score。
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv run ruff format --check .
|
||||
uv run ruff check .
|
||||
uv run pyright
|
||||
uv run pytest
|
||||
uv run alembic upgrade head --sql
|
||||
uv run alembic downgrade -1 --sql
|
||||
|
||||
cd ../zhixing-web
|
||||
pnpm format:check
|
||||
pnpm lint
|
||||
pnpm typecheck
|
||||
pnpm test
|
||||
pnpm build
|
||||
|
||||
cd ..
|
||||
./dev.sh check
|
||||
./dev.sh test
|
||||
```
|
||||
|
||||
## 交付与后续
|
||||
|
||||
- [ ] 交付时报告算法版本、案例完整性、数值 parity、测试结果、性能数据和是否运行真实 PostgreSQL 集成测试。
|
||||
- [ ] 将“评分筛选/代码导出”“独立评分重跑”“多评分器/版本并存”“1–5 主观视觉评分”保留为独立后续需求,不在本任务顺带实现。
|
||||
@@ -0,0 +1,50 @@
|
||||
# 知行 B1 集成原项目评分
|
||||
|
||||
## Goal
|
||||
|
||||
在不改变知行 B1 选股语义的前提下,复用原项目的案例、特征、权重和阈值,并修正曲线距离为真正生效的 FastDTW,为每只 B1 命中股票提供可解释、可持久化、可验证的 0–100 最佳案例匹配结果。
|
||||
|
||||
## Background
|
||||
|
||||
当前系统已具备 `POST /api/v1/selection/runs`、后台批量评估、PostgreSQL 结果持久化、结果查询与前端轮询展示;策略固定为 `zhixing_b1`,按显式目标交易日读取 qfq OHLCV,并独立保留七种子信号(`.trellis/spec/backend/selection.md:12-49,85-152`,`docs/adr/0005-selection-formula-semantics-and-independent-subsignals.md:7-23`)。评分尚未接入。
|
||||
|
||||
用户已明确本任务只迁移 Python 可执行的 0–100 图形相似度评分,不迁移 prompt 中依赖图片和大模型的 1–5 主观视觉评分。
|
||||
|
||||
实施门禁发现原源码的一维 FastDTW 调用实际抛错并回退 `_simple_dtw`;用户进一步确认版本一直接修正为真正生效的 FastDTW,因为允许局部时间对齐更符合评分要求。新分数使用独立版本,不承诺兼容旧 `_simple_dtw` 历史结果。
|
||||
|
||||
原可执行评分在候选信号产生后运行,对候选最近 25 个交易日与十个固定案例比较趋势结构、KDJ、量能和价格形态,实际权重为 `0.10/0.20/0.25/0.45`,总分为加权和乘以 100;每股只保留最高分案例,达到 `60.0` 才 enrichment(`/Users/yuxuanhui/bcc-github/quant-project/zgnb/zgnb-project/src/zgnb/domain/pattern/config.py:8-38`,`/Users/yuxuanhui/bcc-github/quant-project/zgnb/zgnb-project/src/zgnb/domain/pattern/matcher.py:19-128`,`/Users/yuxuanhui/bcc-github/quant-project/zgnb/zgnb-project/src/zgnb/application/pipeline.py:168-220`)。原文档权重与运行代码不一致,YAML 动态权重也未真正注入,迁移以实际运行代码为兼容基线。
|
||||
|
||||
原案例行情与缓存均未被 Git 跟踪,缓存没有版本或失效校验且已与当前行情漂移;原项目也没有评分数值 golden 测试。完整证据记录在 `research/scoring-analysis.md`。
|
||||
|
||||
## Requirements
|
||||
|
||||
- R1:评分必须是 B1 命中后的 enrichment,不参与七个 mask 的判断,不改变股票是否选中、同股多 category、signals 顺序或 `(ts_code, target_trade_date, strategy, category)` 稳定身份。
|
||||
- R2:版本一固定使用原运行代码的十个案例、25 日升序窗口、四维特征、`0.10/0.20/0.25/0.45` 权重、容忍参数、最佳案例规则和 `>= 60.0` 阈值;曲线距离使用真正生效、显式半径的 FastDTW,版本标识为 `zhixing_b1_pattern_fastdtw_v1`。算法、案例、FastDTW 半径或阈值变化必须升级评分版本。
|
||||
- R3:案例定义属于代码中的版本化业务规则;案例特征在每次 run 中从 PostgreSQL 最新 qfq 行情完整构建,严格使用突破日前最后 25 个交易日,不依赖旧项目、本地 CSV、Tushare 或旧磁盘缓存。
|
||||
- R4:每个 run 只加载一次完整案例库,每只 `selected` 股票只评分一次;同股七个 category 共享股票级评分,不能复制成 category 级规则。
|
||||
- R5:评分结果存入 `selection_run_item`,与选股 evaluation status 分离;结果包含状态、有限的 0–100 总分、60 分阈值、评分版本、最佳案例、四个有限分项和安全原因。旧 run 保持可读。
|
||||
- R6:案例库缺失、单股评分异常、低于阈值或关闭评分都不得使选股失败,也不得进入现有选股失败计数;状态必须能区分 `not_executed`、`matched`、`below_threshold` 和 `failed`。
|
||||
- R7:HTTP 在 `stocks[].score` 返回可空的股票级评分,现有 `stocks[].signals[]` 与 `failures[]` 语义不变;后端支持稳定的代码、评分升序和评分降序数据库分页。
|
||||
- R8:前端在股票级区域展示匹配分数、案例、四个分项以及低于阈值/评分失败状态,并提供评分排序;任何评分状态都不能遮挡已命中的 signals。
|
||||
- R9:所有持久化和 HTTP 数值必须有限;原 25 日窗口产生的非有限中间特征必须通过离线兼容 fixture 锁定最终行为,不能把 `NaN` 或 `Infinity` 写入数据库或响应。
|
||||
- R10:提供 `ZHIXING_SELECTION_PATTERN_SCORING_ENABLED` 运行开关;关闭后选股链维持原行为,新结果不产生评分。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 离线 fixture 不依赖原项目或网络,数值 golden 覆盖十案例最佳匹配、四分项、总分、阈值边界、稳定同分、窗口不足和非有限中间值,并锁定修正后 FastDTW 版本一的确定结果。
|
||||
- [ ] 对同一固定 B1 run,开启和关闭评分得到完全相同的选中股票、七个 category、signal details 和选股批次状态,差异只在股票级评分字段。
|
||||
- [ ] 一只同时命中多个 category 的股票只调用一次 scorer,只保存和返回一个 `stocks[].score`,全部 signals 仍按既有顺序返回。
|
||||
- [ ] 十个案例均存在时,最高分 `>= 60` 的股票返回完整 matched score、案例、版本和四个分项;低于 60 时返回明确的 below-threshold 状态而不伪装成匹配。
|
||||
- [ ] 任一案例缺失或单股评分抛错时,选股继续并保留 signals;评分返回 failed/不可用状态,现有 `failed_count` 与 `failures[]` 不增加。
|
||||
- [ ] 旧 run 和关闭评分产生的 run 可由新后端与前端安全读取,`score` 为空或 not-executed,不影响原页面功能。
|
||||
- [ ] `code`、`score_desc` 和 `score_asc` 排序在 PostgreSQL 分页前执行,并以 `ts_code` 作为稳定次级键;前端不会只重排当前页。
|
||||
- [ ] 一次 run 只读取一次案例库且不按股票/category 重复查询;验证记录包含评分耗时和查询/调用次数。
|
||||
- [ ] Alembic upgrade/downgrade SQL、后端 Ruff/Pyright/pytest、前端 format/lint/typecheck/test/build 和根目录门禁全部通过;未配置 PostgreSQL 测试库时明确报告跳过项。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- prompt 中的 1–5 主观视觉评分、图片生成、视觉模型调用和 `PASS/WATCH/FAIL`。
|
||||
- 改写知行 B1 公式、合并七个子信号、让评分反向决定是否入选或用于自动交易。
|
||||
- 评分独立重跑、同股多评分器或多版本并存、通用评分平台。
|
||||
- 评分阈值筛选、只导出高分股票代码和独立排名页面;MVP 只提供结果展示与排序。
|
||||
- 兼容原项目实际回退的 `_simple_dtw` 历史分数;FastDTW v1 是用户明确选择的新评分版本。
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# 原项目 B1 图形相似度评分调研
|
||||
|
||||
## 结论
|
||||
|
||||
本任务迁移的是原项目 Python 已执行并持久化的 0–100 B1 完美图形相似度评分,不包含 `prompt/b1.md` 定义的 1–5 主观视觉评分。相似度评分属于 B1 命中后的 enrichment,不参与七个子信号的命中判断。
|
||||
|
||||
原执行链为 `SelectionPipeline._enrich_with_pattern_match()` 调用 `B1PatternLibrary.find_b1_best_match()`,对每只候选股票计算一次结果,再把同一结果写入该股票的信号详情。关键源码位于:
|
||||
|
||||
- `/Users/yuxuanhui/bcc-github/quant-project/zgnb/zgnb-project/src/zgnb/application/pipeline.py:168-220`
|
||||
- `/Users/yuxuanhui/bcc-github/quant-project/zgnb/zgnb-project/src/zgnb/domain/pattern/library.py:22-101`
|
||||
- `/Users/yuxuanhui/bcc-github/quant-project/zgnb/zgnb-project/src/zgnb/domain/pattern/feature_extractor.py:22-154`
|
||||
- `/Users/yuxuanhui/bcc-github/quant-project/zgnb/zgnb-project/src/zgnb/domain/pattern/matcher.py:19-128`
|
||||
- `/Users/yuxuanhui/bcc-github/quant-project/zgnb/zgnb-project/src/zgnb/domain/pattern/config.py:8-38`
|
||||
|
||||
## 算法事实
|
||||
|
||||
候选与案例都取最近 25 个升序交易日,提取四组特征:趋势结构、KDJ 状态、量能形态和价格形态。四组实际代码权重分别为 `0.10`、`0.20`、`0.25` 和 `0.45`,总分为分项相似度加权和乘以 100,保留两位小数。文档中 `0.30/0.20/0.25/0.25` 的权重与当前运行代码不一致,不能作为迁移基线。
|
||||
|
||||
价格曲线源码先尝试 `fastdtw` 和 SciPy 欧氏距离,异常时回退 `_simple_dtw`;原项目把 `fastdtw>=0.3.4` 与 `scipy>=1.10.0` 声明为正式依赖。实施门禁在 Python 3.12 上用相同的一维数组调用点验,`scipy.spatial.distance.euclidean` 接收到标量后稳定抛出 `AxisError: axis -1 is out of bounds for array of dimension 0`,因此原 `_shape()` 实际捕获异常并使用 `_simple_dtw`。这说明旧项目落地运行结果的曲线分数来自 simple-DTW fallback,而不是 FastDTW 成功路径。匹配十个固定案例后只保留最高分案例,最高分达到 `60.0` 才向外提供 `similarity_score`、`match_case` 和四个分项。
|
||||
|
||||
案例窗口严格使用 `breakout_date` 之前的数据,不包含突破日。十个案例为 `688799.SH`、`600366.SH`、`688321.SH`、`600601.SH`、`002074.SZ`、`605378.SH`、`600184.SH`、`301076.SZ`、`002940.SZ` 和 `000547.SZ`;原编号缺少 `case_005`,迁移时保持既有十条定义,不自行补案例。
|
||||
|
||||
## 案例资产与兼容风险
|
||||
|
||||
原项目 `data/raw/` 行情和 `data/cache/b1_pattern_library_cache.json` 都被 `.gitignore` 排除,不属于可部署资产。缓存没有算法版本、案例定义哈希、行情修订或完整性校验;本机缓存与当前 CSV 重算结果已有八个案例发生差异。因此新系统不能复制该缓存作为事实源,应迁移案例定义并从 PostgreSQL 最新 qfq 行情构建案例特征。
|
||||
|
||||
原特征提取器先截取 25 行,再计算最长 114 日均线,导致部分趋势字段为非有限值。迁移必须通过固定 fixture 锁定原 matcher 对这些中间值的最终有限分数行为,禁止把 `NaN` 写入 PostgreSQL 或 HTTP。若无法得到有限、确定的结果,应将评分标记为失败,但不得改变选股结果。
|
||||
|
||||
原项目没有案例特征、窗口截断或评分数值 golden 测试,只测试了字段透传与排序。新系统必须把从旧 CSV 提取的最小窗口和离线期望结果纳入测试 fixture;测试运行时不得依赖原项目、本机缓存、Tushare 或生产数据库。
|
||||
|
||||
## FastDTW 决策
|
||||
|
||||
用户确认版本一不兼容旧 `_simple_dtw` fallback,而是直接修正为真正生效的 FastDTW,因为允许局部时间轴对齐更符合业务期望。新版本使用一维标量欧氏距离、显式 `radius=1` 和版本标识 `zhixing_b1_pattern_fastdtw_v1`;不得在 FastDTW 异常时静默切回 simple-DTW。原项目的十案例、特征、权重、容忍参数和 60 分阈值继续复用,数值 golden 以修正后的新算法为准。
|
||||
|
||||
## 当前系统接缝
|
||||
|
||||
当前 B1 执行链为 HTTP 创建 run、批量读取 `StockHistory`、并发评估、写入 `selection_run_item` 与 `selection_signal`、查询并按股票聚合到 `stocks[].signals[]`。评分是每股一次的结果,最合适的持久化位置是 `selection_run_item`,而不是每条 `selection_signal.details`。
|
||||
|
||||
关键依据:
|
||||
|
||||
- `.trellis/spec/backend/selection.md`
|
||||
- `zhixing-server/src/zhixing_server/modules/selection/application/run.py:91-159`
|
||||
- `zhixing-server/src/zhixing_server/modules/selection/domain/runs.py:19-58`
|
||||
- `zhixing-server/src/zhixing_server/modules/selection/infrastructure/postgres_runs.py:184-229,355-454`
|
||||
- `zhixing-server/src/zhixing_server/modules/selection/presentation/http.py:97-137,273-323`
|
||||
- `zhixing-web/src/features/selection/api/selection.types.ts:36-83`
|
||||
|
||||
当前结果以股票为分页实体,一股可以拥有多个 category。把 score 复制到 signal details 会造成重复与 category 语义混淆,也不利于数据库级排序。为 `selection_run_item` 增加可空、版本化的评分列能复用现有主键、批量 upsert、重跑级联与股票聚合读取。
|
||||
|
||||
## 已验证基线
|
||||
|
||||
调研阶段后端全量测试基线为 `83 passed, 2 skipped`,两个跳过项需要 `ZHIXING_TEST_DATABASE_URL`;B1 相关单元、golden 与 HTTP 测试为 `42 passed`。原项目运行点验因环境导入名不匹配失败,过程中临时产生的 `.venv` 与 `uv.lock` 已移至系统废纸篓,没有保留对原项目的改动。
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "integrate-b1-scoring",
|
||||
"name": "integrate-b1-scoring",
|
||||
"title": "知行 B1 集成原项目评分",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "yuxuanhui",
|
||||
"assignee": "yuxuanhui",
|
||||
"createdAt": "2026-08-29",
|
||||
"completedAt": "2026-08-31",
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
Reference in New Issue
Block a user