feat(hub): show key/trend/roll counts on monitor cards

Add per-card strategy stats chips for breakout, fib, and watch key levels plus trend and roll plan counts when non-zero.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-03 23:25:43 +08:00
parent 7957a62c65
commit 98038b1945
3 changed files with 102 additions and 2 deletions
+67
View File
@@ -1606,6 +1606,68 @@
</div>`;
}
const KEY_BUCKET_FIB_TYPES = new Set([
"斐波回调0.618",
"斐波回调0.786",
"关键位斐波0.618",
"关键位斐波0.786",
]);
const KEY_BUCKET_BREAKOUT_TYPES = new Set([
"箱体突破",
"收敛突破",
"关键位箱体突破",
"关键位收敛突破",
"关键位收敛结构",
]);
const KEY_BUCKET_WATCH_TYPES = new Set([
"关键阻力位",
"关键支撑位",
"关键位监控",
]);
function classifyKeyMonitorBucket(monitorType) {
const t = String(monitorType || "").trim();
if (!t) return "watch";
if (KEY_BUCKET_FIB_TYPES.has(t) || /斐波/.test(t)) return "fib";
if (KEY_BUCKET_BREAKOUT_TYPES.has(t) || /突破/.test(t)) return "breakout";
if (KEY_BUCKET_WATCH_TYPES.has(t) || /阻力|支撑/.test(t)) return "watch";
return "watch";
}
function countKeyMonitorsByBucket(keys) {
const counts = { breakout: 0, fib: 0, watch: 0 };
(keys || []).forEach((k) => {
if (!k || typeof k !== "object") return;
const bucket = classifyKeyMonitorBucket(k.monitor_type || k.type);
if (bucket === "breakout") counts.breakout += 1;
else if (bucket === "fib") counts.fib += 1;
else counts.watch += 1;
});
return counts;
}
function renderCardStrategyStats(row, hm, flaskOk) {
if (!flaskOk || !hm || typeof hm !== "object") return "";
const caps = row.capabilities || [];
const chips = [];
if (caps.includes("key")) {
const kc = countKeyMonitorsByBucket(hm.keys || []);
if (kc.breakout > 0) chips.push(`突破 ${kc.breakout}`);
if (kc.fib > 0) chips.push(`斐波 ${kc.fib}`);
if (kc.watch > 0) chips.push(`监控 ${kc.watch}`);
}
if (caps.includes("trend")) {
const trendN = Array.isArray(hm.trends) ? hm.trends.length : 0;
if (trendN > 0) chips.push(`趋势回调 ${trendN}`);
}
const rollN = Array.isArray(hm.rolls) ? hm.rolls.length : 0;
if (rollN > 0) chips.push(`顺势加仓 ${rollN}`);
if (!chips.length) return "";
return `<div class="card-strategy-stats">${chips
.map((label) => `<span class="card-stat-chip">${esc(label)}</span>`)
.join("")}</div>`;
}
function renderGridPositionsTable(exchangeId, exchangeKey, positions, orders, trends, tickMap) {
const rows = positions
.map((p) =>
@@ -1646,6 +1708,7 @@
} else {
inner += '<div class="empty-hint">无持仓</div>';
}
inner += renderCardStrategyStats(row, hm, flaskOk);
inner += `<div class="card-expand-hint">点击标题栏进入全屏 · 委托 / 关键位 / 下单监控 / 趋势回调 / 顺势加仓</div>`;
return inner;
}
@@ -1925,6 +1988,9 @@
const tsShort = ts ? ts.slice(-8) : "—";
const posLine =
openCount > 0 ? `${openCount}仓 · ${alert.summary}` : alert.summary;
const hm = row.hub_monitor || {};
const flaskOk = row.flask_ok !== false && hm.ok !== false;
const strategyStats = renderCardStrategyStats(row, hm, flaskOk);
return `<div class="card hub-tile ${tileCls}" data-ex-id="${esc(row.id)}">
<div class="hub-tile-body card-expand-zone" title="点击进入全屏详情">
<div class="hub-tile-top">
@@ -1933,6 +1999,7 @@
</div>
<div class="hub-tile-pnl ${pnlCls(upnl)}">${fmt(upnl, 2)} <small>U</small></div>
<div class="hub-tile-meta">${esc(posLine)}</div>
${strategyStats}
<div class="hub-tile-foot">UPD ${esc(tsShort)}</div>
</div>
</div>`;