Files
zhixing-system/.trellis/tasks/archive/2026-08/08-08-migrate-zhixing-b1/design.md
T
2026-08-09 12:34:02 +08:00

218 lines
9.3 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.
# 知行 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 字段。