7.4 KiB
上游选股计划接口 · 接入记录与待确认口径
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)
应答骨架:
{
"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 强刷 |
五条钉死的口径:
- 计划里没有价格、没有金额。 上游只回答「买什么、排第几」; 买多少、什么价位是 PMS
自己的活 (sizer/planner)。价格一律现取
market.get_price, 取不到就不进池并记 warning —— 不进池比拿一个假价格进去安全。 - 拿不到 ≠ 今天没票可买。 接口失败时候选池为空 + ERROR 告警, 绝不静默回退到
trading_buy_plan。那张表在目标架构下没有明确写入方, 拿它当事实源比没有候选更危险。 页面顶部会挂红色横幅, 不会安静地什么都不买。 - 961 只主榜不是买入清单, 是排序池。 默认按 score 降序取前 30 (
PMS_PLAN_TOP_N), 档位再按PMS_PLAN_TIERS白名单过滤 (默认只要「强传导」)。 - 日龄按交易日算并硬校验。 默认允许 1 个交易日 (上游是收盘后出下一日的计划), 超了直接拒用。防的是上游停更/长假时拿上周的榜当今天用 —— 这种错在盘中完全静默。
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。 那张表还有别的消费方吗?
若没有, 上游可以停写 —— 少一处没有明确写入方的"事实源"。