# 原项目 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` 已移至系统废纸篓,没有保留对原项目的改动。