历史选股代码规格
Scenario: zhixing_b1 历史公式评估
1. Scope / Trigger
- 触发:新增
modules/selection bounded context,基于 PostgreSQL 已保存的 qfq
日线执行历史知行 B1 公式。
- 边界:selection 只读市场事实并返回领域评估结果;不负责市场数据同步、信号
持久化、批次调度、HTTP 路由或前端展示。
2. Signatures
MarketDataReader.load_history(ts_code: str, target_trade_date: date) -> StockHistory
ZhixingB1Strategy.evaluate(history: StockHistory, target_trade_date: date) -> SelectionEvaluation
EvaluateZhixingB1.execute(ts_code: str, target_trade_date: date) -> SelectionEvaluation
SelectionSignal.identity -> tuple[str, date, str, str]
3. Contracts
StockHistory.bars 必须是升序、去重的 qfq 行情,且不得包含目标交易日之后的
数据;daily_basic 按交易日保存同日可空指标。
SelectionBar 使用有限的 float | None 表示 OHLCV;目标日的 open/high/low/
close/volume 任一缺失时不得生成信号。
- 策略名称固定为
zhixing_b1;7 个分类固定为
zhixing_b1_oversold_turn、zhixing_b1_oversold_volume、
zhixing_b1_original_b1、zhixing_b1_extreme_volume、
zhixing_b1_pullback_white、zhixing_b1_pullback_super、
zhixing_b1_pullback_yellow。
- 同一股票同一交易日可以返回多个分类;唯一身份是
(ts_code, target_trade_date, strategy, category),返回顺序遵循
ZHIXING_B1_SIGNAL_ORDER。
- PostgreSQL reader 必须参数化查询
source_adj = 'qfq' 且
trade_date <= target_trade_date,左连接同日 market_daily_basic;不得回退到
CSV、Tushare 或当前最后一行。
- 评估状态区分
selected、no_signal、insufficient_history、
missing_target_bar 和 data_error。业务状态不是异常,数据库读取失败才映射为
data_error。
4. Validation & Error Matrix
| 条件 |
行为 |
| 少于公式最小暖机长度 |
返回 insufficient_history,不返回信号 |
| 目标日没有 qfq bar 或 OHLCV 不完整 |
返回 missing_target_bar |
| 目标日数据完整但无分类命中 |
返回 no_signal |
| PostgreSQL 读取失败 |
返回 data_error,保留股票和目标日上下文 |
| rolling 窗口不足 |
只使用已到达的交易行;EVERY 等需要完整窗口的条件不命中 |
| 除零、NaN 或无穷中间值 |
转为 NaN/False,不得静默制造命中 |
5. Good / Base / Bad Cases
- Good:给定历史目标日,reader 只返回该日及之前的 qfq 行,策略返回可序列化详情
和全部命中分类。
- Base:同日多分类命中时每类都保留稳定身份;同日
daily_basic 缺失只影响实际
依赖该指标的条件。
- Bad:用当前最新市值覆盖历史 K 线、取 bars 最后一行代替显式目标日,或把 7 类
mask OR 成一个结果后丢失分类。
6. Tests Required
- 指标单元测试:rolling 暖机、交易日
REF、窗口边界、除零、NaN、宽幅代码参数。
- 策略单元测试:目标日截断、暖机/缺失状态、7 个 mask 独立存在和同日多分类身份。
- reader 单元测试:参数化 SQL、qfq 过滤、目标日截断、升序映射和 left join 可空值。
- golden 测试:离线固定 fixture 可重复运行;不得在测试运行时导入旧项目或访问生产库。
7. Wrong vs Correct
Wrong
Correct
Scenario: 持久化策略执行结果与 HTTP 重跑
1. Scope / Trigger
- Trigger:为已有历史选股公式增加每日批次持久化、HTTP 触发/查询和 Web 轮询时,沿用
selection bounded context;不要让查询请求重新计算公式。
- 触发入口是
POST /api/v1/selection/runs,结果读取入口是
GET /api/v1/selection/runs/{run_id} 和
GET /api/v1/selection/results?strategy=...&target_trade_date=...。
2. Signatures
SelectionUniverseReader.load_execution_source(strategy: str, target_trade_date: date) -> SelectionExecutionSource
SelectionRunStore.prepare_run(strategy, target_trade_date, source, *, rerun: bool) -> SelectionRun
POST /api/v1/selection/runs 请求:
{"strategy": "zhixing_b1", "target_trade_date": "YYYY-MM-DD", "rerun": false};成功返回
202 和 {run_id, strategy, target_trade_date, status: "running"}。
selection_run 的业务唯一键是 (strategy, target_trade_date);
selection_run_item 的唯一键是 (run_id, ts_code);
selection_signal 的唯一键是 (run_id, ts_code, category)。
3. Contracts
load_execution_source 必须先确认目标日存在 strategy_eligible=true 且已完成的
success/partial_success 市场同步批次,并且股票为 active 且目标日 qfq bar/basic
完整;来源批次 ID、目标数、实际参与数和 coverage 写入 run。
- 市场数据预检在删除旧结果之前执行。预检失败不得创建新 run,也不得破坏已有终态结果。
- 同一策略同一目标日的重跑在一个短事务内使用 advisory transaction lock,删除旧 run
(依赖子表
ON DELETE CASCADE)并创建唯一的新 running run;长时间的逐股计算在
事务外执行。
- 查询响应必须返回 run 状态、批次统计、coverage、失败股票和全部独立 signals;同一
股票同日的多 category 不能合并,并按
ZHIXING_B1_SIGNAL_ORDER 稳定排序。
- 前端仅在首次无结果时直接触发;已有终态结果或失败重试必须先确认,再传
rerun=true。运行中重复请求返回冲突,不能创建第二个当前 run。
4. Validation & Error Matrix
| 条件 |
行为 |
| 不支持的策略、非法日期或缺少合格市场数据 |
422,错误码 market_data_not_ready(输入校验仍使用 FastAPI 默认 422) |
同策略同日已有 running run |
409,错误码 run_in_progress |
已有终态 run 且 rerun=false |
409,错误码 rerun_confirmation_required |
| PostgreSQL 读写失败 |
503,错误码 selection_storage_unavailable |
| run ID 不存在 |
404,错误码 run_not_found |
| 单只股票评估抛出异常 |
记录该 item 为 data_error,继续其他股票;批次最终为 partial_success 或 failed |
| 全股票评估完成但没有命中 |
批次为 success、signal_count=0;前端显示“没有命中信号”,不是“无数据” |
5. Good / Base / Bad Cases
- Good:目标日已有合格同步批次,首次 POST 返回
202,轮询最终结果保留同一股票的
两个独立 category;确认重跑后旧 signals 随旧 run 级联清除。
- Base:目标日没有结果时查询返回
200 status=no_data;查询不触发公式计算,用户可在
页面选择日期后发起首次执行。
- Bad:在市场数据预检前删除旧 run;把多个 category OR 成一条 signal;用浏览器长连接
等待全股票池计算;或把进程异常留下的
running 伪装成成功。
6. Tests Required
- Domain/application:断言 source 预检先于
prepare_run、首次执行、多分类落盘、单股异常
继续执行、全部 no-signal 和失败计数/终态聚合。
- PostgreSQL adapter:用 fake connection 断言 advisory lock、终态重跑删除后插入、运行中
冲突、JSONB details、级联删除契约和公式优先级排序。
- HTTP:用
TestClient(create_app()) 断言 202、409 两类冲突、422 数据未就绪、
GET 无数据、run 轮询和终态 signals。
- Migration:在可用 PostgreSQL 中断言三张 selection 表、唯一键、索引、cascade 外键,
并验证 downgrade 顺序;无数据库时至少生成 offline upgrade/downgrade SQL。
- Frontend:断言加载、查询失败、无数据、执行中、失败、部分成功、无命中、多 category,
以及重跑/失败重试确认取消不发 POST、确认发送
rerun=true。
7. Wrong vs Correct
Wrong
Correct