diff --git a/frontend/console.html b/frontend/console.html index 548fb08..7c2ac82 100644 --- a/frontend/console.html +++ b/frontend/console.html @@ -350,6 +350,133 @@ gap: 18px; } + .focus-summary { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; + margin-top: 14px; + } + + .focus-summary-card { + padding: 14px 16px; + border-radius: 16px; + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.06); + } + + .focus-summary-card .kicker { + color: var(--muted); + font-size: 11px; + margin-bottom: 8px; + text-transform: uppercase; + letter-spacing: 0.08em; + font-family: "IBM Plex Mono", monospace; + } + + .focus-summary-card .headline { + font-family: "IBM Plex Mono", monospace; + font-size: 18px; + color: var(--text); + margin-bottom: 6px; + } + + .focus-summary-card .headline.good { + color: var(--good); + } + + .focus-summary-card .headline.warn { + color: var(--warn); + } + + .focus-summary-card .headline.danger { + color: var(--danger); + } + + .focus-summary-card .detail { + color: var(--muted); + font-size: 12px; + line-height: 1.55; + } + + .alert-strip { + display: grid; + gap: 10px; + margin-top: 14px; + } + + .alert-strip:empty { + display: none; + } + + .alert-banner { + display: grid; + grid-template-columns: 140px 1fr auto; + gap: 12px; + align-items: center; + padding: 12px 14px; + border-radius: 16px; + border: 1px solid rgba(255,255,255,0.08); + background: rgba(255,255,255,0.03); + } + + .alert-banner.danger { + border-color: rgba(255, 111, 97, 0.26); + background: rgba(255, 111, 97, 0.10); + } + + .alert-banner.warn { + border-color: rgba(255, 184, 77, 0.24); + background: rgba(255, 184, 77, 0.10); + } + + .alert-banner.good { + border-color: rgba(48, 209, 88, 0.22); + background: rgba(48, 209, 88, 0.08); + } + + .alert-kicker { + color: var(--text); + font-size: 11px; + font-family: "IBM Plex Mono", monospace; + text-transform: uppercase; + letter-spacing: 0.08em; + } + + .alert-detail { + color: var(--muted); + font-size: 13px; + line-height: 1.55; + } + + .alert-count { + font-family: "IBM Plex Mono", monospace; + font-size: 18px; + color: var(--text); + } + + .focus-grid { + display: grid; + grid-template-columns: minmax(320px, 0.82fr) minmax(0, 1.18fr); + gap: 18px; + margin-top: 18px; + } + + .focus-panel { + min-height: 100%; + } + + .overview-grid { + display: grid; + grid-template-columns: minmax(0, 1.18fr) minmax(320px, 0.82fr); + gap: 14px; + } + + .overview-main, + .overview-side { + display: grid; + gap: 14px; + } + .ops-panel-body { min-height: 320px; } @@ -636,7 +763,7 @@ } .platform-grid { - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: 1fr; } .platform-grid.compact { @@ -647,12 +774,22 @@ padding: 18px; } + .platform-card.danger { + border-color: rgba(255, 111, 97, 0.22); + background: rgba(255, 111, 97, 0.06); + } + + .platform-card.warn { + border-color: rgba(255, 184, 77, 0.18); + background: rgba(255, 184, 77, 0.05); + } + .platform-top { display: flex; justify-content: space-between; gap: 12px; align-items: flex-start; - margin-bottom: 14px; + margin-bottom: 10px; } .platform-title { @@ -667,8 +804,38 @@ color: var(--muted); } + .platform-overview { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 10px; + margin-bottom: 12px; + } + + .platform-pill-stat { + padding: 10px 12px; + border-radius: 12px; + background: rgba(255,255,255,0.035); + border: 1px solid rgba(255,255,255,0.05); + } + + .platform-pill-stat .label { + display: block; + color: var(--muted); + font-size: 10px; + margin-bottom: 4px; + text-transform: uppercase; + letter-spacing: 0.05em; + font-family: "IBM Plex Mono", monospace; + } + + .platform-pill-stat .value { + font-family: "IBM Plex Mono", monospace; + font-size: 14px; + color: var(--text); + } + .risk-band { - margin: 12px 0 14px; + margin: 8px 0 12px; height: 8px; border-radius: 999px; background: rgba(255,255,255,0.06); @@ -683,7 +850,7 @@ .platform-stats { display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; } @@ -705,6 +872,29 @@ font-size: 14px; } + .platform-detail-toggle { + margin-top: 12px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + color: var(--muted); + font-size: 12px; + } + + .platform-detail-toggle summary { + cursor: pointer; + list-style: none; + font-family: "IBM Plex Mono", monospace; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--cold); + } + + .platform-detail-toggle summary::-webkit-details-marker { + display: none; + } + .stream-card { padding: 18px; } @@ -789,6 +979,21 @@ min-height: 0; } + .scroll-panel { + max-height: 560px; + overflow: auto; + padding-right: 4px; + } + + .log-shell { + display: grid; + gap: 12px; + } + + .log-head { + margin-bottom: 0; + } + .event-list { display: grid; gap: 10px; @@ -1557,7 +1762,9 @@ @media (max-width: 1240px) { .hero, .layout, - .priority-layout { + .priority-layout, + .focus-grid, + .overview-grid { grid-template-columns: 1fr; } @@ -1568,9 +1775,18 @@ grid-template-columns: 1fr; } + .platform-overview, + .platform-stats { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .hero-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); } + + .focus-summary { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } } @media (max-width: 720px) { @@ -1590,10 +1806,16 @@ .hero-metrics, .platform-stats, + .platform-overview, .signal-stats, .heartbeat-grid, .health-ribbon, - .ops-summary { + .ops-summary, + .focus-summary { + grid-template-columns: 1fr; + } + + .alert-banner { grid-template-columns: 1fr; } @@ -1659,62 +1881,47 @@
最近轮次
-
人工处理
-
+
+
运行心跳
-
+
待执行机会
-
+
风险暴露
-
+
阻塞状态
-
+
+
-
-
-
-
-
- -

管理待处理事项

-
把需要你判断和干预的事情放到最前面
-
+
+
+
+ +

指挥台视图

+
先看系统机会与风险,再下钻到运行细节和日志
+
+
+ +
+ + + +
-
-
正在汇总待处理事项...
-
-
+
-
-
-
-
- -

运行监控面板

-
把平台概览、分析心跳和停机熔断收进一个运行面板,避免首屏纵向堆叠
-
-
- -
- - - +
+
+
+
+
+ +

最近决策预览

+
优先看系统准备做什么,而不是先看历史事件
-
-
- -
- - - -
- -
-
+
+
正在读取决策预览...
+
+
+

平台执行概览

@@ -1723,12 +1930,65 @@
正在加载平台状态...
-
+
+
+
+
+
+ +

管理待处理事项

+
把需要你判断和干预的事情放到最前面
+
+
+
正在汇总待处理事项...
+
+
+
+
+ +

最近信号流

+
只保留紧凑摘要,不让历史列表抢占主视图
+
+
+
正在读取信号...
+
+
+
+ + -
+
+
+
+
+ +

平台停机 / 熔断

+
风险触发后,这里应当最先看到
+
+
+
正在读取平台停机状态...
+
+
+
+
+ +

最近阻塞原因

+
未落单、风险过滤、执行限制统一归因
+
+
+
正在读取未落单汇总...
+
+
+
+
+ +
+
+
+
-

分析心跳与日志

+

分析心跳

没有信号时,也能确认系统仍在正常扫盘
@@ -1746,34 +2006,53 @@
执行监管器
正在整理执行监管状态...
-
-
最近阻塞原因
-
-
正在读取未落单汇总...
-
-
+
+
+ +
+
+
+ +

执行监管目标

+
监管 target、保护单补救、持仓管理能力
正在读取执行监管目标...
-
-
正在读取分析日志...
-
-
- -
-
- -

平台停机 / 熔断

-
风险触发后,这里应当最先看到
-
-
-
正在读取平台停机状态...
-
- -
+ - + + + +
+
+
+ +

运行与执行日志

+
日志放到最后一级,按需查看,不再占用首屏决策空间
+
+
+ + +
+
+
+
+ + + + + +
+
+
正在读取执行事件...
+
+
+
+
+
正在读取分析日志...
+
+
@@ -1782,14 +2061,13 @@
- -

交易工作区

-
次级明细放进 tab,保留一个干净的管理视角
+ +

补充工作区

+
首屏用于判断,补充区用于核对账户、信号缓存和明细状态
- - - + +
@@ -1808,11 +2086,14 @@
-

最近决策预览

-
最近一轮各平台准备执行的动作
+

Agent / 账户协同

+
保留辅助视角,用于对照信号缓存与系统工作状态
-
-
正在读取决策预览...
+
+
+ 主视图已迁移到指挥台 +
最近决策预览已放到上方“总览”主标签,这里仅保留补充信息,避免重复占屏。
+
@@ -1821,29 +2102,14 @@
-

最近信号流

-
数据库最新信号与等级、置信度、时间分布
+

最近信号缓存

+
用于核对 Agent 最近缓存的信号,不再重复展示完整主信号流
-
-
正在读取信号...
-
-
- -
-
- -

执行事件流

-
最近执行结果、未执行原因、平台异常
-
-
- - - - - -
-
-
正在读取执行事件...
+
+
+ 主信号流已迁移到指挥台 +
最近信号流已放到上方“总览”主标签,这里保留为信号缓存核对区。
+
@@ -2111,6 +2377,118 @@ setActiveTab('ops', getActiveTabTarget('ops') || 'opsPlatform'); } + function renderFocusSummary(data) { + const container = document.getElementById('focusSummary'); + if (!container) return; + + const cryptoAgent = data.crypto_agent || {}; + const monitor = cryptoAgent.analysis_monitor || {}; + const previews = Object.values(cryptoAgent.last_execution_preview || {}); + const management = data.management || {}; + const positions = management.positions || []; + const attentionItems = management.attention_items || []; + const haltedCount = countHalted(cryptoAgent.platform_halts || {}); + const blockedEvents = (data.execution_events || []).filter((event) => event.event_type === 'execution_blocked_summary'); + const actionablePreviewCount = previews.filter((preview) => { + const paperDecision = preview.paper?.decision; + const bitgetDecision = preview.bitget?.decision; + return ['OPEN', 'ADD', 'CLOSE', 'CANCEL_PENDING'].includes(paperDecision) || + ['OPEN', 'ADD', 'CLOSE', 'CANCEL_PENDING'].includes(bitgetDecision); + }).length; + const heartbeatHeadline = monitor.last_heartbeat_at ? relativeTime(monitor.last_heartbeat_at) : '无心跳'; + const heartbeatTone = toneClassForHealth(cryptoAgent.running ? (monitor.last_cycle_status || monitor.last_analysis_status) : 'stopped'); + const exposureNotional = positions.reduce((sum, item) => sum + Number(item.notional || item.size || 0), 0); + const riskTone = haltedCount > 0 ? 'danger' : (positions.length > 0 ? 'warn' : 'good'); + const blockedTone = blockedEvents.length > 0 || attentionItems.length > 0 ? 'warn' : 'good'; + + container.innerHTML = ` +
+
运行心跳
+
${heartbeatHeadline}
+
状态 ${String(monitor.last_cycle_status || monitor.last_analysis_status || 'idle').toUpperCase()} / 下次 ${monitor.next_scheduled_run_at ? formatTime(monitor.next_scheduled_run_at) : '-'}
+
+
+
待执行机会
+
${actionablePreviewCount}
+
最近预览中可执行动作 ${actionablePreviewCount} 个
+
+
+
风险暴露
+
${positions.length} 仓
+
名义暴露 ${formatSensitiveMoney(exposureNotional)} / 停机 ${haltedCount}
+
+
+
阻塞状态
+
${blockedEvents.length}
+
阻塞事件 ${blockedEvents.length} / 待处理 ${attentionItems.length}
+
+ `; + } + + function renderAlertStrip(data) { + const container = document.getElementById('alertStrip'); + if (!container) return; + + const cryptoAgent = data.crypto_agent || {}; + const monitor = cryptoAgent.analysis_monitor || {}; + const attentionItems = data.management?.attention_items || []; + const haltedCount = countHalted(cryptoAgent.platform_halts || {}); + const disabledCount = Object.values(cryptoAgent.target_execution_controls || {}).filter((item) => item && item.enabled === false).length; + const blockedEvents = (data.execution_events || []).filter((event) => event.event_type === 'execution_blocked_summary'); + const banners = []; + + if (!cryptoAgent.running || !monitor.last_heartbeat_at) { + banners.push({ + tone: 'danger', + kicker: 'System Halt', + detail: !cryptoAgent.running ? 'Crypto Agent 当前未运行。' : '尚未收到最近心跳,请优先确认调度与任务循环。', + count: !cryptoAgent.running ? 'STOP' : 'NO HB', + }); + } + + if (haltedCount > 0) { + banners.push({ + tone: 'danger', + kicker: 'Platform Halt', + detail: `当前有 ${haltedCount} 个执行目标处于停机/熔断状态,需要优先恢复或核查风控触发原因。`, + count: `${haltedCount} HALT`, + }); + } else if (disabledCount > 0) { + banners.push({ + tone: 'warn', + kicker: 'Manual Off', + detail: `当前有 ${disabledCount} 个执行目标被人工关闭自动交易。`, + count: `${disabledCount} OFF`, + }); + } + + if (blockedEvents.length > 0) { + banners.push({ + tone: 'warn', + kicker: 'Execution Blocked', + detail: `最近存在 ${blockedEvents.length} 条执行阻塞汇总,建议切到“风险”标签查看平台级归因。`, + count: `${blockedEvents.length} BLOCK`, + }); + } + + if (!banners.length && attentionItems.length === 0) { + banners.push({ + tone: 'good', + kicker: 'System Clear', + detail: '当前没有停机、阻塞或明显告警,系统处于可管理状态。', + count: 'CLEAR', + }); + } + + container.innerHTML = banners.slice(0, 3).map((banner) => ` +
+
${banner.kicker}
+
${banner.detail}
+
${banner.count}
+
+ `).join(''); + } + function syncTabState(data) { const recentSignals = data.signals?.latest || []; const executionEvents = data.execution_events || []; @@ -2126,30 +2504,42 @@ const haltedCount = countHalted(data.crypto_agent?.platform_halts || {}); const disabledCount = Object.values(data.crypto_agent?.target_execution_controls || {}).filter((item) => item && item.enabled === false).length; const runtimeCount = (data.crypto_agent?.recent_analysis_events || []).length + (data.crypto_agent?.analysis_monitor?.last_heartbeat_at ? 1 : 0); + const blockedCount = (data.execution_events || []).filter((event) => event.event_type === 'execution_blocked_summary').length; - updateTabButton('ops', 'opsPlatform', '平台概览', platformCount, platformCount > 0); - updateTabButton('ops', 'opsRuntime', '心跳日志', runtimeCount, runtimeCount > 0); - updateTabButton('ops', 'opsRisk', '停机熔断', haltedCount + disabledCount, haltedCount + disabledCount > 0); - updateTabButton('workspace', 'workspaceCoordination', '协同', coordinationCount, coordinationCount > 0); - updateTabButton('workspace', 'workspaceSignals', '信号流', recentSignals.length, recentSignals.length > 0); - updateTabButton('workspace', 'workspaceExecution', '执行流', executionEvents.length, executionEvents.length > 0); + updateTabButton('command', 'commandOverview', '总览', coordinationCount + platformCount, coordinationCount + platformCount > 0); + updateTabButton('command', 'commandRisk', '风险', haltedCount + disabledCount + blockedCount, haltedCount + disabledCount + blockedCount > 0); + updateTabButton('command', 'commandRuntime', '运行', runtimeCount, runtimeCount > 0); + updateTabButton('command', 'commandLogs', '日志', executionEvents.length + runtimeCount, executionEvents.length + runtimeCount > 0); + updateTabButton('logs', 'logsExecution', '执行事件', executionEvents.length, executionEvents.length > 0); + updateTabButton('logs', 'logsAnalysis', '分析日志', runtimeCount, runtimeCount > 0); + updateTabButton('workspace', 'workspaceCoordination', '账户协同', coordinationCount, coordinationCount > 0); + updateTabButton('workspace', 'workspaceSignals', '信号缓存', recentSignals.length, recentSignals.length > 0); updateTabButton('asset', 'assetPositions', '持仓', positions.length, positions.length > 0); updateTabButton('asset', 'assetOrders', '挂单', orders.length, orders.length > 0); - const opsCurrent = getActiveTabTarget('ops'); - const opsChoices = [ - { target: 'opsRisk', hasData: haltedCount + disabledCount > 0 }, - { target: 'opsRuntime', hasData: runtimeCount > 0 }, - { target: 'opsPlatform', hasData: platformCount > 0 }, + const commandCurrent = getActiveTabTarget('command'); + const commandChoices = [ + { target: 'commandRisk', hasData: haltedCount + disabledCount + blockedCount > 0 }, + { target: 'commandOverview', hasData: coordinationCount + platformCount > 0 }, + { target: 'commandRuntime', hasData: runtimeCount > 0 }, + { target: 'commandLogs', hasData: executionEvents.length + runtimeCount > 0 }, ]; - if (!opsChoices.find((item) => item.target === opsCurrent && item.hasData)) { - setActiveTab('ops', opsChoices.find((item) => item.hasData)?.target || 'opsPlatform'); + if (!commandChoices.find((item) => item.target === commandCurrent && item.hasData)) { + setActiveTab('command', commandChoices.find((item) => item.hasData)?.target || 'commandOverview'); + } + + const logsCurrent = getActiveTabTarget('logs'); + const logsChoices = [ + { target: 'logsExecution', hasData: executionEvents.length > 0 }, + { target: 'logsAnalysis', hasData: runtimeCount > 0 }, + ]; + if (!logsChoices.find((item) => item.target === logsCurrent && item.hasData)) { + setActiveTab('logs', logsChoices.find((item) => item.hasData)?.target || 'logsExecution'); } const workspaceCurrent = getActiveTabTarget('workspace'); const workspaceChoices = [ { target: 'workspaceCoordination', hasData: coordinationCount > 0 }, - { target: 'workspaceExecution', hasData: executionEvents.length > 0 }, { target: 'workspaceSignals', hasData: recentSignals.length > 0 }, ]; if (!workspaceChoices.find((item) => item.target === workspaceCurrent && item.hasData)) { @@ -2304,9 +2694,18 @@ const disabledBitgetCount = key === 'bitget' ? accounts.filter((accountItem) => executionControls?.[`Bitget:${accountItem.account_id}`]?.enabled === false).length : 0; + const drawdownValue = risk.drawdown_percent || risk.drawdown || 0; + const thresholdValue = risk.circuit_breaker_threshold || 25; + const platformTone = (!enabled || halt.halted || bitgetHaltActive || drawdownValue >= thresholdValue) + ? 'danger' + : ((key === 'paper' && platformExecutionControl?.enabled === false) || disabledBitgetCount > 0 || currentLeverage >= maxLeverage * 0.75) + ? 'warn' + : ''; const accountRows = key === 'bitget' && accounts.length ? ` -
+
+ 查看账号明细 +
${accounts.slice(0, 4).map((accountItem) => { const accountTargetKey = `Bitget:${accountItem.account_id}`; const accountHalt = platformHalts?.[accountTargetKey] || {}; @@ -2333,12 +2732,13 @@
`; }).join('')} -
+ + ` : ''; return ` -
+
${subtitle}
@@ -2346,24 +2746,26 @@
${!enabled ? 'DISABLED' : (halt.halted || bitgetHaltActive) ? 'HALTED' : ((key === 'paper' && platformExecutionControl?.enabled === false) ? 'MANUAL OFF' : 'ONLINE')}
-
-
-
+
+
权益 ${formatSensitiveMoney(account.current_balance || account.account_value)}
-
- 可用 - ${formatSensitiveMoney(account.available || account.available_balance)} +
+ 可用 / 保证金 + ${formatSensitiveMoney(account.available || account.available_balance)} / ${formatSensitiveMoney(account.used_margin || account.total_margin_used)}
-
- 持仓 - ${positions.count || 0} +
+ 持仓 / 挂单 + ${positions.count || 0} / ${orders.count || 0}
-
- 挂单 - ${orders.count || 0} +
+ 自动交易 + ${key === 'paper' ? (platformExecutionControl?.enabled === false ? 'OFF' : 'ON') : `${accounts.length - disabledBitgetCount}/${accounts.length} ON`}
+
+
+
开仓单 / TP-SL ${orders.entry_orders || orders.pending_count || 0} / ${orders.tp_sl_orders || 0} @@ -2374,13 +2776,17 @@
回撤 - - ${formatPercent(risk.drawdown_percent || risk.drawdown || 0)} + + ${formatPercent(drawdownValue)}
- 自动交易 - ${key === 'paper' ? (platformExecutionControl?.enabled === false ? 'OFF' : 'ON') : `${accounts.length - disabledBitgetCount}/${accounts.length} ON`} + 风险阈值 + ${formatPercent(thresholdValue, 1)} +
+
+ 账号数量 + ${key === 'bitget' ? accounts.length : 1}
${key === 'paper' ? ` @@ -3041,6 +3447,8 @@ syncTabState(data); renderHero(data); renderHealthRibbon(data); + renderFocusSummary(data); + renderAlertStrip(data); renderOpsSummary(data); renderPlatforms(data.platforms, data.crypto_agent?.platform_halts, data.crypto_agent?.target_execution_controls || {}); renderSignalStream(data.signals?.latest || []);