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:
@@ -1280,6 +1280,39 @@ body.market-chart-fs-open {
|
||||
border-bottom: 1px dashed var(--border-soft);
|
||||
}
|
||||
|
||||
.card-strategy-stats {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin: 10px 0 4px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px dashed var(--border-soft);
|
||||
}
|
||||
|
||||
.card-stat-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 3px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 11px;
|
||||
line-height: 1.3;
|
||||
color: var(--accent);
|
||||
background: var(--accent-dim);
|
||||
border: 1px solid var(--border-soft);
|
||||
}
|
||||
|
||||
.hub-tile .card-strategy-stats {
|
||||
margin: 4px 0 0;
|
||||
padding-top: 6px;
|
||||
border-top: none;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.hub-tile .card-stat-chip {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
|
||||
.pos-action-group {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -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>`;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
|
||||
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
|
||||
<link rel="stylesheet" href="/assets/app.css?v=20260604-hub-mobile-tiles" />
|
||||
<link rel="stylesheet" href="/assets/app.css?v=20260604-hub-card-stats" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-bg" aria-hidden="true"></div>
|
||||
@@ -246,6 +246,6 @@
|
||||
<div id="toast"></div>
|
||||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||
<script src="/assets/chart.js?v=20260603-hub-binance-tick"></script>
|
||||
<script src="/assets/app.js?v=20260604-hub-pos-table"></script>
|
||||
<script src="/assets/app.js?v=20260604-hub-card-stats"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user