Files
2026-08-31 16:27:21 +08:00

11 KiB
Raw Permalink Blame History

知行 B1 图形相似度评分集成设计

目标与边界

在不改变知行 B1 七个子信号公式、命中状态和稳定身份的前提下,为每只已命中的股票计算一次 0–100 完美图形相似度。评分使用原项目实际代码中的十个案例、25 日窗口、四维特征、权重、容忍参数和 60 分阈值,并修正为真正生效的 FastDTW 曲线对齐,在选股结果页展示匹配案例与分项。

本设计不包含图片生成、视觉模型、1–5 主观评分、PASS/WATCH/FAIL、自动交易、评分独立重跑、多评分器并存或跨策略通用评分平台。评分只属于 selection bounded context。

当前与目标数据流

当前执行链:

POST selection run
  -> load qfq histories in batches
  -> evaluate zhixing_b1 masks
  -> SelectionRunItem + category signals
  -> PostgreSQL
  -> GET results
  -> stocks[].signals[]

目标执行链:

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:

{
  "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/文档中明确分数是当前版本模板相似度,不是历史收益承诺。