diff --git a/live/bot_dashboard.html b/live/bot_dashboard.html
index c6b9d295..e152e733 100644
--- a/live/bot_dashboard.html
+++ b/live/bot_dashboard.html
@@ -58,6 +58,26 @@ footer{text-align:center;color:var(--dim);font-size:12px;margin-top:30px}
·
+
+
@@ -249,6 +269,132 @@ async function render() {
render();
setInterval(render, 30000);
+
+// ========== 系统健康面板 ==========
+async function renderHealth() {
+ let h;
+ try {
+ const r = await fetch('/api/health', {cache: 'no-store'});
+ h = await r.json();
+ } catch (e) {
+ document.getElementById('health-content').innerHTML =
+ '
获取失败: ' + e.message + '';
+ return;
+ }
+
+ const svcNames = {
+ 'copybot-paper': 'Paper Bot',
+ 'copybot-live': 'Live Bot',
+ 'copybot-dashboard': 'Dashboard',
+ 'mihomo': '代理'
+ };
+ let html = '';
+ for (const [svc, label] of Object.entries(svcNames)) {
+ const state = h.services?.[svc] || 'unknown';
+ const color = state === 'active' ? 'var(--green)' : 'var(--red)';
+ html += `
${label}: ${state}`;
+ }
+
+ // 代理
+ const proxyOk = h.proxy === 'ok';
+ html += `
代理: ${h.proxy || '?'}`;
+
+ // CASH≠CHAIN
+ if (h.cash_drift !== null && h.cash_drift !== undefined) {
+ const driftOk = Math.abs(h.cash_drift) < 0.5;
+ html += `
CASH≠CHAIN: ${driftOk ? '正常' : '$' + h.cash_drift}`;
+ }
+
+ // 心跳
+ if (h.last_heartbeat) {
+ const ageMin = Math.floor((h.heartbeat_age_s || 0) / 60);
+ const ageSec = (h.heartbeat_age_s || 0) % 60;
+ const fresh = (h.heartbeat_age_s || 999) < 300;
+ html += `
心跳: ${ageMin}分${ageSec}秒前`;
+ }
+
+ document.getElementById('health-content').innerHTML = html;
+}
+
+// ========== 每日流水线面板 ==========
+async function renderDaily() {
+ let d;
+ try {
+ const r = await fetch('/api/daily', {cache: 'no-store'});
+ d = await r.json();
+ } catch (e) {
+ document.getElementById('daily-content').innerHTML =
+ '
获取失败';
+ return;
+ }
+
+ const ok = d.status === 'ok';
+ const color = ok ? 'var(--green)' : 'var(--red)';
+ let html = `
${d.status === 'ok' ? '✓ 正常' : d.status}`;
+ if (d.last_run) {
+ html += `
上次运行: ${d.last_run}`;
+ }
+ html += `
· 钱包池: ${d.skilled_count} skilled / ${d.sharps_count} sharps`;
+ document.getElementById('daily-content').innerHTML = html;
+}
+
+// ========== 钱包建议面板 ==========
+async function renderWallets() {
+ let w;
+ try {
+ const r = await fetch('/api/wallets', {cache: 'no-store'});
+ w = await r.json();
+ } catch (e) {
+ document.getElementById('wallets-content').innerHTML =
+ '
获取失败';
+ return;
+ }
+
+ let html = '
' +
+ '| 当前跟的 | 排名 | copy_pnl | 信念胜率 | 状态 |
';
+
+ for (const f of (w.followed || [])) {
+ let statusBadge, statusColor;
+ if (f.status === 'ok') {
+ statusBadge = '✅ 正常';
+ statusColor = 'var(--green)';
+ } else {
+ statusBadge = '❌ 已淘汰';
+ statusColor = 'var(--red)';
+ }
+ html += `
+ | ${f.name} |
+ ${f.rank ? '#' + f.rank : '-'} |
+ ${f.copy_pnl != null ? '+' + f.copy_pnl : '-'} |
+ ${f.conv_win != null ? f.conv_win + '%' : '-'} |
+ ${statusBadge} |
+
`;
+ }
+ html += '
';
+
+ // 建议加的
+ const suggestions = w.suggestions || [];
+ if (suggestions.length > 0) {
+ html += '
建议加(watch_sharps 排名前 6 未跟的):
';
+ html += '
';
+ for (const s of suggestions) {
+ html += `⬆️ ${s.name} · #${s.rank} · +${s.copy_pnl}${s.conv_win != null ? ' · ' + s.conv_win + '%' : ''}`;
+ }
+ html += '
';
+ } else {
+ html += '
当前跟的钱包都在 watch_sharps 里,无需调整 ✓
';
+ }
+
+ document.getElementById('wallets-content').innerHTML = html;
+}
+
+// 健康面板 60 秒刷新(比交易数据慢,系统状态变化少)
+renderHealth();
+renderDaily();
+renderWallets();
+setInterval(renderHealth, 60000);
+setInterval(renderDaily, 60000);
+setInterval(renderWallets, 60000);