@@ -1802,18 +2065,18 @@ createApp({
};
// ===================== 交易员视图:翻译层 + 名称 + 动作 =====================
const T = {
- action: { OPEN:'建新仓', FILL:'回踩补足', ADD:'盈利加仓', DCA:'浮亏补仓', TRIM:'保垫减仓',
+ action: { OPEN:'建新仓', FILL:'回落补足', ADD:'盈利加仓', DCA:'浮亏补仓', TRIM:'保盈减仓',
EXIT:'清仓', T0:'做T', T0_ROUND:'做T', SWITCH:'换仓', HALT:'停手' },
side: { buy:'买入', sell:'卖出' },
- autonomy: { full:'全自动', propose_only:'只提议不下手', off:'暂停' },
+ autonomy: { full:'全自动', propose_only:'只提议,等你拍板', off:'暂停' },
cushion: { SOLID:'厚垫', THIN:'薄垫', NONE:'浮亏无垫' },
- lot: { BASE:'底仓', FILL:'回踩补足', ADD:'盈利加仓', DCA:'补仓', T0:'做T仓', RECON:'对账调整' },
- frozen: { NONE:'', COMMAND_HALT:'命令冻结(禁增持)', BRAKE:'刹车冻结', MANUAL:'手动冻结' },
+ lot: { BASE:'首批', FILL:'回落补足', ADD:'盈利加仓', DCA:'浮亏补仓', T0:'做T仓', RECON:'对账修正' },
+ frozen: { NONE:'', COMMAND_HALT:'命令要求只卖不买', BRAKE:'组合刹车,只卖不买', MANUAL:'你设的只卖不买' },
posStatus: { PLANNED:'计划建仓', OPENING:'建仓中', HOLDING:'持有中', EXITING:'清仓中', CLOSED:'已清仓' },
- insStatus: { PROPOSED:'待校验', RULE_PASSED:'合规已过', JUDGE_PASSED:'复核已过', DISPATCHED:'已下发挂单',
- CONFIRMED:'已成交', REJECTED:'被拒', EXPIRED:'到期作废', CANCELLED:'已撤销' },
+ insStatus: { PROPOSED:'等着校验', RULE_PASSED:'合规检查已过', JUDGE_PASSED:'决策系统复核已过', DISPATCHED:'已挂单,等成交',
+ CONFIRMED:'已成交', REJECTED:'被挡下', EXPIRED:'到期作废', CANCELLED:'已撤销' },
arbiter: { rule:'合规检查', judge:'决策系统', user:'你' },
- verdict: { PASS:'放行', REJECT:'驳回', NOTE:'记录', UNAVAILABLE:'决策系统暂不可用' },
+ verdict: { PASS:'放行', REJECT:'驳回', NOTE:'记一笔', UNAVAILABLE:'决策系统现在读不到' },
stype: { T0:'做T', GRID:'网格', TRAIL:'跟踪止盈' },
stratStatus: { ACTIVE:'运行中', PAUSED:'已暂停', CANCELLED:'已撤下', DONE:'已完成' },
};
@@ -1919,17 +2182,17 @@ createApp({
const attention = computed(() => {
const o = ov.value || {}, out = [];
- if (o.buy_halt) out.push('已暂停买入(卖出与止损不受影响)');
- if (o.exec_halt) out.push('休假模式:所有自动动作暂停,仅保留对账与日报');
- if (o.brake_until) out.push('组合刹车中(解除日 ' + o.brake_until + ')');
+ if (o.buy_halt) out.push('买入已经暂停,卖出和止损照常');
+ if (o.exec_halt) out.push('休假模式:系统的自动动作全停了,只留每天的对账和日报');
+ if (o.brake_until) out.push('组合刹车中,连续亏损后系统自己停了手,' + o.brake_until + ' 解除');
if (marketOpen.value && (o.price_missing || []).length)
- out.push(o.price_missing.length + ' 只取不到实时价(停牌或行情中断),其盈亏与安全垫暂不可用');
- if (o.sector_ready === false) out.push('行业数据未就绪,行业类操作暂不可用');
+ out.push(o.price_missing.length + ' 只取不到实时价(停牌或者行情断了),它们的盈亏和安全垫暂时算不出来');
+ if (o.sector_ready === false) out.push('行业数据还没就绪,按行业的操作暂时点不动');
const j = (dm.value || {}).judge;
- if (j && j.available === false) out.push('决策系统暂不可用:补足/加仓/补仓一律降级为需你确认');
+ if (j && j.available === false) out.push('决策系统现在读不到:回落补足、盈利加仓、浮亏补仓一律改成要你拍板');
const c = wsc.value || {};
- if (c.conn_state && c.conn_state !== 'ONLINE') out.push('行情/成交通道未连接(' + c.conn_state + ')');
- if (c.resync_required) out.push('通道待人工全量对账');
+ if (c.conn_state && c.conn_state !== 'ONLINE') out.push('行情和成交的通道没连上(' + c.conn_state + ')');
+ if (c.resync_required) out.push('通道要人工做一次全量对账');
return out;
});
@@ -1944,10 +2207,10 @@ createApp({
return bits.join(' · ') || '—';
}
function nextStep(r) {
- if (r.frozen_reason && r.frozen_reason !== 'NONE') return '已冻结,暂不增持';
- if (r.cushion_state === 'SOLID') return '安全垫较厚,达标可加仓';
- if ((r.cushion_pct || 0) < 0) return '浮亏中,跌到评估档会考虑补仓(深档需你确认)';
- return '持有观察';
+ if (r.frozen_reason && r.frozen_reason !== 'NONE') return '这只票现在只卖不买。';
+ if (r.cushion_state === 'SOLID') return '浮盈够厚,条件符合就会自动加仓。';
+ if ((r.cushion_pct || 0) < 0) return '还在亏。跌到设定的补仓线会考虑补仓;亏得更深的那一档要你点头才补。';
+ return '先拿着,观察。';
}
async function promptNum(msg, title) {
@@ -1966,7 +2229,7 @@ createApp({
let d = await call('post', '/api/commands', { cmd_type, params: params || {}, note: '交易员视图' });
if (!d.ok && (d.conflicts || []).length) {
try {
- await ElementPlus.ElMessageBox.confirm('与在途命令冲突,仍要下达?', '命令冲突', { type: 'warning' });
+ await ElementPlus.ElMessageBox.confirm('和还没做完的命令有冲突,还是要下达吗?', '命令冲突', { type: 'warning' });
d = await call('post', '/api/commands', { cmd_type, params: params || {}, note: '交易员视图', force: true });
} catch (e) { return; }
}
@@ -1974,7 +2237,7 @@ createApp({
else ElementPlus.ElMessage.error((d.errors || [d.error]).join('; '));
}
// 个股
- function actExitStock(row) { quickCmd('EXIT_STOCK', { ts_code: row.ts_code }, '清仓 ' + nm(row.ts_code) + '?系统会在窗口内择机把它卖光。'); }
+ function actExitStock(row) { quickCmd('EXIT_STOCK', { ts_code: row.ts_code }, '清仓 ' + nm(row.ts_code) + '?系统会在允许下单的时间段里挑时机把它卖光。'); }
async function actReduceStock(row) {
const curPct = row.pct_of_scale != null ? (row.pct_of_scale * 100).toFixed(1) + '%' : '—';
const v = await promptNum('当前 ' + row.total_qty + ' 股,市值 ' + money(row.market_value)
@@ -1983,7 +2246,7 @@ createApp({
}
async function actStop(row) {
const v = await promptNum('现价 ' + row.price + ',成本 ' + (row.avg_cost || '—')
- + '。止损价(元,跌破即挂卖)', '设止损 ' + nm(row.ts_code));
+ + '。跌破这个价就挂单卖出,填多少元?', '设止损 ' + nm(row.ts_code));
if (v != null) quickCmd('SET_STOP_PRICE', { ts_code: row.ts_code, price: v });
}
async function actTargetPrice(row) {
@@ -2008,7 +2271,7 @@ createApp({
function pResumeBuy() { quickCmd('RESUME_BUY', {}); }
function pHaltAll() { quickCmd('HALT_ALL', {}, '进入休假模式:所有自动动作暂停,只保留对账与日报?'); }
function pResumeAll() { quickCmd('RESUME_ALL', {}); }
- function pLiquidate() { quickCmd('LIQUIDATE_ALL', { confirm: 'YES' }, '【一键清仓】将把全部持仓按最快节奏卖出、不做择时优化。确认?'); }
+ function pLiquidate() { quickCmd('LIQUIDATE_ALL', { confirm: 'YES' }, '一键清仓:全部持仓按最快速度卖出,不再挑时间点。确定要这么做吗?'); }
async function pReduce() { const v = await promptNum('降低敞口(占总规模,%)', '降仓'); if (v != null) quickCmd('REDUCE_EXPOSURE', { pct: v + '%' }); }
async function pIncrease() { const v = await promptNum('提高敞口(占总规模,%)', '升仓'); if (v != null) quickCmd('INCREASE_EXPOSURE', { pct: v + '%' }); }
async function pSectorExit() {
@@ -2078,12 +2341,12 @@ createApp({
const fl = Math.abs(Number(pval('PMS_FILL_MAX_LOSS') || 0) * 100).toFixed(0) + '%';
return '开启,上限为底仓的 ' + pctOf(pval('PMS_DCA_MAX_RATIO')) + '。浮亏 ' + (trg || '—')
+ ' 各评估一次补仓;浮亏 ' + dc + ' 及更深的补仓永远需你确认;浮亏超过 ' + fl
- + ' 就不再回踩补足、转入补仓评估。';
+ + ' 就不再回落补足,转去评估要不要补仓。';
});
const sumAutonomy = computed(() => ({
- full: '全自动 —— 系统自主提议并直接执行。',
- propose_only: '只提议不下手 —— 系统提议,等你在「等我拍板」里确认。',
- off: '暂停 —— 系统不自主提议。'
+ full: '全自动 —— 系统自己提议,检查通过就直接下单。',
+ propose_only: '只提议 —— 系统给建议,等你在「等我拍板」里点头。',
+ off: '暂停 —— 系统不自己提议。'
}[pval('PMS_AUTONOMY')] || (pval('PMS_AUTONOMY') || '—')));
// ===================== /交易员视图 =====================
@@ -2205,13 +2468,13 @@ createApp({
// 改版二 (2026-08-18): 六个常用框固定成对常驻; 四个稀有/条件触发信号进"事件"区,
// 触发了出卡、没触发的收进一条待触发条。money_flow(资金流强度)频繁为空, 归入事件区。
const EVENT_SIGS = [
- { key: 'sell', label: '决策系统 风控卖出' }, { key: 'multi', label: '多源升级' },
- { key: 'timing', label: '盘中择时层 BUY 入场' }, { key: 'money_flow', label: '资金流强度' }];
+ { key: 'sell', label: '决策系统建议卖出' }, { key: 'multi', label: '多个来源同时报警' },
+ { key: 'timing', label: '买入时机' }, { key: 'money_flow', label: '资金流强度' }];
const eventsQuiet = computed(() => EVENT_SIGS.filter(e => !SIG.value[e.key].length));
// 告警固定分类格子: 6 类常驻 + 其他(catch-all), 永远这几个框、空的显示暂无 —— 不再随数据冒出冒消。
const ALERT_CATS_FIXED = [
- { key:'money_flow', label:'资金流强度' }, { key:'capital', label:'资金分布' }, { key:'qrs', label:'日QRS对称' },
- { key:'volume', label:'放量异动' }, { key:'multi', label:'多源升级' }, { key:'price_notice', label:'股价异动' }];
+ { key:'money_flow', label:'资金流强度' }, { key:'capital', label:'资金分布' }, { key:'qrs', label:'日线形态' },
+ { key:'volume', label:'放量异动' }, { key:'multi', label:'多个来源同时报警' }, { key:'price_notice', label:'股价异动' }];
const alertGridFixed = computed(() => {
const by = {};
for (const a of alertItemsF.value) { (by[a.cat] || (by[a.cat] = [])).push(a); }
@@ -2254,14 +2517,14 @@ createApp({
}
const s = SIG.value;
for (const r of (s.sell || [])) out.push({
- ts_code: r.ts_code, time: r.time || '', cat: '风控卖出',
+ ts_code: r.ts_code, time: r.time || '', cat: '建议卖出',
direction: '卖出', level: 'DANGER', held: held.has(code6(r.ts_code)) });
for (const r of (s.decision || [])) out.push({
ts_code: r.ts_code, time: r.time || '', cat: '决策系统',
direction: r.action === 'SELL' ? '卖出' : '买入', level: 'WARNING',
held: held.has(code6(r.ts_code)) });
for (const r of (s.timing || [])) out.push({
- ts_code: r.ts_code, time: r.time || '', cat: '择时入场',
+ ts_code: r.ts_code, time: r.time || '', cat: '买入时机',
direction: '买入', level: 'INFO', held: held.has(code6(r.ts_code)) });
out.sort((a, b) => (b.held - a.held) || String(b.time).localeCompare(String(a.time)));
return out;
@@ -2554,7 +2817,7 @@ createApp({
await Promise.all([loadStrategies(), loadOpLog()]);
}
async function resumeBuy(row) {
- try { await ElementPlus.ElMessageBox.confirm('恢复 ' + nm(row.ts_code) + ' 的策略买入?此前因决策系统风控预警自动暂停(卖出/平回一直正常)。', '确认', { type:'warning' }); }
+ try { await ElementPlus.ElMessageBox.confirm('恢复 ' + nm(row.ts_code) + ' 的策略买入?之前是决策系统的风控提示让它自动停买的,卖出、以及把当天做T买的那部分卖回去,一直都正常。', '确认', { type:'warning' }); }
catch (e) { return; }
const d = await call('post', '/api/strategies/' + row.strategy_id + '/resume-buy', {});
ElementPlus.ElMessage[(d.ok ? 'success' : 'error')](d.ok ? '已恢复买入' : (d.error || '失败'));
@@ -2629,6 +2892,179 @@ createApp({
}
onUnmounted(() => { if (refreshTimer) clearInterval(refreshTimer); });
onMounted(() => { loadAll(); loadPlan(); startAutoRefresh(); }); // 首屏 + 启动自动刷新
+ // ═══════════ 交易员工作台改版 (2026-08-26) ═══════════
+ // 一、融合消息栏: 把四类消息合成一条按时间倒序的流。
+ // 数据全部来自页面已经在拉的接口, 后端一个字都不用改。
+ const MSG_KINDS = [
+ { key:'all', label:'全部' },
+ { key:'alert', label:'行情告警' },
+ { key:'order', label:'系统下单' },
+ { key:'prop', label:'等我拍板' },
+ { key:'strat', label:'策略与风控' },
+ ];
+ const KIND_LABEL = { alert:'告警', order:'下单', prop:'待办', strat:'风控' };
+ const kindLabel = k => KIND_LABEL[k] || k;
+ const hm = v => { const m = String(v || '').match(/\d{2}:\d{2}/); return m ? m[0] : (v || ''); };
+ const msgOpen = ref(false), msgKind = ref('all');
+ // 时间字段各表叫法不一, 取到哪个算哪个; 排序用 HHMMSS, 同一天内够用。
+ const _at = (r, keys) => { for (const k of keys) { const v = r && r[k]; if (v) return String(v); } return ''; };
+ const _hm = s => { const m = String(s || '').match(/\d{2}:\d{2}/); return m ? m[0] : ''; };
+ const _hms = s => { const m = String(s || '').match(/(\d{2}):(\d{2})(?::(\d{2}))?/);
+ return m ? (m[1] + m[2] + (m[3] || '00')) : ''; };
+
+ function insWhy(r) {
+ const bits = [];
+ if (r.qty != null) bits.push('下单 ' + r.qty + ' 股');
+ if (r.exec_qty) bits.push('已成交 ' + r.exec_qty + ' 股');
+ if (r.limit_price) bits.push('限价 ' + r.limit_price);
+ if (r.reason) bits.push(r.reason);
+ return bits.join(',');
+ }
+
+ const msgFeed = computed(() => {
+ const out = [];
+ // 1) 行情告警 —— 跟着左栏的级别筛选走, 你关掉的档这里也不吵你
+ for (const a of alertItemsF.value) {
+ out.push({ kind:'alert', time:_hm(a.time), sort:_hms(a.time), ts_code:a.ts_code,
+ cat:(a.cat_label || a.cat), dir:a.direction, level:a.level,
+ stale:a.stale, age_min:a.age_min, why:'', goLabel:'看信号', go:'sig' });
+ }
+ // 2) 系统下单动作 —— 建仓 / 补足 / 加仓 / 补仓 / 减仓 / 清仓, 以及成交回报
+ for (const r of (instructions.value || [])) {
+ if (_isArch(r) || !_sameDayIns(r)) continue;
+ const t = _at(r, ['updated_at', 'created_at', 'at', 'dispatched_at']);
+ out.push({ kind:'order', time:_hm(t), sort:_hms(t), ts_code:r.ts_code,
+ cat:tx('action', r.action) + ' · ' + tx('insStatus', r.status),
+ strong:true, why:insWhy(r), goLabel:'看在办', go:'ins' });
+ }
+ // 3) 等我拍板 —— 自主档位是「只提议」时, 新提议在这里也提醒一次
+ for (const p of (proposals.value || [])) {
+ const t = _at(p, ['created_at', 'at', 'decided_at']);
+ out.push({ kind:'prop', time:_hm(t), sort:_hms(t) || '999999', ts_code:p.ts_code,
+ cat:tx('action', p.action) + ' · 等你拍板', strong:true,
+ why:(p.judge_reason || propWhy(p)), goLabel:'去处理', go:'prop' });
+ }
+ // 4) 策略与风控动向
+ const sc = (autoInfo.value || {}).last_scan;
+ if ((autoInfo.value || {}).ran_today && sc) {
+ const c = sc.counts || {};
+ out.push({ kind:'strat', time:_hm(sc.at), sort:_hms(sc.at), ts_code:'',
+ cat:'自动挂方案跑过一轮', strong:true,
+ why:'检查 ' + (sc.checked || 0) + ' 只,新挂 ' + (c.attached || 0)
+ + ',换方案 ' + (c.handoffs || 0) + ',停买 ' + (c.paused || 0)
+ + ',被挡 ' + (c.blocked || 0) + ',跳过 ' + (c.skipped || 0),
+ goLabel:'去我的策略', go:'strat' });
+ }
+ for (const s of (strategies.value || [])) {
+ if (!s.buy_paused) continue;
+ const t = _at(s.buy_paused, ['at', 'paused_at']) || _at(s, ['updated_at']);
+ out.push({ kind:'strat', time:_hm(t), sort:_hms(t), ts_code:s.ts_code,
+ cat:'这只票改成只卖不买', strong:true,
+ why:'决策系统提示' + (s.buy_paused.reason || '资金异动')
+ + ',已经停掉这个方案的买入。卖出、以及把当天做T买的那部分卖回去,都不受影响。',
+ goLabel:'去我的策略', go:'strat' });
+ }
+ for (const r of (SIG.value.sell || [])) {
+ out.push({ kind:'strat', time:_hm(r.time), sort:_hms(r.time), ts_code:r.ts_code,
+ cat:(r.dominant_signal === 'take_profit' ? '决策系统建议止盈' : '决策系统建议卖出')
+ + (r.confidence != null ? (',把握 ' + r.confidence + '(满分 1)') : ''),
+ strong:true, level:'DANGER', why:r.reason || '', goLabel:'去处理', go:'strat' });
+ }
+ for (const o of (opLog.value || [])) {
+ if (o.result === 'OK') continue; // 顺利做完的不占消息栏, 在「你做过的操作」里查
+ out.push({ kind:'strat', time:_hm(o.at), sort:_hms(o.at), ts_code:o.ts_code || '',
+ cat:o.op + ' ' + (o.result === 'BLOCKED' ? '被合规检查挡下' : ('结果 ' + o.result)),
+ strong:true, why:o.reason || '', goLabel:'看记录', go:'ins' });
+ }
+ out.sort((a, b) => String(b.sort).localeCompare(String(a.sort)));
+ return out;
+ });
+ const msgCounts = computed(() => {
+ const c = { all:msgFeed.value.length, alert:0, order:0, prop:0, strat:0 };
+ for (const m of msgFeed.value) c[m.kind] = (c[m.kind] || 0) + 1;
+ return c;
+ });
+ const msgShown = computed(() =>
+ msgKind.value === 'all' ? msgFeed.value : msgFeed.value.filter(m => m.kind === msgKind.value));
+ const msgLatest = computed(() => msgShown.value[0] || null);
+ // 消息栏左边那道竖线的颜色: 有风控类的用橙, 有重要或警告的用琥珀, 否则中性。
+ const msgLevelClass = computed(() => {
+ const it = msgShown.value;
+ if (it.some(m => m.kind === 'strat')) return 'stop';
+ if (it.some(m => ['DANGER', 'IMPORTANT', 'WARNING'].includes(String(m.level || '')))) return 'warn';
+ return it.length ? '' : 'none';
+ });
+ function setMsgKind(k) { msgKind.value = k; msgOpen.value = true; }
+ function msgGo(m) {
+ if (m.go === 'sig') { sigTab.value = 'brief'; openRailSide('l'); }
+ else if (m.go === 'ins') { msgOpen.value = false; scrollTo('t-ins'); }
+ else if (m.go === 'prop') { sectClosed.prop = false; openRailSide('r'); }
+ else if (m.go === 'strat') { sectClosed.strat = false; openRailSide('r'); }
+ }
+
+ // 二、左栏信号流: 速览分「持仓的票」和「没持仓的票」两段, 持仓的置顶。
+ const sigTab = ref('brief');
+ const srcOpen = reactive({ metrics:true, mr:false, decision:false, qrs:false,
+ capital:false, price:false, rare:false });
+ function toggleSrc(k) { srcOpen[k] = !srcOpen[k]; }
+ const sigBriefHeld = computed(() => sigBrief.value.filter(x => x.held));
+ const sigBriefOther = computed(() => sigBrief.value.filter(x => !x.held));
+
+ // 三、左右两栏: 宽度够就常驻, 不够就收成窄条; 窄条点开是浮层。
+ // 临界值按「主区要放得下持仓表」倒推 —— 表最小 940 加面板内边距 32 等于 972:
+ // 两栏都常驻 976+302+334+26+32 = 1670 → 取 1680 (实测 1660 差 10 像素)
+ // 只常驻左栏 976+302+ 40+26+32 = 1376 → 取 1380
+ const W_DOCK_BOTH = 1680, W_DOCK_LEFT = 1380;
+ const RAIL_STORE_KEY = 'pms_rail_fold';
+ const _railLoad = () => { try { const v = JSON.parse(sessionStorage.getItem(RAIL_STORE_KEY));
+ if (v && typeof v === 'object') return v; } catch (e) {} return { l:false, r:false }; };
+ const _rf = _railLoad();
+ const lFold = ref(!!_rf.l), rFold = ref(!!_rf.r);
+ const railOpen = ref(null); // null / 'l' / 'r'
+ const sectClosed = reactive({ prop:false, strat:false, pool:false, ops:false, set:true });
+ function toggleSect(k) { sectClosed[k] = !sectClosed[k]; }
+ function _railSave() {
+ try { sessionStorage.setItem(RAIL_STORE_KEY, JSON.stringify({ l:lFold.value, r:rFold.value })); } catch (e) {}
+ }
+ function _railClasses() {
+ document.body.classList.toggle('l-fold', lFold.value);
+ document.body.classList.toggle('r-fold', rFold.value);
+ }
+ function applyDock() {
+ const w = window.innerWidth;
+ document.body.classList.toggle('dock-l', w >= W_DOCK_LEFT);
+ document.body.classList.toggle('dock-r', w >= W_DOCK_BOTH);
+ // 这一侧已经能常驻了, 就不该还浮在上面
+ if (railOpen.value === 'l' && w >= W_DOCK_LEFT && !lFold.value) railOpen.value = null;
+ if (railOpen.value === 'r' && w >= W_DOCK_BOTH && !rFold.value) railOpen.value = null;
+ }
+ function foldRail(side) {
+ if (side === 'l') lFold.value = true; else rFold.value = true;
+ _railClasses(); _railSave();
+ }
+ function closeRail() { railOpen.value = null; }
+ function openRailSide(side, forcePull) {
+ const canDock = document.body.classList.contains(side === 'l' ? 'dock-l' : 'dock-r');
+ const folded = side === 'l' ? lFold.value : rFold.value;
+ // 宽度够、只是手动收起来了: 点一下直接展回常驻, 不用浮层
+ if (!forcePull && canDock && folded) {
+ if (side === 'l') lFold.value = false; else rFold.value = false;
+ _railClasses(); _railSave(); return;
+ }
+ if (!forcePull && canDock && !folded) return; // 已经常驻着, 不用再浮一层
+ railOpen.value = side;
+ }
+ function _onEsc(e) { if (e.key === 'Escape') closeRail(); }
+ onMounted(() => {
+ _railClasses(); applyDock();
+ window.addEventListener('resize', applyDock);
+ document.addEventListener('keydown', _onEsc);
+ });
+ onUnmounted(() => {
+ window.removeEventListener('resize', applyDock);
+ document.removeEventListener('keydown', _onEsc);
+ });
+
return { tab, loading, err, health, ov, params, catalog, commands, plans, plansOf,
positions, lots, lotsOf, instructions, ledger, proposals, report, reportDrawer,
opsDrawer, opsResult, opsLoading, issuing, form, curSpec, dirtyCount, dm,
@@ -2662,7 +3098,12 @@ createApp({
alertStripCount, alertTotalCount, catShown, toggleCat, ageText, alertRowClass,
alertItemsF, levelShown, toggleLevel, sellOpen, toggleSell, levelCount, ALERT_LEVELS, lvLabel, lvClass, alertGridFixed, passLevel, flowText, flowClass,
sigBrief, sigBriefHeldCount, heldSigOf, openSigCenter,
- SIG, moneyCn, zBand, zBars, dirUp, eventsQuiet };
+ SIG, moneyCn, zBand, zBars, dirUp, eventsQuiet,
+ MSG_KINDS, kindLabel, hm, msgOpen, msgKind, msgFeed, msgCounts, msgShown,
+ msgLatest, msgLevelClass, setMsgKind, msgGo,
+ sigTab, srcOpen, toggleSrc, sigBriefHeld, sigBriefOther,
+ lFold, rFold, railOpen, foldRail, closeRail, openRailSide,
+ sectClosed, toggleSect };
}
}).use(ElementPlus).mount('#app');
diff --git a/app/web/static/index.html.bak-20260826 b/app/web/static/index.html.bak-20260826
new file mode 100644
index 0000000..dcc6f5f
--- /dev/null
+++ b/app/web/static/index.html.bak-20260826
@@ -0,0 +1,2670 @@
+
+
+
+
+
+
PMS 持仓管理系统
+
+
+
+
+
+
+
+
+
+
+
PMS 持仓管理v{{ health.version || '-' }} · {{ health.now || '' }}
+
{{ marketState }}
+
买入 {{ ov.buy_halt ? '已暂停':'正常' }}
+
执行 {{ ov.exec_halt ? '已暂停':'正常' }}
+
自主 {{ tx('autonomy', ov.autonomy) }}?
+
计划 {{ planStatus.ok===false ? '不可用' : (planStatus.date || '-') }}
+
+
+
+
宏观择时未启用总开关 PMS_MACRO_ENABLED 在「参数设置」里打开
+
宏观择时:暂无信号数据(还没扫描过;调度位在交易日 09:35)
+
+ {{ mzSig.label }}
+ {{ mzSig.value==null ? '—' : (mzSig.value>0?'+':'') + Number(mzSig.value).toFixed(1) }}
+ {{ mzLabel(mzSig.zone) }}
+ {{ mzSig.ymd }}
+ 超额深度 {{ mzSig.e_now }} · 区内第 {{ mzSig.streak }} 天
+ {{ mzSig.note }}
+
+
+
+ 近 {{ (mzSig.history||[]).length }} 个交易日 · 热 +{{ (macro.params||{}).hot_th }} / 冷 {{ (macro.params||{}).cold_th }} / 退出带 ±{{ (macro.params||{}).exit_band }} · 档位 {{ macro.autonomy==='full' ? '全自动' : (macro.autonomy==='propose_only' ? '只出建议' : '只算不动') }}
+ 偏热闸生效中:系统自主买入与策略买开腿已暂停(你的命令不受影响)
+
+
今日建议:{{ mzSig.advice.direction==='REDUCE' ? '降仓' : '升仓' }} {{ (mzSig.advice.pct*100).toFixed(1) }}%
+
+
{{ mzSig.advice.why }} · 当日有效,隔日作废
+
+ 最近宏观命令 {{ mzSig.last_command.command_id }} · {{ mzSig.last_command.cmd_type==='REDUCE_EXPOSURE' ? '降仓' : '升仓' }} · {{ mzSig.last_command.status }}
+
+
+
+
+ 读取失败: {{ mzSig.error }}
+
+
+
+ 宏观 {{ mzChipText }}
+
+
下发 {{ dm.mode || '-' }}?
+
+
+
+
+
+
+
+
+
+
{{ autoRefresh ? '自动' : '手动' }} · {{ lastRefresh }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
最新告警
+
+ {{ nm(alertLatest.ts_code) }} {{ alertLatest.ts_code }}
+ {{ alertLatest.direction }}
+ {{ alertLatest.cat_label }}
+ {{ lvLabel(alertLatest.level) }}
+ {{ alertLatest.time }} · {{ ageText(alertLatest) }}
+ 陈旧
+
+
· 读取失败:{{ usrc('alerts').error }}
+
· 暂无告警
+
+
完整信号中心 ▾
+
+
+
+
+
+
+
账户总资产
{{ money(ov.total_asset) }}
+
可用资金 (估算)
+
{{ money(ov.cash_source==='ws' ? ov.cash_avail : ov.cash_est) }}
+
持仓市值
{{ money(ov.portfolio_mv) }}
+
仓位
{{ pct(ov.portfolio_pct) }} / {{ pct(ov.portfolio_cap) }}
+
浮动盈亏
+
{{ money(ov.float_pnl) }} {{ ov.float_pnl_pct==null?'':pct(ov.float_pnl_pct) }}
+
持仓数
{{ ov.names_count }} / {{ ov.max_names }}
+
等我拍板
+
{{ proposals.length }} 条
+
自主档位
{{ tx('autonomy', ov.autonomy) }}
+
+
颜色约定?
+
+
+
+
+
+
等我拍板 · {{ proposals.length }} 条
+
暂无需要你确认的提议。
+
+
+
{{ nm(p.ts_code) }} {{ p.ts_code }}
+ {{ tx('action', p.action) }}
+
{{ p.expire_at }} 前有效
+
+
决策系统:{{ p.judge_reason }}
+
{{ propWhy(p) }}
+
+
约 {{ p.qty }} 股 · 参考价 {{ (p.hard_numbers||{}).price==null ? '—' : (p.hard_numbers||{}).price }}
+ · 约需 {{ money(((p.hard_numbers||{}).price||0)*(p.qty||0)) }}
+
+
采纳
+
驳回
+
+
采纳=记一笔待执行指令,由择时在窗口内择机下单;驳回=今日不再提这只。
+
+
+
+
+
+
我的持仓 · {{ heldPositions.length }} 只
+
+
+
+
+
+
持仓批次
+
+ {{ tx('lot', l.row.lot_type) }}
+
+
+
+
+
+
无持仓批次
+
载入中…
+
+
+
该股账本
+
今日暂无记录
+
+ {{ (l.decided_at||'').slice(5,16) }}
+ {{ tx('arbiter', l.arbiter) }}{{ tx('verdict', l.verdict) }} {{ tx('action', l.action) }} — {{ l.reason }}
+
+
+
+
+
+
+ {{ nm(s.row.ts_code) }}
{{ s.row.ts_code }}
+
+
+ {{ s.row.price }} 估
{{ s.row.avg_cost||'—' }}
+
+
+
+ {{ pct(s.row.price/s.row.avg_cost-1) }}
+ —
+
+
+
+ {{ money(s.row.market_value) }}
{{ pct(s.row.pct_of_scale) }}
+
+
+ {{ tx('cushion', s.row.cushion_state) }}
+ {{ pct(s.row.cushion_pct) }} · 连负{{ s.row.neg_cushion_days }}日
+
+
+
+
+
+
+ {{ heldSigOf(s.row.ts_code)[0].cat }} {{ heldSigOf(s.row.ts_code)[0].direction || '' }}
+ {{ heldSigOf(s.row.ts_code)[0].time }}
+
+ +{{ heldSigOf(s.row.ts_code).length-1 }}
+
+
+ —
+
+
+
+ {{ tx('posStatus', s.row.status) }}
+ {{ tx('frozen', s.row.frozen_reason) }}
+
+ {{ nextStep(s.row) }}
+
+
+ 减仓
+ 清仓
+ rowMore(c,s.row)">
+ 更多 ▾
+
+
+ 设止损价
+ 设目标价
+ {{ (s.row.frozen_reason && s.row.frozen_reason!=='NONE') ? '解冻' : '冻结(禁增持)' }}
+ 加入黑名单
+ 挂交易方案(做T/网格/跟踪止盈)
+
+
+
+
+
+
+
+
+
+
信号速览 · {{ sigBrief.length }} 条,持仓相关 {{ sigBriefHeldCount }} 条
+ 完整信号中心 ▾
+
持仓票今日暂无信号,全场暂无重要或警告级信号。
+
+
+ {{ g.time || '—' }}
+ 持仓
+ {{ nm(g.ts_code) }}{{ g.ts_code }}
+ {{ g.cat }}
+ {{ g.direction || '' }}
+ {{ lvLabel(g.level) }}
+ 陈旧
+
+
+
+
+
+
我的策略 · {{ strategies.length }} 个
+
+ {{ stratEnabled ? '策略层已启用' : '策略层总开关未开(PMS_STRATEGY_ENABLED)' }}
+ 显示已撤下/已完成
+
+ {{ autoInfo.enabled ? '自动挂载已开' : '自动挂载总开关未开' }}
+
+ 预演
+ ?
+
+
+
+
+ 今日自动挂载已于 {{ (autoInfo.last_scan.at||'').slice(11,16) }} 执行:查
+ {{ autoInfo.last_scan.checked }} 只 · 挂载 {{ ((autoInfo.last_scan.counts||{}).attached)||0 }} ·
+ 接力 {{ ((autoInfo.last_scan.counts||{}).handoffs)||0 }} · 停买 {{ ((autoInfo.last_scan.counts||{}).paused)||0 }} ·
+ 被挡 {{ ((autoInfo.last_scan.counts||{}).blocked)||0 }} · 跳过 {{ ((autoInfo.last_scan.counts||{}).skipped)||0 }}
+
+
+ 今日自动挂载未执行(交易日 09:40 定时运行)
+
+
+
+
+
+
+ 预演结果(未执行)· 查 {{ autoScanRes.checked }} 只持仓
+
+
+
+ {{ x.stage }}
+ {{ nm(x.ts_code) }}
+ {{ x.why }}
+ · 热度 {{ Number(x.heat).toFixed(2) }}
+ · 浮盈 {{ pct(x.cushion_pct) }}
+ · 定性「{{ x.accum_state }}」
+
+
+
+
+ 想挂
+ {{ nm(x.ts_code) }} {{ tx('stype', x.type) }} —— {{ x.why }}
+
+ 想换挂
+ {{ nm(x.ts_code) }} 网格换跟踪止盈 —— {{ x.why }}
+
+ 想停买入
+ {{ nm(x.ts_code) }} —— {{ x.why }}
+
+ 想恢复买入
+ {{ nm(x.ts_code) }}
+
+ 被挡
+ {{ nm(x.ts_code) }} —— {{ x.why }}
+
+ · {{ nm(x.ts_code) }}:{{ x.why }}
+
✗ {{ e }}
+
+ {{ autoScanRes.unknown_states.length }} 只票的吸筹定性超出约定词表(已按无标志处理),
+ 样本:{{ autoScanRes.unknown_states.slice(0,3).map(u=>u.state).join('、') }}。需与决策系统核对
+
+
+ 本轮无动作:无符合触发条件的信号
+
+
+
尚未挂载交易方案。可在「我的持仓」行内「更多 ▾ → 挂交易方案」创建。
+
+ {{ nm(s.row.ts_code) }} {{ s.row.ts_code }}
+ {{ tx('stype', s.row.type) }}
+ {{ s.row.autonomy==='auto'?'自动执行':'待我拍板' }}
+ {{ stratStateText(s.row) }}
+ 买入已暂停
+ 决策系统风控:{{ s.row.buy_paused.reason || '资金异动' }}(卖出/平回不受影响;确认无碍点右侧「恢复买入」)
+
+ {{ tx('stratStatus', s.row.status) }}
+
+ 暂停
+ 恢复
+ 恢复买入
+ 撤下
+ 移除
+ 恢复显示
+
+
+
+
操作日志 · {{ opLog.length }} 条 {{ showOpLog?'▲ 收起':'▼ 展开' }}
+
+ 每次操作留痕一行;被约束挡下的操作记录原因。
+ 还没有操作记录。
+
+ {{ (o.at||'').slice(11,16) }}
+ {{ o.result }}
+ {{ o.op }} · {{ nm(o.ts_code) }}
+ — {{ o.reason }}
+
+
+
+
+
+
+
今日在办
+
今天下出去的买卖单,按状态分三档:还在跑的、已成交的、已经结束的。
+
+
+ 在办中 {{ insLive.length }}
+
+ {{ nm(s.row.ts_code) }} {{ s.row.ts_code }}
+ {{ tx('action', s.row.action) }} · {{ tx('side', s.row.side) }}
+ {{ s.row.qty }} / {{ s.row.exec_qty||0 }}
+ {{ s.row.limit_price||'—' }}
+ {{ tx('insStatus', s.row.status) }}
+
+
+
+
+ 今日成交 {{ insDone.length }}
+
+ {{ nm(s.row.ts_code) }} {{ s.row.ts_code }}
+ {{ tx('action', s.row.action) }} · {{ tx('side', s.row.side) }}
+ {{ s.row.qty }} / {{ s.row.exec_qty||0 }}
+ {{ tx('insStatus', s.row.status) }}
+
+
+
+ 已了结 {{ insEnd.length }}
+
+ {{ nm(s.row.ts_code) }} {{ s.row.ts_code }}
+ {{ tx('action', s.row.action) }} · {{ tx('side', s.row.side) }}
+ {{ tx('insStatus', s.row.status) }}
+
+
+
+ 今日否决 {{ denyToday.length }}
+ 今天考察过、但没建仓的票,连原因一起摆出来。
+
+ {{ s.row.name }} {{ s.row.ts_code }}
+ {{ s.row.why || '—' }}
+
+
+
+
今天发生了什么 · {{ ledger.length }} 条 {{ showLedger?'▲ 收起':'▼ 展开' }}
+
+ 今天还没有记录。
+
+ {{ (l.decided_at||'').slice(11,16) }}
+ {{ nm(l.ts_code) }}:{{ tx('arbiter', l.arbiter) }}{{ tx('verdict', l.verdict) }} {{ tx('action', l.action) }} —— {{ l.reason }}
+
+
+
+
+
+
+
上游信号 · 只读参考,不进下单逻辑 {{ showUpstream?'▲ 收起':'▼ 展开' }}
+
+ 最近取数 {{ (upstream.at||'').slice(11,19) }} · 来源:决策系统 / 盘中择时
+
+
+ 级别筛选
+ {{ L.label }} {{ levelCount(L.key) }}
+ ?
+
+
+
+
+
+
mtf 资金异动 · {{ SIG.metrics.length }} · 读取失败
+
+
+ {{ nm(r.ts_code) }}{{ r.ts_code }}
+ {{ flowText(r.direction) }}
+
+ {{ moneyCn(r.window_net) }}
+ {{ zBand(r.z_dd).label }}
+
+
暂无资金异动
+
+
强度=横截面异动 z_dd:显著(2–3) · 强(3–5) · 极强(≥5)。悬停看原始 z。
+
+
+
mtf 实况分 · 关注 / 回避榜 · 读取失败
+
+
+
关注(高分)
+
{{ nm(r.ts_code) }} {{ r.ts_code }}{{ r.score }}
+
暂无
+
+
+
回避(低分)
+
{{ nm(r.ts_code) }} {{ r.ts_code }}{{ r.score }}
+
暂无
+
+
+
+
+
+
+
+
+
决策系统 买/卖 · {{ SIG.decision.length }} · 读取失败
+
+
+ {{ nm(r.ts_code) }}{{ r.ts_code }}
+
+ {{ r.action }} · {{ r.signal_type }}
+ {{ r.time }}
+
+
暂无买卖广播
+
+
+
+
日QRS对称 · {{ SIG.qrs.length }}
+
+
+ {{ nm(r.ts_code) }}{{ r.ts_code }}
+ {{ r.direction }}
+
+ {{ lvLabel(r.level) }}
+ {{ r.time }}{{ ageText(r) }}
+
+
暂无 QRS 告警
+
+
+
+
+
+
+
+
资金分布 · {{ SIG.capital.length }}
+
+
+ {{ nm(r.ts_code) }}{{ r.ts_code }}
+ {{ r.direction }}
+
+ {{ lvLabel(r.level) }}
+ {{ r.time }}{{ ageText(r) }}
+
+
暂无资金分布告警
+
+
+
+
股价异动 · {{ SIG.price_notice.length }}
+
+
+ {{ nm(r.ts_code) }}{{ r.ts_code }}
+ {{ r.direction }}
+
+ {{ lvLabel(r.level) }}
+ {{ r.time }}{{ ageText(r) }}
+
+
暂无股价异动
+
+
+
+
+
+
+
+
决策系统 · 风控卖出 · {{ SIG.sell.length }}
+
+
+
{{ sellOpen[r.ts_code+'_'+i] ? '▾' : '▸' }}
+
{{ nm(r.ts_code) }}{{ r.ts_code }}
+
{{ r.dominant_signal==='take_profit'?'止盈':'风控卖出' }}
+
置信 {{ r.confidence }}
+
+
{{ r.time }}
+
{{ r.reason }}
+
+
+
+
+
多源升级 · {{ SIG.multi.length }}
+
+
+ {{ nm(r.ts_code) }}
+ {{ r.direction }}
+
+ {{ lvLabel(r.level) }}
+ {{ r.time }}
+
+
+
+
+
盘中择时层 · BUY 入场 · {{ SIG.timing.length }}
+
+
+ {{ nm(r.ts_code) }}
+
+ 参考价 {{ r.price==null?'—':r.price }}
+ 置信 {{ r.confidence==null?'—':r.confidence }}
+ {{ r.time }}
+
+
+
+
+
资金流强度 · {{ SIG.money_flow.length }}
+
+
+ {{ nm(r.ts_code) }}
+ {{ r.direction }}
+
+ {{ lvLabel(r.level) }}
+ {{ r.time }}
+
+
+
+
+
+
+
+ 在用 · 今日待触发:
+ {{ e.label }}
+ 其他/未登记源 · {{ SIG.other.length }}
+
+
+
+
+
+
+
+
+ 现价 {{ stratDlg.price }} · 持仓 {{ stratDlg.total_qty }} 股(T+1 可卖 {{ stratDlg.avail_qty }})· 浮盈 {{ pct(stratDlg.cushion_pct) }}。参数按这些现值给了默认,不用盲填。
+
+
+
+
+ 做T
+ 网格
+ 跟踪止盈
+
+
+
+
+
+ ×持仓
+ ≤ 1/3;每轮约 {{ stratQty }} 股
+
+
+ 回落近支撑→买、按目标价差高卖(正T);近压力或滞涨→卖存量、低买回(反T)。当日 ≤ 3 次,14:50 强制平回,单票 / 全局当日T亏熔断。
+
+
+
+
+
+
+ ~
+
+
+
+
+ 默认取现价
+
+
+ ×价
+ 0.02=每 2% 一档
+
+
+ 股
+
+
+ 元
+ 须落在单股上限内(合规检查兜底)
+
+
+ 跌破未买档→买一份,涨破已买档→卖一份(从 T+1 可卖存量);跌破下界=继续持有、不再买。
+
+
+
+
+
+ 浮盈
+ 浮盈达此才开始跟踪
+
+
+ 自高点
+ 0.05=从高点回落 5% 就卖
+
+
+ ×可卖
+ 1=全清
+
+
+ 浮盈
+ 0=不设;达此直接全清
+
+
+ 创新高抬止盈线,从高点回落到设定比例就卖(只卖不买,纯离场保护)。
+
+
+
+
+
+ 自动执行
+ 待我拍板
+
+
+
+
+
+ · {{ r }}
+
+
+
+ 先校验
+ 挂载并生效
+
+
+
+
+
+
组合操作
+
对整个组合的手动动作。数值都基于当前实际持仓算出、并预览影响金额,不用盲填;危险动作要二次确认。
+
+ {{ ov.buy_halt?'恢复买入':'暂停买入' }}
+ 停止一切新增买入并撤销在途买入指令;卖出与止损不受影响。当前:{{ ov.buy_halt?'已暂停买入':'买入正常' }}。
+
+
+ {{ ov.exec_halt?'结束休假':'休假模式' }}
+ 所有自动动作暂停,只保留对账与日报。当前:{{ ov.exec_halt?'休假中':'正常运行' }}。
+
+
+ 调整仓位
+
+ 当前总仓位 {{ pct(ov.portfolio_pct) }}(市值 {{ money(ov.portfolio_mv) }} / 规模 {{ money(ov.scale) }}),可用资金 {{ money(ov.cash_source==='ws'?ov.cash_avail:ov.cash_est) }}。
+ 目标 %
+ {{ posMovePreview }}
+
+
+
+ 清仓行业
+
+ 卖光某个行业的全部持仓,从下面选(不用手打):
+
+
+
+ —— {{ ov.sector_ready===false?'行业数据未就绪':'当前无带行业的持仓' }}
+
+
+
+ 一键清仓(紧急)
+ 全部持仓({{ heldPositions.length }} 只,市值 {{ money(ov.portfolio_mv) }})按最快节奏卖出、不做择时优化。紧急离场用,需二次确认。
+
+
+
+
设置 {{ showSettings?'▲ 收起':'▼ 展开' }}
+
+ 每组先给一句「现在是什么状态」的合成说明,再让你改其中的项。改完点保存即时生效。
+
+
资金与仓位纪律:{{ sumPosition }}
+
+ 总操作规模 元
+ 总仓上限
+ 单股上限
+ 最多持仓 只
+ 预留现金
+
+
+
+
补仓策略:{{ sumDca }}
+
+ 是否补仓
+
+ 上限(占底仓)
+ 评估档(浮亏)
+ 深档确认
+ 回踩止损
+
+
+
+
+
自主档位:{{ sumAutonomy }}
+
+
+
+
+
+
+
+
+
+ 保存修改 ({{ dirtyCount }})
+ 放弃
+ 关闭补仓=把「补仓上限」设为 0;调低总仓上限可能触发降仓提议。
+
+
+
+
+
+
+
系统在盯的候选 · 今日选股计划 · 计划日 {{ planStatus.date || '—' }}
+
系统还在盯、可能建仓的票。已下单 / 待你确认 / 被否决的,见上方「今日在办」与「等我拍板」。
+
{{ n }};
+
上游计划暂不可用。
+
+ {{ s.row.rank }}
+ {{ s.row.name || nm(s.row.ts_code) }} {{ s.row.ts_code }}
+ {{ s.row.theme }}—
+ {{ s.row.tier || '—' }}
+
+ ● {{ dispOf(s.row.ts_code).label }}
+ {{ dispOf(s.row.ts_code).why }}
+
+
+
+
+
+
+
+
+
+
组合市值
{{ money(ov.portfolio_mv) }}
+
总仓位
+
{{ pct(ov.portfolio_pct) }} / {{ pct(ov.portfolio_cap) }}
+
可用额度
{{ money(ov.cap_room) }}
+
账户可用 (估算)
+
{{ money(ov.cash_source==='ws' ? ov.cash_avail : ov.cash_est) }}
+
持仓数
+
{{ ov.names_count }} / {{ ov.max_names }}
+
浮动盈亏
+
{{ money(ov.float_pnl) }}
+
厚垫 / 负垫
+
{{ ov.solid_names }} / {{ ov.neg_names }}
+
+
+
+
+
+
+
+
页面改动持久化到 pms_runtime_param, 优先于 settings.py 初值; 基础设施连接串只在 .env 维护。
+
+
+ 保存修改 ({{ dirtyCount }})
+
放弃修改
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ s.row.source==='table' ? '页面' : '文件' }}
+
+
+
+
+
+
+
+
+
+
+
下达命令
+
+
+
+
+ {{ c.label }}
+ {{ c.cls==='param'?'参数':'任务' }}
+
+
+
+
+ {{ curSpec.cls==='param' ? '参数命令 (立即生效)' : '任务命令 (生成方案)' }}
+ 高危
+
+
{{ curSpec.note }}
+
+
+ {{ fieldLabel(name) }}
+
+
+
+
+ %
+ 元
+ 元
+
+
+
+
下达
+
+
+ 任务命令下达后立即生成方案并落 pms_plan; 指令下发由择时执行器负责 (下一批交付)。
+
+
+
+
+
在途命令与进度
+ 显示已完成/已移除
+
+
+
+
+
+
参数: {{ JSON.stringify(s.row.params) }}
+
+ 说明: {{ (s.row.progress.notes||[]).join(' | ') }}
+
+ 查看方案明细
+
+
+
+
+
+ {{ money(p.row.amount) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ s.row.status }}
+
+
+
+
+
+
+ {{ money(s.row.progress.done_amount) }} /
+ {{ money(s.row.progress.target_amount) }}
+ · 缺口 {{ money(s.row.progress.gap) }}
+
+
+ {{ (s.row.progress||{}).plan_count || 0 }} 条方案
+
+
+
+
+
+ 撤销
+ 重规划
+ 移除
+ 恢复显示
+
+
+
+
+
+
+
+
+
+
持仓总览 ({{ positions.length }} 只)
+
+
+
+
+ 查看批次
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ s.row.frozen_reason }}
+ —
+
+
+
+
+
+ {{ s.row.base_qty }}/{{ s.row.fill_qty }}/{{ s.row.add_qty }}/{{ s.row.dca_qty }}/{{ s.row.t0_qty }}
+
+
+
+
+
+
+ {{ pct(s.row.cushion_pct) }} {{ s.row.cushion_state }}
+ 连负{{ s.row.neg_cushion_days }}日
+
+
+
+ {{ pct(s.row.pct_of_scale) }}
+
+ {{ money(s.row.market_value) }}
+
+ {{ s.row.support_ref||'-' }}/{{ s.row.pressure_ref||'-' }}/{{ s.row.stop_ref||'-' }}
+ {{ s.row.ref_source||'' }}
+
+
+
+
+ {{ pct(s.row.t0_ratio) }}—
+
+
+
+
+
+
+
在途指令
+ 显示已了结
+
+
+
+
+
+
+ 最近判定 [{{ s.row.progress.last_decision.at }}]
+ {{ s.row.progress.last_decision.action }} —
+ {{ s.row.progress.last_decision.reason }}
+
+
+ 执行窗口截止: {{ s.row.progress.deadline }}
+
+ · {{ s.row.progress.window_verdict.note }}
+
+
+
+
+
+
+
+
+ {{ c.row.forced ? '是' : '—' }}
+
+
+
+
尚无出手记录
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 撤销
+
+
+
+
+ {{ dm.hint }} 展开可看每日子单与择时依据; 成交量以下游回放为准, 不拿下发量当成交量。
+
+
+
+
+
评审账本 (规则闸 / 研判闸 / 人工裁决全量留痕)
+
+
+
+
+
+
+
+
+ {{ s.row.verdict }}
+
+
+
+ {{ (s.row.failed_checks||[]).join(' | ') }}
+
+
+
+
+
+
+
+
+
+ 提议确认
+
+
+
+
待确认队列 (自主档位 {{ ov.autonomy }})
+
+ propose_only 档位下, 增持类自主动作全部在此等待裁决; 减持方向不设确认门槛。
+ 采纳后先落指令表 (先记账后动作), 由择时执行器分日出手。
+
+
+
+
+
+
+
+
+ {{ JSON.stringify(s.row.hard_numbers) }}
+
+
+
+
+
+ 采纳
+ 驳回
+
+
+
+
+
+
+
+
+
+
+ 进程 {{ wsc.process_alive ? '在线':'不在线' }}
+
+ {{ wsc.conn_state || '-' }}
+
心跳 {{ wsc.heartbeat_age_sec==null ? '从未' : wsc.heartbeat_age_sec+'s 前' }}
+
+
每 3 秒自动刷新
+
刷新
+
+
+
+
+
+
+
seq 已落库
{{ wsc.last_seq }}
+
seq 已确认
{{ wsc.acked_seq }}
+
待入账上行
{{ wsc.inbox_pending }}
+
收 / 发
+
{{ wst.rx||0 }} / {{ wst.tx||0 }}
+
成交 / 拒绝
+
{{ wst.trades||0 }} / {{ wst.rejects||0 }}
+
重连
{{ wst.reconnects||0 }}
+
对端时钟偏差
+
+ {{ wst.peer_skew_ms==null ? '—' : (wst.peer_skew_ms>0?'+':'') + wst.peer_skew_ms + 'ms' }}
+
+
+
+
通道明细
+
+ 下发模式 {{ wsMode || '-' }}
+ 上次连上 {{ wsc.connected_at || '—' }}
+ 出口队列 {{ JSON.stringify(wsc.queue||{}) }}
+
+
+ 最后错误: {{ wsc.last_error }}
+
+ 最后一条拒绝: {{ wst.last_reject }}
+
+ 最后断开原因: {{ wst.last_close }}
+
+
+
+
出口委托 (pms_qmt_order)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
上行消息 (pms_qmt_inbox)
+ · 新→旧
+
+ 隐藏 pong
+ pong 每 5 秒一条且按协议 §5 带 seq, 不隐藏会把成交冲没
+
+
+
+
+
+
+ {{ ({0:'待入账',1:'已入账',2:'已消化'})[s.row.processed] || '?' }}
+
+
+
+
+ {{ JSON.stringify(s.row.payload || {}) }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 调度器 (--profile sched) 会自动按调度总表执行; 此处按钮用于未接调度或临时验证时手动触发。
+
+
+ 推进待规划命令
+ 方案转指令
+
+ 出手试算 (不下发)
+ 出手一跳
+ 窗口收口
+
+
+
+ 提议扫描试算 (不落表)
+ 提议扫描
+
+
+ 成交回放
+ 账本对账
+ 盘前准备
+ 日终结算
+ 生成日报
+ 导出下游表结构
+
+
+ 当前下发通道: {{ dm.mode }} —— {{ dm.hint }}
+ (通道在「参数设置 → PMS_DISPATCH_MODE」切换)
+
+ {{ opsResult }}
+
+
+
+
+
+
+ 强刷 + 灌行业映射
+ 只重读
+
+ 计划不带价格与金额 —— 买多少/什么价一律 PMS 自己算; 行情取不到价的票会被剔除。
+
+
+
+
+ 计划日 {{ planStatus.date }} (日龄 {{ planStatus.age_tdays }} 交易日) ·
+ 热度日 {{ planStatus.heat_date || '-' }} · 快照 {{ (planStatus.market_snapshot_days||[]).join(',') }} ·
+ 上游主题上限 {{ planStatus.theme_cap }} · theme 灌映射 {{ JSON.stringify(planStatus.theme_sync||{}) }}
+
请求 {{ JSON.stringify(planStatus.query||{}) }} · 漏斗: 打分池
+ 主榜 {{ (planStatus.funnel||{}).scored_main }} / 观察 {{ (planStatus.funnel||{}).scored_observe }}
+ →(券商预期 + 目标价≥现价 + 主题限额 + top)→ 实收
+ 主榜 {{ (planStatus.funnel||{}).returned_main }} / 观察 {{ (planStatus.funnel||{}).returned_observe }}
+
+
+
+
+ 按当前参数筛选: 排序池 {{ planCand.considered }} → 合格 {{ planCand.eligible }} →
+ 取 {{ (planCand.items||[]).length }} 只 ·
+ 白名单档位取全 {{ planCand.tier_complete===null ? '不适用' : (planCand.tier_complete ? '是':'否') }} ·
+ 丢弃 {{ JSON.stringify(planCand.dropped||{}) }}
+ ·
+ {{ planCand.st_unknown.length }} 只无名称, ST 判不了已放行
+
{{ (planCand.items||[]).map(x=>x.ts_code).join(' ') }}
+
+
+ 榜单变化
+
+ 重算变化
+ {{ chgSt.hint }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ chg.kind === 'first' ? '首次落快照' : (chg.prev_date + ' → ' + chg.curr_date) }} ·
+ 名册 {{ chg.roster_size.prev }} → {{ chg.roster_size.curr }} ·
+ 新进 {{ chg.counts.entered }} · 掉榜 {{ chg.counts.exited }} ·
+ 升档 {{ chg.counts.tier_up }} · 降档 {{ chg.counts.tier_down }} ·
+ 覆盖翻转 {{ chg.counts.bucket_moved }} · 名次跳变 {{ chg.counts.rank_jump }}
+ ·
+ 榜尾进出 {{ chg.tail_churn.entered + chg.tail_churn.exited }} 只未计入 (那一版吃满 top,
+ 尾部是截断噪音不是观点变化)
+ · 其中
+ {{ chg.tail_churn.held_exempt }} 只因是持仓票照报 (持仓票不受榜尾闸约束 ——
+ 漏报一条就是一个仓位)
+ · 同一计划日出现多版:
+ {{ chgRevised.map(x => x[0] + '×' + x[1]).join(', ') }}
+
+
+
+
+
+
+
+ {{ s.row.why }}
+
+
+
+
+
+ 上游把一只持仓票摘出榜或降了档, 是"还该不该继续拿着"的直接信号 ——
+ 单独拎出来免得被几十条新进榜淹掉。它只是提示, 不自动产生任何指令:
+ 要减要清仍走命令台或提议确认。
+
+
+
+
+
+
+
+
+
+
+ 持仓
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ s.row.tier_from }} → {{ s.row.tier_to }}
+
+
+
+
+
+
+
+
+
+
+ {{ s.row.rank_from }} → {{ s.row.rank_to }}
+
+ ({{ s.row.rank_delta > 0 ? '+' : '' }}{{ s.row.rank_delta }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 主榜
+
+
+
+
+
+ 同一「计划日」出现多行 = 上游那天重算过多版(UPSTREAM_PLAN_API.md §7.3:
+ `/plan` 是实时算的, 应答里没有 generated_at, 下游本来没办法判断手上这份是哪一版)。
+ 指纹不含 score —— 分数抖动但次序没变不算新版本, 否则表白涨而信息量为零。
+
+
+
+ 主榜明细
+
+
+
+
+
+
+
+
+
+
+ {{ s.row.upside==null ? '-' :
+ ((s.row.upside>0?'+':'') + (s.row.upside*100).toFixed(0) + '%') }}
+
+
+
+
+
+ 「预期空间」= 券商目标价相对现价的空间 (上游 upside 字段, 2.12 → +212%)。噪音大,
+ 所以永不参与排序 —— 排序始终按 score; 只在 PMS_PLAN_MIN_UPSIDE 上做下限过滤。
+ 筛选参数在「参数设置」里: PMS_PLAN_TOP_N / PMS_PLAN_TIERS / PMS_PLAN_INCLUDE_OBSERVE /
+ PMS_PLAN_MIN_SCORE / PMS_PLAN_MIN_SOURCES / PMS_PLAN_MIN_UPSIDE / PMS_PLAN_STALE_TDAYS。
+
发给上游的三个: PMS_PLAN_TOP / PMS_PLAN_OBS_TOP / PMS_PLAN_THEME_CAP (0=不传, 用上游默认
+ 20/10/5)。PMS_PLAN_THEME_CAP_LOCAL 是 PMS 侧的同主题限额, 在 TOP_N 截断之前生效 ——
+ 让上游别裁 (THEME_CAP 设大) + PMS 自己裁, 前 N 名才不会被单一主题垄断。
+
PMS_PLAN_EXCLUDE_ST 默认开: 上游名单明确不剔除 ST/*ST, 这道闸在 PMS 侧。
+ 「主题」是传导主题(事件驱动、天天变、覆盖约三成), 不是行业 —— 行业硬拦截
+ 请把 PMS_SECTOR_SOURCE 设成 gp_stock_category, 别用 PMS_PLAN_THEME_SYNC。
+
+ {{ planResult }}
+
+
+
+
+
+
关注区
+
+
+
+ {{ JSON.stringify(s.row) }}
+
+
+
+ {{ JSON.stringify(report.report||{}, null, 2) }}
+
+
+
+
+
+
diff --git a/docs/.write_test b/docs/.write_test
new file mode 100644
index 0000000..e69de29
diff --git a/docs/ui_mockup_trader_v2.html b/docs/ui_mockup_trader_v2.html
new file mode 100644
index 0000000..4fcdc5a
--- /dev/null
+++ b/docs/ui_mockup_trader_v2.html
@@ -0,0 +1,747 @@
+
+
+
+
+
+
PMS 交易员工作台 · 改版样张 v2
+
+
+
+
+
+
PMS 持仓管理v1.4.2 · 2026-08-26 11:55
+
盘中
+
买入 正常
+
执行 正常
+
自主 全自动?
+
选股计划 2026-08-26
+
大盘冷热 中性?
+
+
+
+
+
自动刷新 · 11:55:12
+
+
+
+
+
+
+
消息
+
+ 全部14
+ 行情告警8
+ 系统下单4
+ 等我拍板0
+ 策略与风控2
+
+
+
+ 11:16
+ 告警
+ 唯捷创芯 688153.SH
+ 看涨 资金流强度
+ 警告
+ 39 分钟前
+
+
+
+
+
+ 全部14
+ 行情告警8
+ 系统下单4
+ 等我拍板0
+ 策略与风控2
+
+
今天 · 2026-08-26
+
+
11:51策略
+
自动挂方案已跑过一轮 — 检查 0 只,新挂 0,换方案 0,停买 0,被挡 0,跳过 1
+
跳过的是唯捷创芯:它已经有一个同类方案在跑,不重复挂。
+
去我的策略 ›
+
+
11:16告警
+
唯捷创芯 688153.SH
+ 看涨 资金流强度 警告
+
看信号 ›
+
+
10:58下单
+
沐曦股份 688802.SH
+
系统建仓 首批 100 股,成交价 647.53
+
这只票分三批买,比例 50%、25%、25%。这是第一批,后两批要等股价回落到位再买。
+
看在办 ›
+
+
10:52告警
+
中航光电 002179.SZ
+ 看跌 资金分布 重要
+
看信号 ›
+
+
10:31下单
+
沐曦股份 688802.SH
+
系统建仓 已挂单 100 股,限价 648.00
+
今天允许下单的时间段是 10:30 到 11:00。合规检查和决策系统复核都放行了。
+
看在办 ›
+
+
09:52风控
+
天赐材料 002709.SZ
+
决策系统建议卖出,把握程度 0.72(满分 1)
+
日内资金一直在流出,股价也跌破了前一个低点。这只票现在改成只卖不买,卖出和止损照常。
+
去处理 ›
+
+
09:41下单
+
唯捷创芯 688153.SH
+
盈利加仓 被合规检查挡下
+
今天这只票的买入额度已经用完了,再买会超过单只股票的持仓上限。
+
看原因 ›
+
+
09:30告警
+
中航光电 002179.SZ
+ 看跌 日线形态 警告
+
看信号 ›
+
+
+
+
+
+
+
+
+
+
+
+
+ 8信号流▶
+
+
+
+
+
+
+
+
+
+ 需要注意 · 天赐材料已经连亏 2 天,决策系统建议卖出。这只票现在只卖不买,等你拍板。
+
+
+
我的持仓 · 4 只
+
+
+
安全垫?
+ 看的是浮盈厚不厚:厚垫可以考虑加仓,薄垫先放着,浮亏无垫不加仓。
+
+
操作列钉在右边,表格横向再挤也不会被切掉。「⋯」里是:设止损价、设目标价、只卖不买、加入黑名单、挂交易方案。
+
+
+
+
今日在办
+
今天下出去的买卖单,按进展分档。最后一档是今天考察过但没建仓的票,连原因一起摆出来。
+
+
+
+
+
+
+
+
+
+
+
今天发生了什么 · 23 条
+
+
系统的每一次判断,和你的每一次操作,合并成一条时间线。默认收起。
+
+
+
+
+
+
+
+ ◀待办与操作
+
+
+
+
+
+
+
这一版改了两件事
+
+ - 宽度不够时不再换行堆叠,改成三段式。 临界值是按「主区要放得下持仓表」倒推出来的,不是拍脑袋定的:
+ 窗口 ≥1660 像素,左右两栏都常驻;
+ 1380 到 1659,只让左边的信号流常驻,右栏收成 40 像素的窄条;
+ <1380,两栏都是窄条。
+ 窄条一直贴在主区两边看得见,点一下就从边上滑出浮层盖住主区,点旁边空白处或按 Esc 收回去。
+ 主区永远是一栏,任何宽度下都不会被挤成两行;真窄到放不下持仓表时,是表格自己横向滚动,操作列照样钉住。
+ 宽度够的时候你也可以手动点 ◀ ▶ 把某一栏收成窄条,再点窄条就展回来。
+ 右下角的小黑条告诉你现在是哪一段、窗口多宽——那只是给你看的,正式页面里没有。
+ - 文案里的行话全换成白话。 「策略买入腿已暂停」改成「这只票现在只卖不买」;「规则闸」改成「合规检查」;「研判闸」改成「决策系统复核」;「置信 0.72」改成「把握 0.72(满分 1)」;「日QRS对称」改成「日线形态」;「陈旧」改成「已过时」;「摊薄成本」改成「平均成本」。必须保留的项目术语(安全垫、自主档位、做T、日线形态)后面挂了一个「?」,鼠标停上去有一句白话解释。完整的对照表在 UI_COPY_AUDIT.md。
+
+
试一下:把浏览器窗口从最大慢慢拖窄,看它在 1660 和 1380 两处怎么换挡。
+
+
+
停靠模式 · 1512px
+
+
+
+