chore(task): 归档已完成任务
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
{"file":".trellis/spec/backend/index.md","reason":"检查新增选股上下文是否遵循后端入口和模块边界。"}
|
||||
{"file":".trellis/spec/backend/directory-structure.md","reason":"检查 domain、application、infrastructure、presentation 的依赖方向和目录职责。"}
|
||||
{"file":".trellis/spec/backend/market-data-sync.md","reason":"检查 reader 是否只读 qfq 市场事实,并正确处理目标日、六年窗口和数据缺失。"}
|
||||
{"file":".trellis/spec/backend/error-handling.md","reason":"检查数据错误是否可识别、未被吞掉,并与无信号状态区分。"}
|
||||
{"file":".trellis/spec/backend/quality-guidelines.md","reason":"执行并核对 Ruff、Pyright、pytest 及直接依赖声明。"}
|
||||
{"file":"docs/adr/0001-bounded-context-first-modular-monolith.md","reason":"检查没有跨上下文引入全局层或把业务规则放入 shared。"}
|
||||
{"file":"docs/adr/0003-postgresql-as-market-data-store.md","reason":"检查没有把 CSV、旧项目最新市值或非 qfq 数据作为运行时事实。"}
|
||||
{"file":"docs/adr/0004-tushare-six-year-snapshot-sync.md","reason":"检查历史目标日、有效数据和同步资格约束没有被策略实现绕过。"}
|
||||
{"file":".trellis/tasks/08-08-migrate-zhixing-b1/research/legacy-zhixing-b1.md","reason":"检查公式迁移、7 个分类、旧实现差异和 fixture 证据是否保留。"}
|
||||
@@ -0,0 +1,217 @@
|
||||
# 知行 B1 选股策略迁移设计
|
||||
|
||||
## 1. 设计目标
|
||||
|
||||
在新项目中建立 `selection` bounded context,完成 `zhixing_b1` 的第一条公式级
|
||||
垂直切片:策略可以接收明确的目标交易日和历史行情,按通达信公式计算 7 个
|
||||
子信号,并返回可解释、可重复、可区分的多分类信号结果。
|
||||
|
||||
本任务不创建信号数据库表、HTTP API、前端页面或全市场批次调度。信号模型先
|
||||
提供稳定身份和后续持久化所需的契约。
|
||||
|
||||
## 2. 上下文与依赖方向
|
||||
|
||||
新增目录:
|
||||
|
||||
```text
|
||||
zhixing-server/src/zhixing_server/modules/selection/
|
||||
├── domain/
|
||||
│ ├── models.py # 行情输入、策略信号、评估结果
|
||||
│ ├── ports.py # 市场历史读取端口
|
||||
│ ├── indicators.py # TDX 风格滚动/递推指标原语
|
||||
│ ├── zhixing_b1.py # 知行 B1 公式及 7 个子信号
|
||||
│ └── __init__.py
|
||||
├── application/
|
||||
│ ├── evaluate.py # 单股票、明确目标日的策略用例
|
||||
│ └── __init__.py
|
||||
├── infrastructure/
|
||||
│ ├── postgres_reader.py # 只读 PostgreSQL 市场数据适配器
|
||||
│ └── __init__.py
|
||||
└── presentation/
|
||||
└── __init__.py
|
||||
```
|
||||
|
||||
- `selection.domain` 不导入 FastAPI、Psycopg 或 PostgreSQL 适配器。
|
||||
- `selection.domain.ports` 定义策略需要的最小 `MarketDataReader`,不复用旧项目
|
||||
的 CSV repository,也不让策略直接拼 SQL。
|
||||
- `selection.infrastructure.postgres_reader` 只读 `market_stock`、
|
||||
`market_daily_bar` 和 `market_daily_basic`;市场数据写入仍归 `market_data`
|
||||
bounded context 所有。
|
||||
- `selection.application` 负责把目标代码、目标交易日交给 reader 和纯领域策略,
|
||||
将数据缺失、无信号和基础设施错误区分开。
|
||||
- 暂不把 `selection` 接入顶层 HTTP router 或 FastAPI 应用组合,避免首期引入
|
||||
未确定的产品 API 契约。
|
||||
|
||||
## 3. 领域模型
|
||||
|
||||
### 3.1 输入模型
|
||||
|
||||
定义面向策略的只读分析模型,不把 PostgreSQL 的 `Decimal`、Tushare 字段名或
|
||||
Pandas DataFrame 暴露给应用调用方:
|
||||
|
||||
- `SelectionBar`:`trade_date`、`open`、`high`、`low`、`close`、`volume`,价格
|
||||
和成交量在适配器边界转换为与旧项目一致的有限 `float`。
|
||||
- `SelectionDailyBasic`:`trade_date`、可选 `turnover_rate`、`total_mv` 等策略
|
||||
可能使用的同日指标。
|
||||
- `StockHistory`:股票代码、名称、升序 bars、按日期索引的 daily basic;只包含
|
||||
`trade_date <= target_trade_date` 的记录。
|
||||
|
||||
`MarketDataReader.load_history(ts_code, target_trade_date)` 必须保证:
|
||||
|
||||
1. bars 按交易日升序、去重,且只来自 `source_adj = 'qfq'`;
|
||||
2. 不把目标日之后的数据泄露给策略;
|
||||
3. 返回足够的历史 warm-up。首期直接返回数据库保留窗口内截至目标日的全部可用
|
||||
行情,避免人为截断导致 EMA/KDJ 与旧实现不一致;
|
||||
4. 目标日没有有效 bar 时返回可识别的缺失状态,而不是把更早日期伪装成目标日;
|
||||
5. 同日 `daily_basic` 缺失保留为可观察的缺失值,只有公式实际需要的字段才影响
|
||||
可选条件。
|
||||
|
||||
### 3.2 信号模型
|
||||
|
||||
定义 `ZhixingB1Category` 的 7 个稳定语义分类,外部值不再使用旧的
|
||||
`xg_composite` 前缀:
|
||||
|
||||
- `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`
|
||||
|
||||
`SelectionSignal` 至少包含:
|
||||
|
||||
- `ts_code`、`name`、`target_trade_date`;
|
||||
- `strategy = "zhixing_b1"`;
|
||||
- 一个 `ZhixingB1Category`;
|
||||
- qfq `close`;
|
||||
- 可序列化的关键详情,如 J、RSI、知行白线/黄线、振幅、成交量比和命中的
|
||||
公式标签。
|
||||
|
||||
稳定身份为 `(ts_code, target_trade_date, strategy, category)`。同一行数据可以
|
||||
产生多个 category,返回顺序固定为公式文件中 7 个子信号的优先级顺序。
|
||||
|
||||
### 3.3 评估结果
|
||||
|
||||
用例返回带状态的 `SelectionEvaluation`,至少区分:
|
||||
|
||||
- `selected`:至少一个子信号命中;
|
||||
- `no_signal`:目标日数据完整但没有子信号命中;
|
||||
- `insufficient_history`:少于公式要求的最小暖机长度;
|
||||
- `missing_target_bar`:目标交易日没有 qfq 日线;
|
||||
- `data_error`:市场数据适配器发生不可恢复的读取错误。
|
||||
|
||||
`no_signal`、`insufficient_history` 和 `missing_target_bar` 不是异常;数据库连接
|
||||
或 SQL 失败才转换为带上下文的基础设施错误。
|
||||
|
||||
## 4. 公式实现策略
|
||||
|
||||
### 4.1 计算层
|
||||
|
||||
为保持与旧实现及通达信公式的数值语义一致,首期使用直接依赖的 Pandas/NumPy
|
||||
实现向量化指标。当前 `tushare` 已将 Pandas/NumPy 带入锁文件,但新代码直接
|
||||
使用它们,因此实施阶段将把 `pandas` 和 `numpy` 声明为后端直接依赖并更新
|
||||
`uv.lock`。
|
||||
|
||||
在 `selection.domain.indicators` 内实现或迁移以下原语,并用纯输入测试锁定边界:
|
||||
|
||||
- `MA`、`EMA`、`LLV`、`HHV`、`SMA`、`REF`;
|
||||
- `EXIST`、`EVERY`、`COUNT`、`HHVBARS`、`BARSLAST`、`CROSS`;
|
||||
- TDX 风格 KDJ、RSI、知行白线/黄线;
|
||||
- 板块宽幅判定及振幅区间/放宽系数;
|
||||
- 大绿棒、缩量、异动、趋势、回踩和 BBI 派生条件。
|
||||
|
||||
旧项目的 `prepare_xg_indicators()` 可以作为迁移起点,但不得原样保留对旧项目
|
||||
`SignalCategory`、`zgnb` 包或旧 CSV 字段的导入。所有跨公式共享原语先归入
|
||||
`selection` 上下文,等第二个策略迁移时再根据真实复用情况决定是否上移到
|
||||
`shared`。
|
||||
|
||||
### 4.2 7 个子信号
|
||||
|
||||
将旧实现的 7 个 mask 逐一迁移为命名清晰的领域计算步骤,计算结果保留每个
|
||||
mask,而不是先 OR 成单一 `_存在B` 后只取第一项。最终组合逻辑为:
|
||||
|
||||
```text
|
||||
all_matches = [category for category in priority_order if category.mask(target_row)]
|
||||
```
|
||||
|
||||
每个 mask 必须与通达信公式逐段对照;旧实现已经存在的 v1203 调整(例如上涨
|
||||
十字星的涨幅限制、原始 B1 的放宽缩量分支)作为有意语义保留,并在测试名或
|
||||
fixture 说明中标明。
|
||||
|
||||
### 4.3 缺失值和暖机
|
||||
|
||||
- 公式所需 rolling 窗口不足时遵循旧实现的窗口语义,不用当前行的未来数据补齐。
|
||||
- 目标日需要的 OHLCV 缺失时不生成信号。
|
||||
- 中间指标出现 NaN 时,比较型条件默认不命中;除零场景显式转为 NaN/False,
|
||||
不让异常被静默吞掉。
|
||||
- 不使用 `except Exception` 将单个子信号错误转成全局无信号;公式实现错误应
|
||||
让测试或应用调用失败可见。
|
||||
|
||||
## 5. PostgreSQL 只读适配器
|
||||
|
||||
`PostgresMarketDataReader` 使用现有 `Settings.database_url`,通过参数化 SQL
|
||||
读取:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
bar.ts_code, bar.trade_date, bar.open, bar.high, bar.low, bar.close,
|
||||
bar.vol, basic.turnover_rate, basic.total_mv
|
||||
FROM market_daily_bar AS bar
|
||||
LEFT JOIN market_daily_basic AS basic
|
||||
ON basic.ts_code = bar.ts_code
|
||||
AND basic.trade_date = bar.trade_date
|
||||
WHERE bar.ts_code = %s
|
||||
AND bar.source_adj = 'qfq'
|
||||
AND bar.trade_date <= %s
|
||||
ORDER BY bar.trade_date
|
||||
```
|
||||
|
||||
适配器只负责查询、字段映射、排序和缺失状态;不写数据库、不回退到 CSV、不
|
||||
调用 Tushare。查询整个六年保留窗口是首期的正确性优先选择,后续全市场运行
|
||||
若证明有性能压力再引入可配置 warm-up 窗口和批量读取。
|
||||
|
||||
## 6. 验证策略
|
||||
|
||||
### 6.1 公式单元测试
|
||||
|
||||
为每个原语和 7 个 mask 提供边界案例,至少包含:
|
||||
|
||||
- rolling 窗口刚好不足、刚好满足和超过;
|
||||
- `high == low`、前收为零、成交量为零、NaN/None;
|
||||
- 30/68/普通代码的幅度参数;
|
||||
- 大绿棒在 15 日前/后、当前最大量切换;
|
||||
- 上涨十字星涨幅小于 4% 与超过 4%;
|
||||
- 同一行同时满足多个 mask。
|
||||
|
||||
### 6.2 历史 golden
|
||||
|
||||
从旧项目现有 `data/raw` 中选择少量公开行情样本,抽取为新项目测试 fixture,
|
||||
不让测试运行时依赖旧项目目录。固定 fixture 包含:
|
||||
|
||||
- 升序 qfq OHLCV CSV;
|
||||
- 目标交易日和股票代码;
|
||||
- 旧实现/人工确认的命中分类集合;
|
||||
- 关键详情允许小数误差的期望值。
|
||||
|
||||
golden 只验证固定样本,不把旧实现当成新实现的运行时依赖。对于旧实现当前
|
||||
只保留第一个子信号的行为,golden 记录“新实现返回全部命中分类”的有意差异。
|
||||
|
||||
### 6.3 端口和适配器测试
|
||||
|
||||
- Fake reader 测试应用用例只传入目标日前数据,并区分无信号、目标日缺失和
|
||||
基础设施错误。
|
||||
- PostgreSQL reader 使用 mock/fake connection 验证参数化查询、qfq 过滤、升序
|
||||
映射和基本指标左连接;不访问真实网络。
|
||||
- 如使用 PostgreSQL 集成测试,沿用 `ZHIXING_TEST_DATABASE_URL` marker,且
|
||||
不把它作为普通单测必需条件。
|
||||
|
||||
## 7. 兼容性、回滚与后续演进
|
||||
|
||||
- 不修改旧项目目录和旧 SQLite 数据;迁移结果通过 `zhixing_b1` 新身份区分。
|
||||
- 首期不创建数据库迁移,因此回滚只需移除新 selection 模块和直接依赖,不影响
|
||||
已有 market-data 表及同步任务。
|
||||
- 后续增加策略时,优先复用真实验证后确认的 `selection.domain.indicators`;
|
||||
不预先建立跨上下文的全局指标工具箱。
|
||||
- 后续实现信号持久化时,可直接使用稳定身份 `(ts_code, date, strategy,
|
||||
category)` 建立唯一键;本任务不提前锁定表结构或 HTTP 字段。
|
||||
@@ -0,0 +1,10 @@
|
||||
{"file":".trellis/spec/backend/index.md","reason":"实现 selection bounded context 前确认后端分层、开发前检查与质量入口。"}
|
||||
{"file":".trellis/spec/backend/directory-structure.md","reason":"按 domain/application/infrastructure/presentation 边界创建选股上下文,并保持导入方向。"}
|
||||
{"file":".trellis/spec/backend/market-data-sync.md","reason":"读取 market_daily_bar 和 market_daily_basic 时保留 qfq、六年窗口、目标日新鲜度和数据事实源契约。"}
|
||||
{"file":".trellis/spec/backend/error-handling.md","reason":"区分无信号、目标数据缺失和数据库读取失败,避免把异常静默成空结果。"}
|
||||
{"file":".trellis/spec/backend/quality-guidelines.md","reason":"遵循 Python 3.12、严格类型、Ruff、Pyright 和 pytest 质量要求。"}
|
||||
{"file":"docs/adr/0001-bounded-context-first-modular-monolith.md","reason":"确认选股能力应作为明确 bounded context 演进,而不是创建全局 service 或 utils。"}
|
||||
{"file":"docs/adr/0003-postgresql-as-market-data-store.md","reason":"确认 PostgreSQL 是策略事实源,价格使用 qfq,当前股票池和历史分析语义不被策略迁移改变。"}
|
||||
{"file":"docs/adr/0004-tushare-six-year-snapshot-sync.md","reason":"确认策略只消费有效目标日数据、六年窗口和覆盖率资格,不改同步契约。"}
|
||||
{"file":"CONTEXT.md","reason":"使用现有市场数据和分析语义,补充 zhixing_b1、子信号与公式语义的项目术语。"}
|
||||
{"file":".trellis/tasks/08-08-migrate-zhixing-b1/research/legacy-zhixing-b1.md","reason":"实现时按已核对的旧公式、v1203 差异和有意行为变化迁移,不把旧项目运行时导入新项目。"}
|
||||
@@ -0,0 +1,153 @@
|
||||
# 知行 B1 选股策略实施计划
|
||||
|
||||
## 实施原则
|
||||
|
||||
- 只修改 `zhixing-system`;旧项目只读,不回写、不重命名、不提交旧项目数据。
|
||||
- 先写行为测试,再补最小实现;每一步保持 `uv run pytest` 可定位失败范围。
|
||||
- `zhixing_b1` 是新策略身份;不要把 `xg_composite` 作为新模块的对外名称。
|
||||
- 公式语义优先于旧 Python 的偶然行为;每个有意差异都要在 fixture 或测试名中
|
||||
留下证据。
|
||||
- 不启动 HTTP、前端、信号持久化或全市场批次编排;它们属于后续任务。
|
||||
|
||||
## 1. 依赖与模块骨架
|
||||
|
||||
- 在 `zhixing-server/pyproject.toml` 声明直接依赖 `pandas` 和 `numpy`,执行
|
||||
`uv lock`,确认锁文件与 Python 3.12 环境一致。
|
||||
- 创建 `modules/selection/{domain,application,infrastructure,presentation}`
|
||||
包和上下文 README,保持 domain 不导入 FastAPI/Psycopg。
|
||||
- 先新增领域模型、端口和评估状态类型,再接入基础设施。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv lock --check
|
||||
uv run python -c "import numpy, pandas; print(numpy.__version__, pandas.__version__)"
|
||||
```
|
||||
|
||||
回滚点:依赖或包骨架若无法通过 Ruff/Pyright,先撤销骨架,不触碰
|
||||
`modules/market_data`。
|
||||
|
||||
## 2. 迁移公式原语
|
||||
|
||||
- 从旧项目 `shared/indicators.py`、`kdj.py`、`rsi.py`、`zhixing.py` 和
|
||||
`market_type.py` 提取必要实现到 `selection/domain/indicators.py`。
|
||||
- 先覆盖 `MA/EMA/LLV/HHV/SMA/REF/EXIST/EVERY/COUNT/HHVBARS/BARSLAST/CROSS`,
|
||||
再实现 KDJ、RSI、知行线和板块幅度参数。
|
||||
- 每个公共函数增加完整类型、参数/返回值/边界说明;NaN、除零和不足窗口行为
|
||||
用测试锁定。
|
||||
- 不引入 Numba、SciPy 或其他旧项目专用依赖;首期 Pandas/NumPy 足够保持公式
|
||||
计算的向量化和数值接近。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv run pytest tests/unit/selection/test_indicators.py
|
||||
```
|
||||
|
||||
## 3. 实现 `ZhixingB1Strategy`
|
||||
|
||||
- 将旧 `prepare_xg_indicators()` 拆成可读的领域计算步骤:基础线、振幅、KDJ/RSI、
|
||||
缩量、大绿棒、异动、趋势、距离/回踩、7 个子信号。
|
||||
- 策略类固定 `name = "zhixing_b1"`,公开入口显式接收 `StockHistory` 和
|
||||
`target_trade_date`。
|
||||
- 对目标交易日定位使用日期索引,不使用 DataFrame 最后一行猜测目标日期。
|
||||
- 保留 7 个 mask 的全部命中,按公式顺序产生多个 `SelectionSignal`,不可使用
|
||||
旧代码中的 `break`。
|
||||
- 详情只写可序列化、与目标行相关的关键指标;不要保存整张 DataFrame。
|
||||
- 对目标日缺失、历史不足、无信号分别返回评估状态;公式计算异常向上暴露,
|
||||
不静默转换为空结果。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv run pytest tests/unit/selection/test_zhixing_b1.py
|
||||
```
|
||||
|
||||
回滚点:若结果数量明显偏离 golden,保留公式原语测试和差异报告,回滚策略
|
||||
编排层,不回退到旧 `xg_composite` 命名。
|
||||
|
||||
## 4. 建立市场数据读取端口与 PostgreSQL 适配器
|
||||
|
||||
- 在 `selection/domain/ports.py` 定义只读 `MarketDataReader`。
|
||||
- 在 `selection/infrastructure/postgres_reader.py` 实现参数化查询,读取 qfq
|
||||
`market_daily_bar`,左连接同日 `market_daily_basic`,按日期升序映射为
|
||||
`StockHistory`。
|
||||
- 从 `Settings` 注入连接串;不直接读取环境变量,不调用 Tushare,不回退 CSV。
|
||||
- 使用数据库事实表的 `source_adj = 'qfq'` 过滤,拒绝目标日之后的行。
|
||||
- 连接失败转换为带股票和目标日上下文的基础设施错误;目标日无 bar 属于可识别
|
||||
的业务状态。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv run pytest tests/unit/selection/test_postgres_reader.py
|
||||
```
|
||||
|
||||
若增加 PostgreSQL 集成覆盖:
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv run pytest -m integration tests/integration/test_selection_reader.py
|
||||
```
|
||||
|
||||
回滚点:只读适配器失败时删除 selection 适配器即可;不得修改已有市场数据表、
|
||||
同步事务或 Alembic migration。
|
||||
|
||||
## 5. 应用用例与 Fake reader
|
||||
|
||||
- 实现 `EvaluateZhixingB1`,输入 `ts_code`、`target_trade_date` 和 reader,输出
|
||||
`SelectionEvaluation`。
|
||||
- 用例只负责读取、调用领域策略和映射错误;不负责全市场循环、保存信号或 HTTP
|
||||
响应。
|
||||
- 提供 Fake reader 测试目标日期截断、缺失目标行、历史不足、无信号、选中多分类
|
||||
和基础设施错误。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv run pytest tests/unit/selection/test_evaluate.py
|
||||
```
|
||||
|
||||
## 6. 固定 fixture 与 golden 对比
|
||||
|
||||
- 从旧项目现有 `data/raw` 中选取少量股票和目标日期,抽取最小 OHLCV CSV,放入
|
||||
`zhixing-server/tests/fixtures/selection/zhixing_b1/`。
|
||||
- 将旧实现或人工确认结果固化为 JSON,包含目标日期、命中分类集合和关键详情的
|
||||
容差范围;测试运行时不导入旧项目。
|
||||
- 至少包含普通代码和宽幅代码,并加入一个人工构造的同日多信号样本。
|
||||
- 明确记录旧实现“只保留第一个分类”与新实现“保留全部分类”的差异。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv run pytest tests/integration/test_zhixing_b1_golden.py
|
||||
```
|
||||
|
||||
## 7. 完整质量检查与规划复核
|
||||
|
||||
实现结束后运行:
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv run ruff format --check .
|
||||
uv run ruff check .
|
||||
uv run pyright
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
并检查:
|
||||
|
||||
- `rg -n "xg_composite|zgnb\." src/zhixing_server/modules/selection tests` 只在
|
||||
迁移说明或兼容性测试中出现,不成为新领域运行时依赖;
|
||||
- 旧项目工作区没有被修改;
|
||||
- 没有新增 HTTP 路由、前端文件、信号表 migration 或调度入口;
|
||||
- golden、单元测试和端口测试都能在无网络、无生产数据库条件下运行。
|
||||
|
||||
完成 planning 后,先向用户展示 `prd.md`、`design.md` 和本文件摘要;只有用户
|
||||
明确批准最新 planning summary,才能执行 `task.py start` 并进入实现阶段。
|
||||
@@ -0,0 +1,89 @@
|
||||
# 迁移知行B1选股策略
|
||||
|
||||
## Goal
|
||||
|
||||
在新项目中迁移旧项目的 `xg_composite` 选股逻辑,并将新策略名称统一为
|
||||
`zhixing_b1`,让系统能够基于 PostgreSQL 中的历史市场数据执行可复现的知行
|
||||
B1 选股。
|
||||
|
||||
首期采用“公式级垂直切片”:从通达信公式、指标计算、7 个子信号、信号结果、
|
||||
市场数据读取到历史验证用例打通一条完整链路;不在本任务内铺开其他策略或完整
|
||||
前端产品能力。
|
||||
|
||||
## Background and confirmed facts
|
||||
|
||||
- 旧项目的 `xg_composite` 对应通达信选股公式,包含 7 个子信号;实现位于
|
||||
`zgnb-project/src/zgnb/domain/strategy/xg_composite.py`,公式原文位于
|
||||
`zgnb-project/docs/references/formulas/tongdaxin_xuangu_formula.txt`。
|
||||
- 旧项目的共享指标实现位于 `zgnb-project/src/zgnb/shared/xg_indicators.py`,
|
||||
其中包含知行线、BBI、KDJ、RSI、振幅、趋势、回踩和 7 个子信号条件。
|
||||
- 旧项目目前只返回第一个命中的 XG 子信号;本任务以公式/业务意图为准,允许
|
||||
同一股票同一交易日产生多条不同分类的信号。
|
||||
- 新项目已确定 PostgreSQL 为市场数据事实源、价格使用 qfq 日线、股票池为当前
|
||||
沪深非 ST A 股,并保留 6 年数据窗口。
|
||||
- 新项目当前已有 `market_daily_bar`、`market_daily_basic` 和市场数据领域端口,
|
||||
但还没有策略读取端口、选股信号模型或策略 API。
|
||||
|
||||
## Requirements
|
||||
|
||||
### R1. 迁移策略身份
|
||||
|
||||
- 新策略的业务标识为 `zhixing_b1`。
|
||||
- 领域逻辑不得继续依赖旧项目的 `xg_composite` 命名作为对外策略身份。
|
||||
- 7 个子信号保留独立分类,并可在同一股票同一交易日同时出现。
|
||||
|
||||
### R2. 公式语义
|
||||
|
||||
- 以通达信公式和已确认的 v1203 业务调整为主要依据,旧 Python 实现作为迁移
|
||||
参考和差异线索。
|
||||
- 不复制旧流程中将最新市值写入全部历史 K 线、按当前日期查询历史 B1 信号等
|
||||
不能支持历史重放的行为。
|
||||
- 对公式中涉及的代码板块、涨跌幅放宽系数、振幅区间、缩量、大绿棒、趋势、
|
||||
回踩和 7 个子信号条件建立可测试的实现。
|
||||
|
||||
### R3. 市场数据读取
|
||||
|
||||
- 策略使用市场数据领域端口读取目标交易日之前的足够 warm-up 日线数据。
|
||||
- OHLCV 指标使用 `market_daily_bar` 的 qfq 价格与成交量;换手率等估值/交易
|
||||
条件使用同一交易日的 `market_daily_basic` 快照。
|
||||
- 策略计算不得直接依赖 PostgreSQL、Pandas SQL 查询或具体 HTTP 层实现。
|
||||
- 历史选股必须显式使用 `target_trade_date`,不得隐式退化为“当前最后一行”。
|
||||
|
||||
### R4. 信号结果
|
||||
|
||||
- 信号至少包含股票、交易日、策略标识、子信号分类、收盘价和可解释的关键指标
|
||||
详情。
|
||||
- 信号提供由“股票、交易日、策略标识、子信号分类”组成的稳定身份;不同子信号
|
||||
分类不得被合并丢失,为后续持久化提供幂等依据。
|
||||
|
||||
### R5. 验证
|
||||
|
||||
- 为共享指标和每个子信号建立边界条件测试,覆盖缺数据、暖机期、除零和板块
|
||||
参数差异。
|
||||
- 提供固定历史样本的策略级验证,能够判断新实现是否符合公式语义,并明确记录
|
||||
与旧实现的有意差异。
|
||||
- 验证不得访问真实 Tushare、生产数据库或依赖实时网络。
|
||||
|
||||
## Out of scope
|
||||
|
||||
- `bowl_rebound`、`b1`、`b1b2`、`brick_chart` 的迁移。
|
||||
- 完整选股批次编排、自动调度、前端页面和图表生成。
|
||||
- 选股信号 PostgreSQL 表、信号持久化实现和 HTTP API。
|
||||
- 实盘交易、回测收益评价和策略参数优化。
|
||||
- 为解决本任务而改变既有市场数据同步的股票池、qfq 或六年保留契约。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 新项目存在名为 `zhixing_b1` 的领域策略,并能在显式目标交易日上运行。
|
||||
- [ ] 7 个子信号均有独立分类;同一股票同日多信号不会互相覆盖,并具有稳定身份。
|
||||
- [ ] 策略只通过市场数据端口获得 qfq 日线和同日交易指标,不直接耦合存储实现。
|
||||
- [ ] 公式关键分支、暖机边界、缺失值和除零场景均有自动化测试。
|
||||
- [ ] 固定历史样本验证可重复运行,并以固定 golden 结果对比新旧实现或公式差异。
|
||||
- [ ] 关键公式分支同时有人工确认案例和单元测试,golden 对比范围保持为少量固定样本。
|
||||
- [ ] 未实现其他策略、前端页面或实时数据能力,且不改变现有市场数据同步契约。
|
||||
|
||||
## Notes
|
||||
|
||||
- Keep `prd.md` focused on requirements, constraints, and acceptance criteria.
|
||||
- Lightweight tasks can remain PRD-only.
|
||||
- For complex tasks, add `design.md` for technical design and `implement.md` for execution planning before `task.py start`.
|
||||
@@ -0,0 +1,50 @@
|
||||
# 旧项目知行 B1 逻辑研究
|
||||
|
||||
## 来源
|
||||
|
||||
- 公式原文:`/Users/yuxuanhui/bcc-github/quant-project/zgnb/zgnb-project/docs/references/formulas/tongdaxin_xuangu_formula.txt`
|
||||
- 旧策略入口:`/Users/yuxuanhui/bcc-github/quant-project/zgnb/zgnb-project/src/zgnb/domain/strategy/xg_composite.py`
|
||||
- 旧指标实现:`/Users/yuxuanhui/bcc-github/quant-project/zgnb/zgnb-project/src/zgnb/shared/xg_indicators.py`
|
||||
- 旧数据字段转换:`/Users/yuxuanhui/bcc-github/quant-project/zgnb/zgnb-project/src/zgnb/infrastructure/data_source/tushare_adapter.py`
|
||||
|
||||
## 已确认逻辑
|
||||
|
||||
`xg_composite` 先计算趋势白线、大哥黄线、BBI、短期/长期振荡器、KDJ、RSI、
|
||||
振幅区间、缩量、大绿棒、异动、趋势和回踩条件,再执行 7 个子信号 mask:
|
||||
|
||||
1. 超卖缩量拐头 B
|
||||
2. 超卖缩量 B
|
||||
3. 原始 B1
|
||||
4. 超卖超缩量 B
|
||||
5. 回踩白线 B
|
||||
6. 回踩超级 B
|
||||
7. 回踩黄线 B
|
||||
|
||||
旧实现将 7 个 mask OR 为 `_存在B`,在 `select()` 中按优先级找到第一个匹配后
|
||||
`break`。新任务明确改为返回全部命中分类。
|
||||
|
||||
## 迁移时必须保留的语义
|
||||
|
||||
- 数据升序;`REF` 使用前一交易日,不能用自然日偏移。
|
||||
- `趋势白线 = EMA(EMA(C, 10), 10)`。
|
||||
- `大哥黄线 = (MA(C,14)+MA(C,28)+MA(C,57)+MA(C,114))/4`。
|
||||
- `短期` 使用 3 日最低价和 3 日最高收盘价,`长期` 使用 21 日窗口。
|
||||
- 宽幅代码为 `68`、`30`、`4`、`8`、`9` 开头;普通代码如果最近 200 行内出现
|
||||
超过 15% 的上涨,也使用宽幅参数。
|
||||
- v1203 调整包含上涨十字星涨幅上限和原始 B1 的适当缩量分支。
|
||||
|
||||
## 旧实现与新任务的有意差异
|
||||
|
||||
- 新策略标识为 `zhixing_b1`,不再使用 `xg_composite` 作为运行时名称。
|
||||
- 新分类值使用 `zhixing_b1_*` 前缀,不依赖旧 `SignalCategory.XG_*`。
|
||||
- 新实现保留同一股票同一交易日的全部命中分类。
|
||||
- 新实现目标日由显式 `target_trade_date` 决定,不取数据最后一行作为隐式目标。
|
||||
- 新实现只使用新项目 PostgreSQL 的 qfq 日线;旧项目 CSV 中的 `market_cap` 是
|
||||
初始化时取到的最新市值复制值,不能作为历史事实。
|
||||
- 新测试运行时不导入旧项目;旧项目只用于生成固定 fixture 和 golden 期望。
|
||||
|
||||
## 现有 fixture 线索
|
||||
|
||||
旧项目已有完整 CSV 和 SQLite 信号结果,可从 `data/raw` 选取少量普通代码、宽幅
|
||||
代码和已命中日期作为固定样本。由于旧实现没有记录同日多分类命中,需另加人工
|
||||
构造样本验证新任务要求的多信号结果。
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "migrate-zhixing-b1",
|
||||
"name": "migrate-zhixing-b1",
|
||||
"title": "迁移知行B1选股策略",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "yuxuanhui",
|
||||
"assignee": "yuxuanhui",
|
||||
"createdAt": "2026-08-08",
|
||||
"completedAt": "2026-08-09",
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{"file":".trellis/spec/backend/http-api-contracts.md","reason":"检查 HTTP 方法、状态码、Pydantic 响应和 /api/v1 路由组合是否符合项目契约。"}
|
||||
{"file":".trellis/spec/backend/error-handling.md","reason":"检查执行中冲突、重跑确认、失败状态和查询错误是否可观察且未吞异常。"}
|
||||
{"file":".trellis/spec/backend/selection.md","reason":"检查目标交易日、qfq 输入、独立子信号和评估状态在批次持久化中没有被破坏。"}
|
||||
{"file":".trellis/spec/backend/quality-guidelines.md","reason":"执行后端格式、lint、Pyright、pytest 和黑盒 HTTP 质量检查。"}
|
||||
{"file":".trellis/spec/frontend/hook-guidelines.md","reason":"检查 Query key、轮询、mutation 和 AbortSignal 的实现方式。"}
|
||||
{"file":".trellis/spec/frontend/state-management.md","reason":"检查服务器状态没有错误复制到全局 store,运行中状态使用局部/query 状态。"}
|
||||
{"file":".trellis/spec/frontend/type-safety.md","reason":"检查 API 类型和页面状态分支没有使用 any、无解释断言或重复响应形状。"}
|
||||
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"执行前端格式、lint、类型、测试和构建门禁。"}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"检查迁移、领域模型、HTTP JSON、前端类型与用户状态的端到端契约。"}
|
||||
{"file":"docs/adr/0004-tushare-six-year-snapshot-sync.md","reason":"检查策略执行是否只使用合格市场同步批次并携带覆盖率和失败信息。"}
|
||||
@@ -0,0 +1,187 @@
|
||||
# 策略执行结果持久化、HTTP 触发与查询设计
|
||||
|
||||
## 1. 设计目标
|
||||
|
||||
在现有 `selection` bounded context 上补齐一条可重跑的每日策略结果链路:用户在
|
||||
Web 页面选择目标交易日并触发 `zhixing_b1`,HTTP 快速返回执行批次标识,服务端在
|
||||
当前进程的异步批次中完成全股票池评估并持久化结果,页面轮询状态后展示信号明细。
|
||||
|
||||
一次重跑必须在同一数据库事务中清除指定“策略 + 目标交易日”的旧结果并创建新的
|
||||
运行记录,避免旧信号和新信号混在一起。执行中的重复请求只返回冲突,不得并发清理
|
||||
或重复计算同一批次。
|
||||
|
||||
本期不引入独立任务队列、定时器或其他策略;HTTP 是触发入口,FastAPI 进程内的
|
||||
`BackgroundTasks` 是异步执行机制。
|
||||
|
||||
## 2. 上下文边界与模块分工
|
||||
|
||||
```text
|
||||
zhixing-server/src/zhixing_server/modules/selection/
|
||||
├── domain/
|
||||
│ ├── models.py # 已有行情、信号和单股评估模型
|
||||
│ ├── ports.py # 市场数据读取端口
|
||||
│ └── runs.py # 批次状态、持久化读写端口和执行结果模型
|
||||
├── application/
|
||||
│ ├── evaluate.py # 已有单股评估用例
|
||||
│ └── run.py # 全股票池批次编排、重跑和失败收敛
|
||||
├── infrastructure/
|
||||
│ ├── postgres_reader.py # 已有单股 qfq 历史读取,补充执行股票池读取
|
||||
│ └── postgres_runs.py # selection 批次、item、signal 的 PostgreSQL 适配器
|
||||
└── presentation/
|
||||
└── http.py # Pydantic 请求/响应和 HTTP 依赖
|
||||
```
|
||||
|
||||
- `selection.domain` 不依赖 FastAPI、Psycopg 或 PostgreSQL JSON 类型。
|
||||
- `selection.application` 只依赖端口;批次执行负责逐股调用已有
|
||||
`EvaluateZhixingB1`,不复制公式逻辑。
|
||||
- `selection.infrastructure` 负责事务、锁、SQL、JSONB 序列化和市场同步批次关联。
|
||||
- `selection.presentation.http` 只做边界校验、HTTP 状态映射和领域模型转换;由
|
||||
`interfaces/http/router.py` 在 `/api/v1/selection` 下挂载。
|
||||
- 前端新增 `features/selection` 垂直切片;页面可以依赖 `HomeShell` 和 shared UI,
|
||||
`shared` 不反向依赖 selection。
|
||||
|
||||
## 3. 持久化模型与重跑事务
|
||||
|
||||
新增 Alembic migration `0002_selection_results`,同时更新
|
||||
`modules/market_data/infrastructure/schema.py` 的 metadata。使用三张表:
|
||||
|
||||
### 3.1 `selection_run`
|
||||
|
||||
一行代表某个策略、目标交易日的一次当前执行尝试。
|
||||
|
||||
- `id`:UUID 字符串主键,作为异步轮询的 `run_id`。
|
||||
- `strategy`、`target_trade_date`:业务身份;建立唯一约束,保证同一时点只有一条
|
||||
当前尝试。
|
||||
- `market_sync_batch_id`:引用产生输入数据的市场同步批次标识;跨上下文先保存
|
||||
稳定 ID,不改变市场数据 bounded context 的写入所有权。
|
||||
- `status`:`running`、`success`、`partial_success`、`failed`。
|
||||
- `target_count`、`eligible_count`、`evaluated_count`、`selected_stock_count`、
|
||||
`signal_count`、`failed_count`:批次汇总计数。
|
||||
- `coverage`:从市场同步批次复制的覆盖率,使用 Numeric 保存精度。
|
||||
- `error_type`、`error_message`:批次级失败上下文,可空且不保存 traceback 或凭据。
|
||||
- `created_at`、`finished_at`:审计时间。
|
||||
|
||||
`selection_run_item` 以 `(run_id, ts_code)` 为主键,保存每只参与股票的名称、
|
||||
评估状态、信号数量和可读原因。状态沿用领域评估状态:`selected`、`no_signal`、
|
||||
`insufficient_history`、`missing_target_bar`、`data_error`。
|
||||
|
||||
`selection_signal` 以 `(run_id, ts_code, category)` 为主键,保存股票、目标日、
|
||||
策略、子信号分类、qfq 收盘价和 JSONB `details`。同一股票同日的多个 category
|
||||
分别落行,查询时按代码和公式优先级稳定排序。
|
||||
|
||||
### 3.2 首次执行、重跑和并发
|
||||
|
||||
`prepare_run(strategy, target_trade_date, rerun)` 在一个短事务中完成:
|
||||
|
||||
1. 使用按策略和日期派生的 PostgreSQL advisory transaction lock,串行化同一业务键。
|
||||
2. 查询当前 `selection_run`。
|
||||
3. `running` 时拒绝请求,返回 `409 run_in_progress`。
|
||||
4. 已有终态且 `rerun=false` 时返回 `409 rerun_confirmation_required`;页面只有在
|
||||
用户确认弹窗后才发送 `rerun=true`。
|
||||
5. `rerun=true` 时删除旧 run(子表使用 `ON DELETE CASCADE`),再插入新的 `running`
|
||||
run;删除与创建同事务提交。
|
||||
6. 没有旧 run 时直接插入新的 `running` run。
|
||||
|
||||
事务提交后才注册 `BackgroundTasks`。后台执行异常会把 run 收敛为 `failed`;单只
|
||||
股票异常记录到 `selection_run_item`,其余股票继续执行,最后根据失败数量和命中
|
||||
结果写入 `success`、`partial_success` 或 `failed`。
|
||||
|
||||
进程在批次运行中崩溃会留下 `running` 状态;本期将其作为可见的执行中状态,并在
|
||||
后续恢复机制中再增加超时接管。该限制必须在运维风险中保留,不能伪装成成功结果。
|
||||
|
||||
## 4. 执行数据流
|
||||
|
||||
1. HTTP 收到策略、目标交易日和 `rerun`,边界只允许当前支持的 `zhixing_b1`。
|
||||
2. application 通过 selection 端口读取目标日最新的 `market_sync_batch`,只允许
|
||||
`strategy_eligible=true` 的同步批次作为输入;没有可用批次则在任何清理/创建 run
|
||||
事务之前返回 `422 market_data_not_ready`,不使用当前最新日期猜测目标日,也不破坏
|
||||
已有的成功结果。
|
||||
3. 读取当前 `market_stock.is_active=true` 且目标日同时拥有 bar/basic 的有效股票
|
||||
集合;`target_count` 和 `coverage` 来自同步批次,`eligible_count` 来自实际输入。
|
||||
4. 对每只股票调用已有 `PostgresMarketDataReader.load_history` 和
|
||||
`EvaluateZhixingB1.execute`,将 item 状态和全部独立 signals 写入当前 run。
|
||||
5. 完成后一次更新 run 汇总和 `finished_at`;查询端只读取已提交的持久化状态。
|
||||
|
||||
全股票池执行先使用现有“逐股票读取”的正确性优先方案,不在本任务引入并行化或
|
||||
缓存;若性能不足,后续再以批量历史读取为单独设计。
|
||||
|
||||
## 5. HTTP 契约
|
||||
|
||||
### 5.1 触发
|
||||
|
||||
`POST /api/v1/selection/runs`
|
||||
|
||||
请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"strategy": "zhixing_b1",
|
||||
"target_trade_date": "2026-08-08",
|
||||
"rerun": false
|
||||
}
|
||||
```
|
||||
|
||||
成功返回 `202`:
|
||||
|
||||
```json
|
||||
{
|
||||
"run_id": "<uuid>",
|
||||
"strategy": "zhixing_b1",
|
||||
"target_trade_date": "2026-08-08",
|
||||
"status": "running"
|
||||
}
|
||||
```
|
||||
|
||||
错误状态至少包括:
|
||||
|
||||
- `409 run_in_progress`:同一策略和目标日已有运行中的批次;
|
||||
- `409 rerun_confirmation_required`:已有终态结果但请求没有 `rerun=true`;
|
||||
- `422`:策略、日期或市场数据资格不满足请求契约;
|
||||
- `503`:无法创建批次或数据库不可用。
|
||||
|
||||
### 5.2 轮询与结果查询
|
||||
|
||||
- `GET /api/v1/selection/runs/{run_id}`:按 run ID 返回批次状态;运行中返回汇总,
|
||||
终态追加 item 失败列表和 signals。
|
||||
- `GET /api/v1/selection/results?strategy=zhixing_b1&target_trade_date=...`:按业务
|
||||
键查询当前结果。目标日省略时取该策略最近一条当前 run;没有结果返回 `200` 的
|
||||
`status=no_data`,不把“没有执行”伪装成 HTTP 异常。
|
||||
|
||||
稳定响应包含策略、目标日、run ID、状态、市场同步批次、计数、coverage、错误/失败
|
||||
列表和 signal 明细。日期使用 ISO `date`,时间使用带时区的 ISO `datetime`。字段不
|
||||
直接暴露数据库列名以外的内部异常信息。
|
||||
|
||||
## 6. 前端交互
|
||||
|
||||
- 新增 `/selection` 路由,启用 `HomeShell` 的“选股策略”导航。
|
||||
- 页面提供目标交易日选择,默认查询最近持久化结果;策略下拉首期只显示“知行 B1”。
|
||||
- 首次无结果时显示“执行策略”;已有成功、部分成功或失败结果时显示“重新执行/
|
||||
重试执行”,点击先打开确认 Dialog,取消不调用 POST,确认才发送 `rerun=true`。
|
||||
- POST 成功后保存 `run_id` 到组件局部状态,使用 React Query 轮询 run;运行中展示
|
||||
状态和刷新提示,终态失效业务键查询并显示结果。
|
||||
- 页面明确区分加载中、查询错误、无数据、执行中、执行失败、无命中、部分成功和
|
||||
成功;signals 以每个 category 一行或可辨认的标签展示,同一股票的多分类不能合并
|
||||
成一条无分类记录。
|
||||
- API 类型、query key、mutation 和轮询逻辑全部位于 `features/selection/api/`,
|
||||
页面不直接调用 `fetch`,不把服务器结果复制到 Zustand。
|
||||
|
||||
## 7. 兼容性与回滚
|
||||
|
||||
- 不修改 `market_stock`、行情事实表或现有同步批次的语义;只读取其
|
||||
`strategy_eligible`、coverage 和目标日输入。
|
||||
- migration downgrade 按 signals → items → runs 删除新表;删除 selection 结果
|
||||
不影响市场数据。
|
||||
- 如果异步机制或全市场性能不满足,保留已提交的迁移和领域契约,后续替换执行器;
|
||||
不回退到即时查询或删除持久化结果。
|
||||
- 进程崩溃遗留 `running` 和当前实现逐股票读取是已知风险,作为后续任务候选记录。
|
||||
|
||||
## 8. 验证策略
|
||||
|
||||
- domain/application:Fake reader/repository 覆盖首次执行、重跑先清空、运行中冲突、
|
||||
全部状态聚合、多分类落盘和单股失败继续执行。
|
||||
- infrastructure:fake psycopg connection 覆盖参数化查询、事务顺序、级联清理、
|
||||
JSONB details、市场同步资格和稳定排序。
|
||||
- HTTP:`TestClient(create_app())` 覆盖 `202`、`409`、`422`、无数据查询、轮询和
|
||||
终态响应;后台执行依赖通过 FastAPI override 或 fake service 注入。
|
||||
- frontend:页面测试覆盖初次执行、确认弹窗取消/确认、轮询状态、失败重试、无命中、
|
||||
多分类和查询错误;运行格式、lint、类型、Vitest 和生产构建。
|
||||
@@ -0,0 +1,17 @@
|
||||
{"file":".trellis/spec/backend/index.md","reason":"确认后端 bounded context、HTTP 入口和开发前检查。"}
|
||||
{"file":".trellis/spec/backend/directory-structure.md","reason":"按 selection 的 domain/application/infrastructure/presentation 边界组织持久化、执行和 HTTP 代码。"}
|
||||
{"file":".trellis/spec/backend/http-api-contracts.md","reason":"实现 /api/v1 路由目录、Pydantic 响应模型、依赖注入和 TestClient 契约。"}
|
||||
{"file":".trellis/spec/backend/error-handling.md","reason":"为重跑冲突、执行失败、查询失败和 FastAPI 边界建立稳定错误映射。"}
|
||||
{"file":".trellis/spec/backend/selection.md","reason":"复用 zhixing_b1 的目标日、qfq、独立子信号和评估状态契约。"}
|
||||
{"file":".trellis/spec/backend/quality-guidelines.md","reason":"遵守 Ruff、Pyright、pytest、公开类型和 HTTP 测试门禁。"}
|
||||
{"file":".trellis/spec/frontend/index.md","reason":"确认 React feature 垂直切片、同源 API 和前端质量检查。"}
|
||||
{"file":".trellis/spec/frontend/directory-structure.md","reason":"将 selection API、页面、路由和组件放入正确的 feature 边界。"}
|
||||
{"file":".trellis/spec/frontend/hook-guidelines.md","reason":"实现 React Query 查询、轮询和 mutation,不在页面直接 fetch。"}
|
||||
{"file":".trellis/spec/frontend/state-management.md","reason":"将运行状态和结果留在 React Query/局部状态,不复制到 Zustand。"}
|
||||
{"file":".trellis/spec/frontend/component-guidelines.md","reason":"实现可访问的结果页面、表格状态和重跑确认 Dialog。"}
|
||||
{"file":".trellis/spec/frontend/type-safety.md","reason":"保持后端响应类型、字面量状态和严格 TypeScript 一致。"}
|
||||
{"file":".trellis/spec/frontend/quality-guidelines.md","reason":"执行前端格式、lint、测试和构建检查。"}
|
||||
{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"同步数据库、领域、HTTP、前端 API 类型和页面状态的跨层契约。"}
|
||||
{"file":"docs/adr/0001-bounded-context-first-modular-monolith.md","reason":"确认 selection 持久化与 HTTP 仍归属模块化单体的明确 bounded context。"}
|
||||
{"file":"docs/adr/0003-postgresql-as-market-data-store.md","reason":"确认 PostgreSQL 是策略输入事实源,CSV 不承担运行时结果查询。"}
|
||||
{"file":"docs/adr/0004-tushare-six-year-snapshot-sync.md","reason":"遵守策略批次关联市场同步批次、覆盖率和策略资格契约。"}
|
||||
@@ -0,0 +1,137 @@
|
||||
# 策略执行结果持久化与查询实施计划
|
||||
|
||||
## 实施原则
|
||||
|
||||
- 只修改 `zhixing-system`;保留用户已有的 `CONTEXT.md` 和 ADR 工作区变更。
|
||||
- 先锁定数据库事务、批次状态和 HTTP 响应测试,再接入前端交互。
|
||||
- 复用已有 `EvaluateZhixingB1`、`PostgresMarketDataReader` 和 shared UI,不复制公式
|
||||
逻辑或网络 transport。
|
||||
- 任何重跑都必须通过“策略 + 目标日”业务键清空旧结果;不要用插入多版本结果来
|
||||
规避唯一约束。
|
||||
- 后台任务只承担当前进程内异步执行;不得在本任务擅自引入任务队列、定时器或新的
|
||||
外部服务。
|
||||
|
||||
## 1. 规划复核与数据库契约
|
||||
|
||||
- [x] 将 `prd.md`、`design.md`、本文件从头读一遍,确认产品决策、验收标准和实现
|
||||
细节没有重复或冲突。
|
||||
- [x] 阅读 `.trellis/spec/backend`、`.trellis/spec/frontend` 及 cross-layer guide,
|
||||
确认 migration、HTTP、前端类型和测试边界。
|
||||
- [x] 设计并实现 `0002_selection_results`:`selection_run`、`selection_run_item`、
|
||||
`selection_signal`、唯一约束、状态索引、JSONB details 和安全 downgrade。
|
||||
- [x] 更新 `modules/market_data/infrastructure/schema.py` metadata,使 Alembic
|
||||
离线/在线上下文包含新表。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv run alembic check
|
||||
uv run pytest tests/integration/test_market_data_migration.py
|
||||
```
|
||||
|
||||
回滚点:migration 或 schema metadata 不一致时只回滚新 migration 和 selection 表,
|
||||
不修改 `0001_market_data`。
|
||||
|
||||
## 2. 领域端口与 PostgreSQL 适配器
|
||||
|
||||
- [x] 在 selection domain 增加批次状态、股票 item、持久化查询模型和明确端口协议。
|
||||
- [x] 为 `PostgresMarketDataReader` 增加读取当前有效执行股票集合的能力,使用目标日、
|
||||
`market_sync_batch.strategy_eligible`、active stock 和 bar/basic 完整性约束。
|
||||
- [x] 在清理旧结果前完成市场同步资格预检;目标日没有可用合格同步批次时返回
|
||||
`market_data_not_ready`,保留已有结果不做破坏性变更。
|
||||
- [x] 新增 selection PostgreSQL repository:短事务创建/删除/完成 run、记录 item 和
|
||||
signal、按 run 或业务键读取;写入 details 时保持 JSON 可序列化。
|
||||
- [x] 以 advisory transaction lock、终态检查和 `rerun` 参数实现首次执行、重复执行、
|
||||
运行中冲突;清理和创建必须在同一事务。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv run pytest tests/unit/selection/test_postgres_runs.py
|
||||
uv run pytest tests/unit/selection/test_postgres_reader.py
|
||||
```
|
||||
|
||||
回滚点:若 SQL 适配器无法通过 fake connection 测试,保留纯领域端口和 migration,
|
||||
先修适配器,不触碰已有市场数据写入代码。
|
||||
|
||||
## 3. 全股票池执行用例与异步服务
|
||||
|
||||
- [x] 实现 `RunZhixingB1`:准备 run、读取有效股票、逐只调用已有评估用例、保存 item
|
||||
和全部信号、汇总并完成 run。
|
||||
- [x] 为 selected/no_signal/insufficient_history/missing_target_bar/data_error 建立
|
||||
明确计数和终态映射;单股失败不影响其他股票,批次级异常收敛为 failed。
|
||||
- [x] 为后台执行提供可注入的 service/worker 入口,确保 HTTP 响应提交后才调度,异常
|
||||
时更新持久化状态;避免把数据库连接对象跨请求/跨线程复用。
|
||||
- [x] 为首次执行、重跑清理、重复运行冲突、失败重试和多分类信号编写应用测试。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv run pytest tests/unit/selection/test_run.py
|
||||
uv run pytest tests/unit/selection/test_evaluate.py
|
||||
```
|
||||
|
||||
## 4. HTTP 接口与后端契约测试
|
||||
|
||||
- [x] 在 `selection/presentation/http.py` 定义请求和响应 Pydantic 模型,稳定声明日期、
|
||||
状态、计数、coverage、失败 item 和 signal details。
|
||||
- [x] 实现 `POST /api/v1/selection/runs`,成功返回 `202 + run_id`;将
|
||||
`run_in_progress`、`rerun_confirmation_required`、输入错误和数据库错误映射为稳定
|
||||
HTTP 响应。
|
||||
- [x] 实现 `GET /api/v1/selection/runs/{run_id}` 和按策略/日期查询结果的 GET 端点;
|
||||
无当前结果返回 `status=no_data`,不要制造假的成功结果。
|
||||
- [x] 在顶层 router 挂载 selection router,保持 `/api/v1` 唯一目录入口;依赖使用
|
||||
`Settings` 注入并提供测试 override。
|
||||
- [x] 用 `TestClient(create_app())` 覆盖真实路由、202、冲突、无数据、轮询和终态 JSON。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd zhixing-server
|
||||
uv run pytest tests/test_selection_http.py
|
||||
```
|
||||
|
||||
## 5. 前端 feature 与交互
|
||||
|
||||
- [x] 新增 `features/selection/api/selection.types.ts`、`selection.api.ts`、
|
||||
`selection.query.ts`,实现结果查询、run 查询/轮询和触发 mutation。
|
||||
- [x] 新增 selection 页面及结果表/统计卡片/重跑确认 Dialog,覆盖初次执行、成功、
|
||||
无命中、失败、部分成功、查询错误和运行中状态。
|
||||
- [x] 为“选股策略”启用 `/selection` 路由和导航,保持 HomeShell 的布局及可访问性;
|
||||
不让未实现的其他导航入口误显为可用。
|
||||
- [x] 重跑或失败重试按钮只打开确认框;取消不调用 POST,确认后才传 `rerun=true`,
|
||||
并在返回的 run 完成后刷新结果查询。
|
||||
- [x] 同一股票多 category 用独立 badge/行展示,details 只显示后端稳定字段。
|
||||
- [x] 更新页面测试和路由相关测试,使用 query hook mock,不依赖真实后端。
|
||||
|
||||
验证:
|
||||
|
||||
```bash
|
||||
cd zhixing-web
|
||||
pnpm format:check
|
||||
pnpm lint
|
||||
pnpm typecheck
|
||||
pnpm test
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 6. 全量质量检查与交付门禁
|
||||
|
||||
- [x] 运行后端 Ruff、Pyright、pytest;运行前端格式、lint、typecheck、test、build。
|
||||
- [x] 检查 `rg -n "except Exception|fetch\(|xg_composite"`,确认没有吞异常、页面直
|
||||
接请求或旧策略运行时依赖。
|
||||
- [x] 检查 migration downgrade、重复执行事务、run status 与前端轮询是否一致。
|
||||
- [x] 使用 `task.py validate` 校验任务清单和上下文 manifests,再由 `trellis-check`
|
||||
做最终规格/跨层检查。
|
||||
|
||||
## 风险与回滚点
|
||||
|
||||
- 新表/适配器失败:只回滚 `0002_selection_results` 和 selection 新代码,不撤销市场
|
||||
数据 migration 或用户既有修改。
|
||||
- 后台任务进程崩溃:当前 run 可能停在 `running`;必须在页面可见并记录为后续恢复
|
||||
机制,不将其当作成功。
|
||||
- 全股票池逐只读取过慢:保留结果契约和端口,后续在 reader/application 层做批量读
|
||||
取;本任务不通过放宽历史数据或减少股票池来掩盖性能问题。
|
||||
@@ -0,0 +1,83 @@
|
||||
# 补充策略执行结果查询接口和前端页面
|
||||
|
||||
## Goal
|
||||
|
||||
为已完成的知行 B1 历史选股逻辑提供可使用的查询入口,让研究人员能够在 Web
|
||||
页面选择目标交易日并查看策略命中的股票及子信号结果。
|
||||
|
||||
## Background and confirmed facts
|
||||
|
||||
- 上一个任务 `08-08-migrate-zhixing-b1` 已完成 `zhixing_b1` 策略领域逻辑、历史
|
||||
qfq 行情读取适配器和单只股票评估用例。
|
||||
- 当前领域入口是
|
||||
`EvaluateZhixingB1.execute(ts_code, target_trade_date)`,返回
|
||||
`SelectionEvaluation`,状态包括 `selected`、`no_signal`、`insufficient_history`、
|
||||
`missing_target_bar` 和 `data_error`。
|
||||
- `SelectionSignal` 已包含股票代码、名称、目标交易日、策略标识、七种独立子信号
|
||||
分类、收盘价和可序列化详情;稳定身份为
|
||||
`(ts_code, target_trade_date, strategy, category)`。
|
||||
- 当前没有选股结果数据库表、结果持久化、全市场批量执行用例或 selection HTTP
|
||||
路由;selection presentation 包仍为空。
|
||||
- 后端 HTTP 通过 `/api/v1` 统一挂载,稳定响应使用 Pydantic 模型;前端按 feature
|
||||
组织 API 类型、Query hook 和页面,并通过同源 `/api/v1/...` 请求后端。
|
||||
- 当前前端首页侧栏的“选股策略”入口仍是禁用按钮,路由树只有 `/` 首页。
|
||||
|
||||
## Product decisions
|
||||
|
||||
- 结果按每日策略批次持久化;页面查询已保存的策略执行批次,不在查询请求中重新
|
||||
计算策略。
|
||||
- 策略执行通过 HTTP 触发,不新增 CLI 作为本任务的主要入口。
|
||||
- 当用户重复执行或重试失败批次时,服务端必须先清空指定目标交易日、指定策略的
|
||||
旧结果,再执行一次;前端在每次重执行前弹窗确认,确认后才发起 HTTP 请求。
|
||||
- 执行接口需要能区分首次执行、已有结果的重复执行和执行中的冲突,不能因重复点击
|
||||
产生重复信号或多个互相冲突的当前结果。
|
||||
- HTTP 采用异步批次模式:`POST` 只创建/清空并启动批次,返回 `202` 和 `run_id`;
|
||||
前端通过 `GET` 轮询批次状态,完成后展示持久化结果。全股票池计算不得要求浏览器
|
||||
长时间保持原始执行请求。
|
||||
|
||||
## Requirements
|
||||
|
||||
- 策略执行结果必须按目标交易日和策略批次持久化,并可被后续 HTTP 查询读取。
|
||||
- `zhixing_b1` 的多种独立子信号必须分别保存,不能因同一股票同日多分类而覆盖或
|
||||
合并;结果身份继续遵循
|
||||
`(ts_code, target_trade_date, strategy, category)`。
|
||||
- 查询页面展示已保存批次的执行状态、目标交易日、参与数量、命中数量以及股票和
|
||||
子信号明细;查询失败、执行失败和无命中必须有可区分的用户可见状态。
|
||||
- 同一策略同一目标交易日重复执行必须具备幂等语义,不能产生重复信号或多个互相
|
||||
冲突的“最新结果”;按用户确认的重跑规则,重跑前清空该日该策略旧结果。
|
||||
- 结果应关联产生它所依赖的市场数据同步批次,并保留实际参与股票数和数据覆盖率,
|
||||
以便判断结果是否完整。
|
||||
- HTTP 应提供策略执行触发接口,支持指定目标交易日和策略;首次执行、重跑确认、
|
||||
执行中冲突和失败重试应有明确响应语义。
|
||||
- HTTP 响应使用稳定的 Pydantic 契约;前端使用 feature API 类型、React Query 和
|
||||
独立的策略结果页面,不在页面中直接发起 `fetch`。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] 已执行的 `zhixing_b1` 批次及其信号结果可以写入 PostgreSQL,并能通过稳定
|
||||
唯一身份幂等重跑;重跑不会残留上一次执行的信号。
|
||||
- [ ] HTTP 可以查询最新批次和/或指定目标交易日的持久化结果,响应包含批次状态、
|
||||
覆盖率、命中统计和全部独立子信号明细。
|
||||
- [ ] HTTP 可以触发指定目标交易日和策略的执行;已有结果重跑和失败重试遵循清空后
|
||||
重算规则,并对执行中的重复请求返回可识别冲突。
|
||||
- [ ] 前端“选股策略”入口可进入结果页面,能够查看加载中、无数据、执行失败、查询
|
||||
失败、无命中和正常结果状态。
|
||||
- [ ] 前端每次重跑/失败重试都会先展示确认弹窗;取消不会发起执行请求,确认后能显示
|
||||
执行中状态并刷新持久化结果。
|
||||
- [ ] 执行触发接口返回异步批次标识,前端可通过状态查询感知运行中、成功、无命中和
|
||||
失败,并在结束后读取同一批次结果。
|
||||
- [ ] 结果页面的股票明细能区分同一股票同日命中的多个子信号,并展示目标交易日、
|
||||
股票代码/名称、收盘价及关键详情。
|
||||
- [ ] 后端迁移、应用用例、HTTP 契约和前端页面测试覆盖成功、重复执行、无结果和
|
||||
失败场景。
|
||||
|
||||
## Out of scope
|
||||
|
||||
- 不迁移其他选股策略,不改变 `zhixing_b1` 公式语义或市场数据同步规则。
|
||||
- 不实现收益率、持仓、交易撮合或实盘交易能力;本任务展示的是选股信号结果。
|
||||
- 不把执行调度扩展为独立任务队列或跨进程工作流;本期只提供 HTTP 触发和项目现有
|
||||
运行边界内的异步批次执行机制。
|
||||
|
||||
## Open questions
|
||||
|
||||
无。
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "strategy-execution-results",
|
||||
"name": "strategy-execution-results",
|
||||
"title": "补充策略执行结果查询接口和前端页面",
|
||||
"description": "",
|
||||
"status": "completed",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "yuxuanhui",
|
||||
"assignee": "yuxuanhui",
|
||||
"createdAt": "2026-08-08",
|
||||
"completedAt": "2026-08-09",
|
||||
"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