tradingSystem/UPSTREAM_PLAN_API.md

135 lines
7.4 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.

# 上游选股计划接口 · 接入记录与待确认口径
> 2026-07-30 建。上游「选股系统」提供 HTTP 计划接口, PMS 的升仓/建仓候选池自此改吃它,
> 不再读 `trading_buy_plan`。本文是接口现状 + PMS 侧接法 + **需要上游回答的问题**。
> 代码入口: `app/services/plan_feed.py`; 单测: `scripts/test_batch7_units.py` (23 例);
> 实机探活: `scripts/probe_plan_api.py`。
---
## 1. 接口现状 (上游给的)
```
GET http://192.168.16.155:8300/plan
?date=2026-07-29 可选, 缺省=最新一份计划
&format=md 可选, 输出格式 (PMS 只用默认的 JSON)
```
应答骨架:
```json
{
"date": "2026-07-29",
"counts": { "main": 961, "observe": 107, "gate_covered": 2386 },
"market_snapshot_days": ["2026-07-28"],
"heat_date": "2026-07-27",
"theme_cap": 5,
"main": [
{ "rank": 1, "code": "SH600418", "name": "江淮汽车", "score": 242.24,
"evidence": { "theme": "整车", "n_sources": 7, "moved_ratio": 0.0 },
"heat": 0.2249, "upside": 2.1203202525935945, "tier": "强传导" }
],
"observe": [
{ "rank": 1, "code": "SH600877", "name": "电科芯片", "score": 101.18,
"evidence": { "theme": "集成电路设计", "n_sources": 9, "moved_ratio": 0.0 },
"heat": 0.4743, "upside": null }
],
"changes": null,
"encoding": "主榜分=200+传导档位×20+组内分(还没热、还便宜);观察档分=100+0.6z(传导)+0.4z(−热度)"
}
```
字段差异: 观察档没有 `tier`, `upside` 恒为 `null`。代码是前缀式 (`SH600418`),
PMS 内部统一点式 (`600418.SH`), 由 `command_spec.normalize_code` 转。
---
## 2. PMS 侧接法 (已实现)
| 环节 | 落点 |
|---|---|
| 取数与解析 | `plan_feed.fetch()` / `parse_plan()` —— 变形/超时/过期一律抛 `PlanFeedError` |
| 新鲜度 | `assert_fresh()` —— 日龄按**交易日**算, 超 `PMS_PLAN_STALE_TDAYS` 拒用 |
| 筛选 | `select_candidates()` —— score 降序、档位白名单、top-N、观察档闸门 |
| 候选池 | `command_service._candidates()` —— 按 `PMS_CANDIDATE_SOURCE` 取源 |
| 行业源 | 刷新时把 `evidence.theme` upsert 进 `pms_industry_map`, 行业源仍走 `custom_table` |
| 调度 | `pms.plan_pull` 交易日 08:40 (在 premarket 08:50 之前, 盘前算集中度要用映射) |
| 页面 | 头部「上游计划」抽屉 + 不可用时顶部红色横幅; 运维按钮「强刷 + 灌行业映射」 |
| 接口 | `GET /api/upstream/plan` 预览 · `POST /api/ops/plan-refresh` 强刷 |
五条钉死的口径:
1. **计划里没有价格、没有金额。** 上游只回答「买什么、排第几」; 买多少、什么价位是 PMS
自己的活 (sizer/planner)。价格一律现取 `market.get_price`, **取不到就不进池**并记 warning
—— 不进池比拿一个假价格进去安全。
2. **拿不到 ≠ 今天没票可买。** 接口失败时候选池为空 + ERROR 告警, **绝不静默回退**到
`trading_buy_plan`。那张表在目标架构下没有明确写入方, 拿它当事实源比没有候选更危险。
页面顶部会挂红色横幅, 不会安静地什么都不买。
3. **961 只主榜不是买入清单, 是排序池。** 默认按 score 降序取前 30 (`PMS_PLAN_TOP_N`),
档位再按 `PMS_PLAN_TIERS` 白名单过滤 (默认只要「强传导」)。
4. **日龄按交易日算并硬校验。** 默认允许 1 个交易日 (上游是收盘后出下一日的计划),
超了直接拒用。防的是上游停更/长假时拿上周的榜当今天用 —— 这种错在盘中完全静默。
5. **`upside` 不参与任何过滤与排序** (单位未确认, 见下 Q1), 只在页面和探活脚本里展示。
参数 (页面「参数设置」可改, 表值优先):
```
PMS_CANDIDATE_SOURCE plan_api(默认) / buy_plan / both
PMS_PLAN_API_BASE http://192.168.16.155:8300 空=停用(候选池恒为空并告警)
PMS_PLAN_API_PATH /plan
PMS_PLAN_TIMEOUT 10 秒
PMS_PLAN_CACHE_SEC 300 秒 (失败另有 60 秒负缓存, 防每分钟调度位叠超时)
PMS_PLAN_TOP_N 30 主榜取前 N 进候选池
PMS_PLAN_TIERS 强传导 传导档白名单, 空=不按档过滤
PMS_PLAN_INCLUDE_OBSERVE false 观察档是否进候选池
PMS_PLAN_MIN_SCORE 0 score 下限, 0=不设
PMS_PLAN_MIN_SOURCES 0 evidence.n_sources 下限, 0=不设
PMS_PLAN_STALE_TDAYS 1 日龄上限 (交易日)
PMS_PLAN_THEME_SYNC true 刷新时把 theme 灌进 pms_industry_map
```
行业硬拦截的开法: 先跑一次「强刷 + 灌行业映射」(或等 08:40 调度位), 再把
`PMS_SECTOR_SOURCE` 改成 `custom_table`。映射表页面可见可手改, 覆盖面随每天刷新累积
—— 这就是不用「即时查当日计划」的原因: 今天没上榜的持仓票也得有行业标签, 否则它的
行业约束会悄悄失效。
---
## 3. 待上游确认 (按重要性排)
**Q1 `upside` 的单位和口径是什么?** 样例里 0.2617 ~ 2.1203。若是百分比, 那 rank 1 的
「上涨空间 2.12%」小得不足以建仓; 若是倍数, 则是 212%。两种解读会导出完全相反的用法。
分母是什么 —— 压力位 / 目标价 / 近期高点? PMS 现在**完全不用它**, 口径定了才敢接。
**Q2 `date` 是「计划生成日」还是「适用交易日」?** 样例 `date=2026-07-29`,
`market_snapshot_days=["2026-07-28"]`, `heat_date=2026-07-27`。若 date 是生成日, 那么
07-30 开盘该用哪一份? PMS 目前按「date 至多比今天旧 1 个交易日」放行, 口径确认后调
`PMS_PLAN_STALE_TDAYS`。**另: 上游出计划的时点是每天几点?** 08:40 的拉取会不会太早。
**Q3 `main` 会不会分页或截断?** `counts.main=961`, 但一次应答真的会回 961 条吗?
有没有 `limit` / `offset` / `top` 参数? PMS 已把 `counts` (上游全量) 与实际返回条数分开
记录, 两者不一致时会打 warning, 但需要知道正确的取全量方式。
**Q4 `changes` 字段的结构?** 样例是 `null`。若是「与上一份计划的差异」, 请给一个非空
示例 —— PMS 想用它在页面上标「新进榜 / 掉榜」, 那是判断上游观点变化最直接的信号。
**Q5 `counts.gate_covered = 2386` 是什么口径?** 闸门覆盖的股票总数? 与 main+observe
(=1068) 的关系是什么? 目前只做透传展示。
**Q6 `theme` 词表稳定吗? 同一只票的 theme 会日间变动吗?** PMS 把它当行业标签落库,
用来做行业集中度**硬拦截**; 词表漂移会直接影响拦不拦。另: `theme_cap=5` 是上游自己的
同主题限额吗? 若是, PMS 侧的 `PMS_SECTOR_MAX_NAMES` (默认 4) 应否与它对齐。
**Q7 `tier` 的完整枚举?** 样例只见「强传导」。PMS 用它做白名单过滤, 需要知道全集
(以及档位之间的强弱次序)。
**Q8 没有当日计划时的失败语义?** 返回 404 / 空 `main` / 上一日的计划? PMS 目前把
「两档全空」按变形处理并拒用。**接口需要鉴权或有限流吗?** (PMS 默认缓存 300 秒,
每交易日至少一次盘前拉取 + 页面手动强刷。)
**Q9 观察档 `upside` 恒为 `null` 是设计如此吗?** 若观察档本就没有空间测算, PMS 就把它
只当备选池 (当前默认 `PMS_PLAN_INCLUDE_OBSERVE=false`)。
**Q10 (给决策系统侧) PMS 已不再读 `trading_buy_plan`。** 那张表还有别的消费方吗?
若没有, 上游可以停写 —— 少一处没有明确写入方的"事实源"。