Files
zhixing-system/.trellis/spec/backend/selection.md
T
2026-08-08 22:41:45 +08:00

84 lines
3.8 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.
# 历史选股代码规格
## 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
```python
# 会产生历史前视数据,并丢弃同日的其他分类。
target = history.bars[-1]
first = next(category for category in categories if masks[category].iloc[-1])
```
#### Correct
```python
# 用显式交易日定位,并保留所有独立 mask 的命中。
target_index = frame.index[frame["trade_date"] == target_trade_date][0]
matched = tuple(category for category in signal_order if masks[category].iloc[target_index])
```