量价研判链:研判应答归一带出两个期限的头进提议硬数字与提议卡;驳回行记把握度;接口契约 V1.2(第二十二批加一例,全套 680)
This commit is contained in:
parent
7d670b364c
commit
0fa4a5a2ff
|
|
@ -299,3 +299,4 @@ docker compose run --rm --no-deps pms-web python scripts/probe_bionic.py --base
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| V1.0 | 2026-08-03 | 首版定稿并双侧落码。择时部分是一套盘中判定规则(资金阈值、动量追买等) |
|
| V1.0 | 2026-08-03 | 首版定稿并双侧落码。择时部分是一套盘中判定规则(资金阈值、动量追买等) |
|
||||||
| V1.1 | 2026-08-03 | 按用户审核意见重做择时:删掉 V1.0 的盘中判定规则(违反「提前计算为主、盘中监控为辅、不另做盘中判断、可以接受买不上」),改为由昨夜支撑压力推出执行区间、盘中只做区间比对与当日监控核对;研判留痕不再写 `decision_ledger`(每晚判分全表扫描,已核实);文档与代码注释清理生造词 |
|
| V1.1 | 2026-08-03 | 按用户审核意见重做择时:删掉 V1.0 的盘中判定规则(违反「提前计算为主、盘中监控为辅、不另做盘中判断、可以接受买不上」),改为由昨夜支撑压力推出执行区间、盘中只做区间比对与当日监控核对;研判留痕不再写 `decision_ledger`(每晚判分全表扫描,已核实);文档与代码注释清理生造词 |
|
||||||
|
| V1.2 | 2026-09-08 | 量价研判链(《量价研判链吸收方案_2026-09-08》):请求侧硬数字白名单多送 `events_text`、`pricing_text` 两句整句(催化事件与定价状态,事件与量价不是产业逻辑);应答侧新建仓研判可并列多带 `pv_heads`(`h5`、`h20` 各含 `score` −1 到 1、`direction` 看多/中性/看空、`justification`),PMS 归一后进提议硬数字 `judge_pv_heads`,只显示不触发;三值 verdict 口径不变,缺 `pv_heads` 就是 None,向下兼容 |
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,26 @@ def _conf_or_none(v):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _pv_heads_or_none(v):
|
||||||
|
"""应答里的 pv_heads 归一: {h5: {score, direction, justification}, h20: {...}}; 评分夹到 -1 到 1,
|
||||||
|
两个头都没有评分就是 None。"""
|
||||||
|
if not isinstance(v, dict):
|
||||||
|
return None
|
||||||
|
out = {}
|
||||||
|
for k in ("h5", "h20"):
|
||||||
|
h = v.get(k)
|
||||||
|
if not isinstance(h, dict):
|
||||||
|
continue
|
||||||
|
score = _conf_or_none(h.get("score"))
|
||||||
|
if score is None:
|
||||||
|
continue
|
||||||
|
score = max(-1.0, min(1.0, score))
|
||||||
|
direction = "看多" if score > 0.2 else ("看空" if score < -0.2 else "中性")
|
||||||
|
out[k] = {"score": round(score, 3), "direction": direction,
|
||||||
|
"justification": str(h.get("justification") or "").strip()[:120]}
|
||||||
|
return out or None
|
||||||
|
|
||||||
|
|
||||||
def _map_verdict(data: dict) -> dict:
|
def _map_verdict(data: dict) -> dict:
|
||||||
"""把决策系统应答的 verdict 归一到 PASS / REJECT / UNAVAILABLE, 并带出原因与置信度。
|
"""把决策系统应答的 verdict 归一到 PASS / REJECT / UNAVAILABLE, 并带出原因与置信度。
|
||||||
|
|
||||||
|
|
@ -153,16 +173,19 @@ def _map_verdict(data: dict) -> dict:
|
||||||
verdict = str(data.get("verdict") or data.get("decision") or "").upper()
|
verdict = str(data.get("verdict") or data.get("decision") or "").upper()
|
||||||
reason = data.get("reason") or data.get("rationale") or ""
|
reason = data.get("reason") or data.get("rationale") or ""
|
||||||
conf = _conf_or_none(data.get("confidence"))
|
conf = _conf_or_none(data.get("confidence"))
|
||||||
|
# 两个期限的头 (2026-09-08 量价研判链): 决策系统在应答里并列给的 5 日与 20 日判断, 原样带出进提议硬数字,
|
||||||
|
# 只显示不触发。缺就是 None, 旧应答一个字不用改。
|
||||||
|
heads = _pv_heads_or_none(data.get("pv_heads"))
|
||||||
if verdict in ("PASS", "APPROVE", "APPROVED", "ALLOW", "通过"):
|
if verdict in ("PASS", "APPROVE", "APPROVED", "ALLOW", "通过"):
|
||||||
return {"verdict": PASS, "reason": reason, "degraded": False, "raw": data,
|
return {"verdict": PASS, "reason": reason, "degraded": False, "raw": data,
|
||||||
"confidence": conf}
|
"confidence": conf, "pv_heads": heads}
|
||||||
if verdict in ("REJECT", "DENY", "DENIED", "BLOCK", "驳回"):
|
if verdict in ("REJECT", "DENY", "DENIED", "BLOCK", "驳回"):
|
||||||
return {"verdict": REJECT, "reason": reason, "degraded": False, "raw": data,
|
return {"verdict": REJECT, "reason": reason, "degraded": False, "raw": data,
|
||||||
"confidence": conf}
|
"confidence": conf, "pv_heads": heads}
|
||||||
if verdict in ("UNAVAILABLE", "PMS_UNAVAILABLE", "NA", "N/A", "不可用"):
|
if verdict in ("UNAVAILABLE", "PMS_UNAVAILABLE", "NA", "N/A", "不可用"):
|
||||||
logger.warning("[研判闸] 决策系统回不可用, 降级人工确认: %s", reason or "(无原因)")
|
logger.warning("[研判闸] 决策系统回不可用, 降级人工确认: %s", reason or "(无原因)")
|
||||||
return {"verdict": UNAVAILABLE, "reason": reason or "决策系统研判不可用",
|
return {"verdict": UNAVAILABLE, "reason": reason or "决策系统研判不可用",
|
||||||
"degraded": True, "raw": data, "confidence": conf}
|
"degraded": True, "raw": data, "confidence": conf, "pv_heads": heads}
|
||||||
logger.error("[研判闸] 答复无法识别 (%s), 按不可用降级", verdict or data)
|
logger.error("[研判闸] 答复无法识别 (%s), 按不可用降级", verdict or data)
|
||||||
return {"verdict": UNAVAILABLE, "reason": f"研判答复无法识别: {verdict or data}",
|
return {"verdict": UNAVAILABLE, "reason": f"研判答复无法识别: {verdict or data}",
|
||||||
"degraded": True, "raw": data, "confidence": conf}
|
"degraded": True, "raw": data, "confidence": conf}
|
||||||
|
|
|
||||||
|
|
@ -527,9 +527,12 @@ def _route_one(c, view, params, stock_params, brake_active, now, dry_run, out,
|
||||||
out["rejected"].append({"ts_code": code, "action": action, "by": "judge",
|
out["rejected"].append({"ts_code": code, "action": action, "by": "judge",
|
||||||
"failed": [verdict.get("reason") or "决策系统驳回"]})
|
"failed": [verdict.get("reason") or "决策系统驳回"]})
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
|
# 驳回也把把握度记进硬数字 (2026-09-08): 低把握驳回转交人那道保险 (PMS_JUDGE_REJECT_CONF_MIN)
|
||||||
|
# 是否在起作用, 只能从驳回行的把握度分布看出来; 此前只有提议行记它, 驳回行没有, 复核无据。
|
||||||
pms_repo.insert_ledger(ts_code=code, action=action, arbiter="judge",
|
pms_repo.insert_ledger(ts_code=code, action=action, arbiter="judge",
|
||||||
verdict="REJECT", price_at=price,
|
verdict="REJECT", price_at=price,
|
||||||
hard_numbers=c["hard_numbers"],
|
hard_numbers={**(c["hard_numbers"] or {}),
|
||||||
|
"judge_conf": verdict.get("confidence")},
|
||||||
reason=verdict.get("reason") or "决策系统驳回")
|
reason=verdict.get("reason") or "决策系统驳回")
|
||||||
return
|
return
|
||||||
if verdict.get("degraded"):
|
if verdict.get("degraded"):
|
||||||
|
|
@ -737,7 +740,9 @@ def _make_proposal(c, price, verdict) -> str:
|
||||||
"source": c.get("source") or ae.SRC_ENGINE,
|
"source": c.get("source") or ae.SRC_ENGINE,
|
||||||
# 研判应答的结论与置信度 (2026-09-03): 人裁决时要看得见决策系统怎么说、有多确定。
|
# 研判应答的结论与置信度 (2026-09-03): 人裁决时要看得见决策系统怎么说、有多确定。
|
||||||
# judge_reason 另有一列, 这两项进硬数字是为了随账本走 (采纳/驳回时原样落账)。
|
# judge_reason 另有一列, 这两项进硬数字是为了随账本走 (采纳/驳回时原样落账)。
|
||||||
"judge_verdict": verdict.get("verdict"), "judge_conf": verdict.get("confidence")}
|
"judge_verdict": verdict.get("verdict"), "judge_conf": verdict.get("confidence"),
|
||||||
|
# 两个期限的头 (2026-09-08 量价研判链): 决策系统并列给的 5 日与 20 日判断, 只显示不触发。
|
||||||
|
"judge_pv_heads": verdict.get("pv_heads")}
|
||||||
try:
|
try:
|
||||||
pms_repo.insert_proposal(
|
pms_repo.insert_proposal(
|
||||||
proposal_id=pid, ts_code=c["ts_code"], action=c["action"], qty=c["qty"],
|
proposal_id=pid, ts_code=c["ts_code"], action=c["action"], qty=c["qty"],
|
||||||
|
|
|
||||||
|
|
@ -1034,6 +1034,7 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
|
||||||
<div class="st muted" v-if="propText(p, 'valuation_text')">{{ propText(p, 'valuation_text') }}</div>
|
<div class="st muted" v-if="propText(p, 'valuation_text')">{{ propText(p, 'valuation_text') }}</div>
|
||||||
<div class="st muted" v-if="propText(p, 'events_text')">{{ propText(p, 'events_text') }}</div>
|
<div class="st muted" v-if="propText(p, 'events_text')">{{ propText(p, 'events_text') }}</div>
|
||||||
<div class="st muted" v-if="propText(p, 'pricing_text')">{{ propText(p, 'pricing_text') }}</div>
|
<div class="st muted" v-if="propText(p, 'pricing_text')">{{ propText(p, 'pricing_text') }}</div>
|
||||||
|
<div class="st" v-if="propHeads(p)"><span class="muted">择时层两个期限:</span>{{ propHeads(p) }}</div>
|
||||||
<div class="st muted">{{ propWhy(p) }}</div>
|
<div class="st muted">{{ propWhy(p) }}</div>
|
||||||
<div class="st">约 {{ p.qty }} 股 · 参考价 {{ (p.hard_numbers||{}).price==null ? '—' : (p.hard_numbers||{}).price }}
|
<div class="st">约 {{ p.qty }} 股 · 参考价 {{ (p.hard_numbers||{}).price==null ? '—' : (p.hard_numbers||{}).price }}
|
||||||
· 约需 {{ money(((p.hard_numbers||{}).price||0)*(p.qty||0)) }}</div>
|
· 约需 {{ money(((p.hard_numbers||{}).price||0)*(p.qty||0)) }}</div>
|
||||||
|
|
@ -2584,6 +2585,20 @@ createApp({
|
||||||
const v = (p.hard_numbers || {})[key];
|
const v = (p.hard_numbers || {})[key];
|
||||||
return (typeof v === 'string' && v.trim()) ? v : '';
|
return (typeof v === 'string' && v.trim()) ? v : '';
|
||||||
}
|
}
|
||||||
|
// 决策系统并列给的 5 日与 20 日判断 (2026-09-08 量价研判链): 方向词是它给的中文, 评分是 -1 到 1 的数。
|
||||||
|
// 只显示不触发; 没有就不显示这一行。
|
||||||
|
function propHeads(p) {
|
||||||
|
const h = (p.hard_numbers || {}).judge_pv_heads;
|
||||||
|
if (!h || typeof h !== 'object') return '';
|
||||||
|
const bits = [];
|
||||||
|
[['h5', '5 日'], ['h20', '20 日']].forEach(([k, name]) => {
|
||||||
|
const x = h[k];
|
||||||
|
if (x && typeof x.score === 'number') {
|
||||||
|
bits.push(name + x.direction + '(' + (x.score >= 0 ? '+' : '') + x.score.toFixed(2) + (x.justification ? ',' + x.justification : '') + ')');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return bits.join(';');
|
||||||
|
}
|
||||||
function propLogic(p) {
|
function propLogic(p) {
|
||||||
const l = (p.hard_numbers || {}).logic;
|
const l = (p.hard_numbers || {}).logic;
|
||||||
return Array.isArray(l) && l.length ? l[0] : '';
|
return Array.isArray(l) && l.length ? l[0] : '';
|
||||||
|
|
|
||||||
|
|
@ -59,16 +59,16 @@
|
||||||
test_batch22_units.py 逻辑状态四态接入 (2026-09-07 第三件): 解析层收逻辑状态/硬数字带它而研判
|
test_batch22_units.py 逻辑状态四态接入 (2026-09-07 第三件): 解析层收逻辑状态/硬数字带它而研判
|
||||||
白名单不收/判决候选而逻辑存疑强制交人/持仓存疑停增持侧/研究走弱
|
白名单不收/判决候选而逻辑存疑强制交人/持仓存疑停增持侧/研究走弱
|
||||||
减持默认关且开了必交人/同轮减持优先级/早上取回与映射新鲜度/策略
|
减持默认关且开了必交人/同轮减持优先级/早上取回与映射新鲜度/策略
|
||||||
买入腿按来源暂停恢复/持仓视图两栏三情形/假仓库签名/安全边际整句透传/参考目标价/催化事件与定价状态透传 (18 例)
|
买入腿按来源暂停恢复/持仓视图两栏三情形/假仓库签名/安全边际整句透传/参考目标价/催化事件与定价状态透传/两个期限的头 (19 例)
|
||||||
test_page_enum_guard.py 页面文案守卫 (静态扫描, 不连库不起浏览器): 枚举字段不许
|
test_page_enum_guard.py 页面文案守卫 (静态扫描, 不连库不起浏览器): 枚举字段不许
|
||||||
直接印到页面上 / 判据码显示前必须剥前缀 / 不许把整个对象
|
直接印到页面上 / 判据码显示前必须剥前缀 / 不许把整个对象
|
||||||
打给交易员看 / 翻译兜底不许让英文码单独当句子 (1 例)
|
打给交易员看 / 翻译兜底不许让英文码单独当句子 (1 例)
|
||||||
test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) +
|
test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) +
|
||||||
目标价到价必定入队 (档位 full 也不自动卖) +
|
目标价到价必定入队 (档位 full 也不自动卖) +
|
||||||
用户设的止损价与目标价单独成列显示 (70 例)
|
用户设的止损价与目标价单独成列显示 (70 例)
|
||||||
共 679 例
|
共 680 例
|
||||||
(总数按实跑逐批相加校正过两次: 曾写 649 是笔误, 实为 650; 09-03 先后加了同轮只发一条
|
(总数按实跑逐批相加校正过两次: 曾写 649 是笔误, 实为 650; 09-03 先后加了同轮只发一条
|
||||||
减持与研究理由两键各一例, 到 652; 09-04 加了仅展示跳过原因与空候选说明各一例, 到 654; 又加了页面文案守卫一例, 到 655; 09-07 审查修复加了跨轮减持等五例, 到 660; 第二件低把握驳回交人一例, 到 661; 第三件逻辑状态接入第二十二批十五例, 到 676; 第四件安全边际整句透传一例, 到 677; 参考目标价一例, 到 678; 催化事件与定价状态透传一例, 现为 679)
|
减持与研究理由两键各一例, 到 652; 09-04 加了仅展示跳过原因与空候选说明各一例, 到 654; 又加了页面文案守卫一例, 到 655; 09-07 审查修复加了跨轮减持等五例, 到 660; 第二件低把握驳回交人一例, 到 661; 第三件逻辑状态接入第二十二批十五例, 到 676; 第四件安全边际整句透传一例, 到 677; 参考目标价一例, 到 678; 催化事件与定价状态透传一例, 到 679; 两个期限的头一例, 现为 680)
|
||||||
任一子集失败即整体失败 (退出码 1)。
|
任一子集失败即整体失败 (退出码 1)。
|
||||||
|
|
||||||
哨兵位置清单 (2026-09-03 抄录; 改了对应的东西就得来这些地方改断言, 断言不动就是漏了):
|
哨兵位置清单 (2026-09-03 抄录; 改了对应的东西就得来这些地方改断言, 断言不动就是漏了):
|
||||||
|
|
|
||||||
|
|
@ -496,6 +496,25 @@ def _():
|
||||||
assert lss.ref_target_view(None) is None and lss.ref_target_view({}) is None
|
assert lss.ref_target_view(None) is None and lss.ref_target_view({}) is None
|
||||||
|
|
||||||
|
|
||||||
|
@case("两个期限的头·研判应答归一带出 pv_heads (评分夹区间、方向按评分), 提议硬数字带 judge_pv_heads, 旧应答为 None")
|
||||||
|
def _():
|
||||||
|
r = jd._map_verdict({"verdict": "PASS", "reason": "ok", "confidence": 70,
|
||||||
|
"pv_heads": {"h5": {"score": 1.7, "direction": "看空", "justification": "x"},
|
||||||
|
"h20": {"score": "-0.1", "justification": "y"}, "h99": {"score": 0.5}}})
|
||||||
|
assert r["verdict"] == jd.PASS and r["pv_heads"]["h5"] == {"score": 1.0, "direction": "看多", "justification": "x"}, r
|
||||||
|
assert r["pv_heads"]["h20"]["direction"] == "中性" and "h99" not in r["pv_heads"], r
|
||||||
|
assert jd._map_verdict({"verdict": "REJECT", "reason": "no"})["pv_heads"] is None
|
||||||
|
assert jd._map_verdict({"verdict": "UNAVAILABLE", "reason": "x", "pv_heads": {"h5": {"direction": "看多"}}})["pv_heads"] is None
|
||||||
|
c = {"ts_code": CODE, "action": "OPEN", "side": "buy", "qty": 1000, "reason": "测试新建仓", "price": 10.0,
|
||||||
|
"hard_numbers": {"price": 10.0, "verdict": "候选"}, "needs_user_confirm": False, "judge_required": True,
|
||||||
|
"source": ae.SRC_ENGINE, "target_amount": 10000.0, "sector": None}
|
||||||
|
out, got = _route(c, autonomy="propose_only", judge_resp={**JUDGE_PASS, "pv_heads": {"h5": {"score": 0.4, "direction": "看多", "justification": "a"}}})
|
||||||
|
assert len(out["queued"]) == 1 and got["proposals"], out
|
||||||
|
assert got["proposals"][0]["hard_numbers"]["judge_pv_heads"] == {"h5": {"score": 0.4, "direction": "看多", "justification": "a"}}, got["proposals"][0]["hard_numbers"]
|
||||||
|
out2, got2 = _route(c, autonomy="propose_only", judge_resp=JUDGE_PASS)
|
||||||
|
assert got2["proposals"][0]["hard_numbers"].get("judge_pv_heads") is None
|
||||||
|
|
||||||
|
|
||||||
@case("假仓库·ledger_by_ref 与真 repo 同名同签名 (第十批 [M1] 也会扫, 这里先钉一次)")
|
@case("假仓库·ledger_by_ref 与真 repo 同名同签名 (第十批 [M1] 也会扫, 这里先钉一次)")
|
||||||
def _():
|
def _():
|
||||||
import ast
|
import ast
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue