diff --git a/DEVLOG.md b/DEVLOG.md index f9be6e5..27824d3 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1939,3 +1939,11 @@ PMS 页面需要重新登录;基座页面版本与研究字段已实机核对 真实浏览器侧栏 295 像素、来源区 694 像素均无横向溢出,默认折叠与基本面详情展开通过。 裁决按钮回到提议摘要之后,重载后无新脚本错误。未触发裁决操作。 备份指纹、生效方式、回退与截图见四仓同步交接最新一节。 + +## 2026-10-16 交易员界面整体精简(本地完成,部署待判收) + +用户要求围绕建议、依据和风险整理整个交易员界面,重点为右侧提议和候选。仅修改静态页面。提议突出数量、预计金额和建议仓位;仓位不一致仍显著提示。重算过程移入数量说明,通用操作说明只保留一次,有效期转为北京时间短格式。清仓使用中性的预计金额,缺价显示缺失。候选保留原列表与排序,直接显示等待条件;占用提示去除内部代码数组,未知失败提示原样保留。交易方案显示当前可见数量,暂停买入的原因可展开。持仓、信号、顶栏和计划详情同步简化,技术来源折叠保留。 + +本地验证:三项页面守卫全部通过;脚本语法、金额缺失、北京时间跨日、仓位差异、原值不变的临时断言通过。采纳、驳回、策略动作、计划读取刷新函数及候选筛选表达式与改前逐字一致。未修改 Python、运行参数或交易规则。 + +部署预判:155 当前为 f79ddff,pms-web 通过仓库绑定挂载提供静态页。先备份页面,以匹配的运行镜像断网运行三项页面守卫,再仅 git pull。预期四容器镜像和启动时间不变。浏览器应显示精简提议、可读的候选等待状态,数量说明与基本面入口正常,关键风险仍可见,右栏无横向溢出。不得采纳提议或制造交易用于验收。188 不操作。 diff --git a/app/web/static/index.html b/app/web/static/index.html index 27e2bbf..b3fd9c6 100644 --- a/app/web/static/index.html +++ b/app/web/static/index.html @@ -492,6 +492,17 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;} .chip.hd-user{background:var(--accent-weak);color:var(--accent);} .chip.hd-user .dot{background:var(--accent);} /* ── 提议卡: 来源结论行 + 详情弹窗 (2026-09-12 重构) ── */ .prop-src{margin:9px 0 4px;border-top:1px solid var(--hair);} +.prop-order{margin:7px 0 4px;display:flex;align-items:baseline;justify-content:space-between;gap:8px;flex-wrap:wrap;} +.prop-order b{font-size:15px;font-variant-numeric:tabular-nums;} +.prop-meta{display:flex;align-items:center;gap:6px 12px;flex-wrap:wrap;color:var(--muted);font-size:12px;} +.prop-actions{display:flex;align-items:center;gap:8px;margin-top:8px;} +.prop-actions .el-button+.el-button{margin-left:0;} +.prop-actions .research-link{margin-left:auto;font-size:12px;} +.rail-note{font-size:12px;color:var(--muted);margin:0 0 8px;} +.rail-detail{font-size:12px;color:var(--ink-2);margin-top:5px;overflow-wrap:anywhere;} +.rail-detail summary{cursor:pointer;color:var(--muted);} +.sigrow2.clk .cand-status{flex-basis:100%;font-size:11.5px;color:var(--muted);padding-left:35px;} +.sigrow2.clk .cand-status.would{color:var(--accent);} .prop-src-row{display:flex;align-items:center;gap:8px;padding:8px 2px;border-bottom:1px solid rgba(0,0,0,.04);cursor:pointer;} .prop-src-row:last-child{border-bottom:none;} .prop-src-row:hover{background:var(--surface-2);} @@ -551,7 +562,7 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
{{ planResult }}
@@ -3124,19 +3132,29 @@ createApp({
if (d.disp === 'would') return { cls: 'would', label: '拟建仓', why: d.why };
// 三源合议判观察 (2026-09-11): 等技术面开口, 是「在盯」不是「被否决」——
// 归到 watch 那一档而不是今天没建仓那一档, 因为它还会回来。
- if (d.disp === 'wait_tech') return { cls: 'wait', label: '等技术面开口', why: d.why };
+ if (d.disp === 'wait_tech') return { cls: 'wait', label: '等待技术面转强', why: d.why };
// 盘中确认包 (2026-09-14): 开口未确认/时段外, 等盘中放量确认, 同样是「在盯」不是「被否决」。
if (d.disp === 'wait_confirm') return { cls: 'wait', label: '等盘中确认', why: d.why };
// 系统自决 (2026-09-14): 方向看多但择时看空, 进观察等择时转多, 也是「在盯」不是「被否决」。
- if (d.disp === 'wait_timing') return { cls: 'wait', label: '择时待转多', why: d.why };
+ if (d.disp === 'wait_timing') return { cls: 'wait', label: '等待择时转多', why: d.why };
// 系统自决今日已放弃 (2026-09-14): 明日重评, 与「未建仓」分开显示。
if (d.disp === 'sys_decline') return { cls: 'deny', label: '系统已放弃', why: d.why };
return { cls: 'deny', label: '未建仓', why: d.why };
}
- return { cls: 'wait', label: '候选观察', why: '' };
+ return { cls: 'wait', label: '待评估', why: '' };
}
// 候选行的三源合议摘要 (2026-09-14 页面收尾包): 后端候选处置快照 by_code 里挂的 consensus 小块
// (fund/overall/tech/phase/direction/route)。没有就返回 null, 芯片整段不显示。
+ function scanNoteText(note) {
+ const text = String(note || '');
+ const m = text.match(/^在途建仓已占 (\d+) 个名额、约 ([\d,]+) 元 \(\[.*\]\), 本轮按余量扫描$/);
+ if (m) return '待确认与未成交建仓占用 ' + m[1] + ' 个名额,约 ' + m[2] + ' 元。';
+ const labels = {'自主档位 off —— 本轮不扫描候选池 (off 是总闸)':'自主操作已暂停,候选暂不评估。',
+ '新建仓档位 off —— 不扫描候选池':'新建仓已暂停,候选暂不评估。',
+ '全局暂停执行 —— 本轮不扫描候选池':'执行已暂停,候选暂不评估。'};
+ return labels[text] || text;
+ }
+ const scanNotes = computed(() => [...new Set((openScan.value.notes || []).map(scanNoteText).filter(Boolean))]);
const conOf = (code) => ((openScan.value.by_code || {})[code] || {}).consensus || null;
const denyToday = computed(() => (planRows.value || []).map(r => {
const d = dispOf(r.ts_code);
@@ -3201,10 +3219,25 @@ createApp({
const amt = Number(h.target_amount), pct = Number(h.target_pct);
return (isFinite(amt) && isFinite(pct) && pct > 0) ? amt / pct : null;
}
+ function propAmount(p) {
+ const raw = (p.hard_numbers || {}).price, price = Number(raw), qty = Number(p.qty);
+ return raw != null && raw !== '' && Number.isFinite(price) && price > 0
+ && Number.isFinite(qty) && qty > 0 ? money(price * qty) + ' 元' : '—';
+ }
+ // PMS 的无偏移业务时刻约定为北京时间;带偏移的时间显式转换。
+ function propExpiry(value) {
+ if (!value) return '有效期未记录';
+ const raw = String(value), date = new Date(/(?:Z|[+-]\d{2}:?\d{2})$/i.test(raw) ? raw : raw.replace(' ','T') + '+08:00');
+ if (Number.isNaN(date.getTime())) return '有效期未记录';
+ const parts = Object.fromEntries(new Intl.DateTimeFormat('en-CA', {timeZone:'Asia/Shanghai',
+ year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit',hourCycle:'h23'}).formatToParts(date).map(x=>[x.type,x.value]));
+ const day = parts.year + '-' + parts.month + '-' + parts.day;
+ return (day === todayDateStr.value ? '今日' : day) + ' ' + parts.hour + ':' + parts.minute + ' 前有效';
+ }
function propSizePct(p) {
const h = p.hard_numbers || {};
const scale = propScale(p), price = Number(h.price), qty = Number(p.qty);
- if (!scale || !isFinite(price) || !isFinite(qty) || qty <= 0) return '';
+ if (!scale || !isFinite(price) || price <= 0 || !isFinite(qty) || qty <= 0) return '';
return (price * qty / scale * 100).toFixed(2) + '%';
}
// 建议档位的短版。原先卡上摆的是 advice_text 那一整句 (实测 90 多个字, 把档位、
@@ -3234,15 +3267,14 @@ createApp({
// 后端算不出新数量时会留一句原因(例如「一手就超过机械档,这只票对当前规模太贵」)。
// 有这句就直接说它 —— 比让人自己去对两个百分比清楚。
if (h.advice_resize_note) {
- return '注意:数量没能按建议档位重算(' + h.advice_resize_note + ')。采纳后下的就是上面那个数。';
+ return '数量未按建议仓位调整,采纳将按 ' + p.qty + ' 股执行。请查看数量说明。';
}
const a = h.advice;
if (!a || typeof a !== 'object') return '';
const advPct = Number(a.target_pct), usedPct = Number(h.target_pct);
if (!isFinite(advPct) || !isFinite(usedPct) || usedPct <= 0) return '';
if (Math.abs(advPct - usedPct) < 1e-9) return '';
- return '注意:上面那个股数是按 ' + (usedPct * 100).toFixed(1) + '% 那一档算的,'
- + '不是按建议档位的 ' + (advPct * 100).toFixed(1) + '%。采纳后下的就是上面那个数。';
+ return '数量按 ' + (usedPct * 100).toFixed(1) + '% 测算,建议为 ' + (advPct * 100).toFixed(1) + '%。';
}
// 数量按建议档位重算过就说一声。这是正面反馈:让人知道卡上写的档位与真下的股数
// 是对上的,不用自己再算一遍。抬到一手那种情形另有一句 note,一并带出来。
@@ -3424,7 +3456,7 @@ createApp({
const propDetailOpen = ref(false), propDetailKind = ref(''), propDetailP = ref(null);
const propTechFull = ref(null), propTechErr = ref('');
const PROP_SRC_LABEL = { plan: '选股系统', judge: '决策系统', company: '基本面',
- tech: '技术面', consensus: '择时' };
+ tech: '技术面', consensus: '择时', size: '数量说明' };
function openPropDetail(p, kind) {
propDetailP.value = p; propDetailKind.value = kind; propDetailOpen.value = true;
propTechFull.value = null; propTechErr.value = '';
@@ -4852,7 +4884,7 @@ createApp({
propVerdictLine, propLogic, propLogicMore, propText, propHeads,
hardCn, opCn, paramsCn, dropCn, plainCheck,
// 2026-09-10 提议卡重排新增
- propScale, propSizePct, propAdviceTier, propSizeMismatch, propResized, propJudgeShort,
+ propScale, propAmount, propExpiry, propSizePct, propAdviceTier, propSizeMismatch, propResized, propJudgeShort,
propMoreOpen, togglePropMore,
// 2026-09-12 提议卡重构: 来源结论标签 + 详情弹窗
cr, propPlanTag, propPlanSub, propJudgeTag, propCompanyTag, propTechTag, propTechSub,
@@ -4870,7 +4902,7 @@ createApp({
openScan, insTab, todayYmd, insLive, insDone, insEnd, dispOf, conOf, loadOpenScan, denyToday,
commandsShown, strategiesShown, instrLive, showAllCommands, showAllStrategies, showAllInstr,
cmdArchivable, stratArchivable, archiveRow,
- showOpLog, showSettings, watchCandidates, marketOpen, marketState, lastRefresh, autoRefresh,
+ showOpLog, showSettings, watchCandidates, scanNotes, marketOpen, marketState, lastRefresh, autoRefresh,
upstream, showUpstream, usrc, loadUpstream, srcFail,
csOpen, csCode, csLoading, csErr, csData, csFromSnap, csTimeline, csSrcFails,
csTruncated, csSrcLabel, csLedger, csLedgerErr, csHeld, csInPlan,