diff --git a/backend/app/api/system.py b/backend/app/api/system.py index e51d831..1c7dab5 100644 --- a/backend/app/api/system.py +++ b/backend/app/api/system.py @@ -11,6 +11,8 @@ from app.crypto_agent.crypto_agent import get_crypto_agent from app.services.signal_database_service import get_signal_db_service from app.services.paper_trading_service import get_paper_trading_service from app.services.bitget_live_trading_service import get_all_bitget_live_services, get_bitget_live_service +from app.services.price_monitor_service import get_price_monitor_service +from app.services.runtime_status_service import get_runtime_status router = APIRouter() @@ -429,6 +431,11 @@ async def get_console_snapshot(): if (_parse_signal_timestamp(signal.get("created_at")) or datetime.min) >= recent_cutoff ) + price_monitor = get_price_monitor_service() + configured_symbols = [symbol.strip().upper() for symbol in (get_settings().crypto_symbols or "").split(",") if symbol.strip()] + for symbol in configured_symbols: + price_monitor.subscribe_symbol(symbol) + paper_position_items = [ _normalize_platform_position("paper", pos) for pos in paper_service.get_open_positions()[:12] @@ -517,6 +524,16 @@ async def get_console_snapshot(): "recent_30m_count": recent_signal_count, }, "platforms": platforms_payload, + "monitoring": { + "price_monitor": { + "running": price_monitor.is_running(), + "mode": "websocket" if getattr(price_monitor, "_use_websocket", False) else "polling", + "subscribed_symbols": price_monitor.get_subscribed_symbols(), + "latest_prices": price_monitor.get_all_prices(), + "checked_at": now.isoformat(), + }, + "execution_loop": get_runtime_status("price_monitor_loop"), + }, "management": { "positions": unified_positions[:18], "orders": unified_orders[:24], diff --git a/backend/app/main.py b/backend/app/main.py index 09c31a8..f13a408 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -5,7 +5,7 @@ import asyncio from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles -from fastapi.responses import FileResponse +from fastapi.responses import FileResponse, RedirectResponse from contextlib import asynccontextmanager from app.config import get_settings from app.utils.logger import logger @@ -485,7 +485,7 @@ if os.path.exists(frontend_path): @app.get("/") async def root(): """根路径,返回主应用页面""" - index_path = os.path.join(frontend_path, "trading.html") + index_path = os.path.join(frontend_path, "console.html") if os.path.exists(index_path): return FileResponse(index_path) return {"message": "页面不存在"} @@ -493,7 +493,7 @@ async def root(): @app.get("/app") async def app_page(): """主应用页面""" - index_path = os.path.join(frontend_path, "trading.html") + index_path = os.path.join(frontend_path, "console.html") if os.path.exists(index_path): return FileResponse(index_path) return {"message": "页面不存在"} @@ -505,19 +505,13 @@ async def health_check(): @app.get("/trading") async def trading_page(): - """交易页面""" - page_path = os.path.join(frontend_path, "trading.html") - if os.path.exists(page_path): - return FileResponse(page_path) - return {"message": "页面不存在"} + """交易页面兼容入口,统一跳转到总控台""" + return RedirectResponse(url="/console", status_code=307) @app.get("/bitget-trading") async def bitget_trading_page(): - """Bitget 交易页面兼容入口,统一跳转到当前 trading 页面""" - page_path = os.path.join(frontend_path, "trading.html") - if os.path.exists(page_path): - return FileResponse(page_path) - return {"message": "页面不存在"} + """Bitget 交易页面兼容入口,统一跳转到总控台""" + return RedirectResponse(url="/console", status_code=307) @app.get("/signals") async def signals_page(): diff --git a/frontend/console.html b/frontend/console.html index 49e93b4..a94a1a7 100644 --- a/frontend/console.html +++ b/frontend/console.html @@ -58,6 +58,167 @@ padding: 24px 0 40px; } + .console-hub { + display: grid; + grid-template-columns: 240px minmax(0, 1fr); + gap: 18px; + align-items: start; + } + + .console-sidebar { + position: sticky; + top: 18px; + padding: 16px; + border-radius: 22px; + background: var(--panel); + border: 1px solid var(--line); + box-shadow: var(--shadow); + backdrop-filter: blur(18px); + } + + .console-sidebar::after { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + border-radius: 22px; + background: linear-gradient(180deg, rgba(255,255,255,0.04), transparent 24%, transparent 76%, rgba(126, 200, 255, 0.04)); + } + + .sidebar-label { + color: var(--muted); + font-size: 11px; + margin-bottom: 12px; + font-family: "IBM Plex Mono", monospace; + text-transform: uppercase; + letter-spacing: 0.08em; + } + + .sidebar-nav { + display: grid; + gap: 10px; + } + + .sidebar-link { + appearance: none; + width: 100%; + text-align: left; + cursor: pointer; + padding: 14px 16px; + border-radius: 16px; + border: 1px solid rgba(255,255,255,0.06); + background: rgba(255,255,255,0.03); + color: var(--text); + transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease; + } + + .sidebar-link:hover { + transform: translateY(-1px); + border-color: rgba(126, 200, 255, 0.18); + } + + .sidebar-link.active { + border-color: rgba(126, 200, 255, 0.28); + background: rgba(126, 200, 255, 0.10); + } + + .sidebar-link-title { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + font-size: 14px; + font-weight: 600; + margin-bottom: 6px; + } + + .sidebar-link-sub { + color: var(--muted); + font-size: 12px; + line-height: 1.55; + } + + .sidebar-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 24px; + height: 20px; + padding: 0 8px; + border-radius: 999px; + background: rgba(255,255,255,0.08); + color: var(--cold); + font-size: 10px; + font-family: "IBM Plex Mono", monospace; + } + + .hub-content { + display: grid; + gap: 18px; + } + + .hub-pane { + display: none; + gap: 18px; + } + + .hub-pane.active { + display: grid; + } + + .pane-grid { + display: grid; + gap: 18px; + } + + .pane-grid.two-col { + grid-template-columns: minmax(0, 1.08fr) minmax(320px, 0.92fr); + } + + .pane-grid.equal { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .compact-panel-body { + display: grid; + gap: 14px; + } + + .monitor-strip { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; + } + + .monitor-card { + padding: 14px 16px; + border-radius: 16px; + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.06); + } + + .monitor-card .title { + color: var(--muted); + font-size: 11px; + margin-bottom: 8px; + font-family: "IBM Plex Mono", monospace; + text-transform: uppercase; + letter-spacing: 0.08em; + } + + .monitor-card .main { + font-family: "IBM Plex Mono", monospace; + font-size: 16px; + color: var(--text); + margin-bottom: 6px; + } + + .monitor-card .meta { + color: var(--muted); + font-size: 12px; + line-height: 1.6; + } + .hero { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(360px, 0.9fr); @@ -2211,6 +2372,7 @@ } @media (max-width: 1240px) { + .console-hub, .hero, .layout, .priority-layout, @@ -2219,11 +2381,23 @@ grid-template-columns: 1fr; } + .console-sidebar { + position: static; + } + + .sidebar-nav { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .platform-grid, .signal-grid, .ops-grid, .coord-grid, - .position-card-grid { + .position-card-grid, + .pane-grid.two-col, + .pane-grid.equal, + .monitor-strip, + .runtime-summary-grid { grid-template-columns: 1fr; } @@ -2251,6 +2425,10 @@ padding-top: 12px; } + .sidebar-nav { + grid-template-columns: 1fr; + } + .hero-main, .hero-side, .panel, @@ -2294,7 +2472,7 @@
System Console

交易系统 总控台

- 统一观察信号流、执行层、三端账户风险和平台熔断状态。 + 统一观察信号流、执行层、多账户风险和平台熔断状态。 这个页面的目标不是展示“历史”,而是让你在一屏内判断系统现在是不是健康、哪里堵住了、哪里需要人工接管。

@@ -2327,8 +2505,34 @@
-
-
+
+ + +
@@ -2336,6 +2540,9 @@

系统健康总览

先判断系统活着没有,再判断风险和堵点
+
+ +
分析状态
-
@@ -2351,297 +2558,283 @@
-
-
-
- -

指挥台视图

-
先看系统机会与风险,再下钻到运行细节和日志
-
-
- -
- - - - -
-
-
- -
-
-
-
-
- -

当前最重要的问题

-
先回答现在最需要你看的是什么,以及你该做什么
-
-
-
正在整理当前优先级...
-
-
-
-
- -

系统运营指标

-
看系统过去一段时间是否真的有分析、信号和执行产出
-
-
-
正在汇总运营指标...
-
-
-
-
- -

信号生命周期

-
把信号、执行结果、挂单和持仓串起来,不再分散查看
-
-
-
正在整理信号生命周期...
-
-
-
-
- -

最近决策预览

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

平台执行概览

-
资金、杠杆、持仓、挂单、回撤阈值
-
-
-
正在加载平台状态...
-
-
-
-
-
-
- -

待处理清单

-
首屏的辅助核对区,完整优先级已提升到左侧顶部
-
-
-
正在汇总待处理事项...
-
-
-
-
- -

最近信号流

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

平台停机 / 熔断

-
风险触发后,这里应当最先看到
+
+
+
+
+
+ +

当前最重要的问题

+
先回答你现在最该关注什么,再决定是否继续下钻
-
-
正在读取平台停机状态...
+
+
+
+
正在整理当前优先级...
+
+
+
正在汇总运营指标...
+
+
+
+ +
+
+
+
+ +

平台执行概览

+
统一看平台权益、仓位、挂单、停机与自动交易状态
+
+
+
+
正在加载平台状态...
-
-
- -

最近阻塞原因

-
未落单、风险过滤、执行限制统一归因
+ +
+
+
+ +

待处理清单

+
保留给人工干预和快速核对的辅助视图
+
+
+
+
正在汇总待处理事项...
+
+
+
+
+
+ +
+
+
+
+
+ +

执行与资产

+
以后不再看 trading 页面,这里承接持仓、挂单和执行状态
+
+
+
+
+
价格链路
+
等待刷新
+
状态加载中
+
+
+
模拟盘执行链路
+
等待刷新
+
状态加载中
+
+
+
+ +
+
+
+ +

统一持仓视图

+
跨平台持仓合并展示,优先看风险、盈亏和保护完整度
+
+
+
+
+
正在整理跨平台持仓...
+
+
+ +
+
+
+ +

统一挂单视图

+
入场单、保护单、资金占用和老化状态集中查看
+
+
+
+
+
正在整理跨平台挂单...
+
+
+
+
+ +
+
+
+
+
+ +

信号与决策

+
把最近信号、生命周期、预览和缓存收敛到一个入口
+
+
+
+
正在整理信号生命周期...
+
+
+ +
+
+
+
+ +

最近决策预览

+
看系统准备做什么,以及不同执行目标如何响应
+
+
+
+
正在读取决策预览...
+
+
+ +
+
+
+ +

最近信号流

+
压缩展示最新机会,但保留关键信号细节
+
+
+
+
正在读取信号...
+
+
+
+ +
+
+
+
+ +

Crypto Agent 状态

+
最近信号缓存、统计和辅助协同信息
+
+
+
+
正在读取 Agent 状态...
+
+
+ +
+
+
+ +

补充视角

+
保留对照区,避免主区过度堆叠
+
+
+
+
+ 主视图已集中到本菜单 +
后续如需要补充“信号转执行”专题卡片,可以继续在这里加,不影响总览区。
+
+
+
+
+
+
+ +
+
+
+
+
+ +

分析心跳与调度

+
确认系统持续在分析,而不是单纯没有信号
+
+
+
+
最近心跳-
+
最近轮次-
+
当前进度-
+
下一次运行-
+
+
+
+
运行摘要
+
正在整理分析状态...
+
+
+
执行监管器
+
正在整理执行监管状态...
+
+
+
+ +
+
+
+ +

执行监管目标

+
监管 target、保护单补救和持仓管理能力集中展示
+
+
+
+
正在读取执行监管目标...
+
+
+
+
+ +
+
+
+
+
+ +

风险与阻塞

+
停机、熔断、执行阻塞统一在这里看,不再散落在各处
+
+
+
+
+
正在读取平台停机状态...
正在读取未落单汇总...
-
-
-
+
+
-
-
-
-
-
- -

分析心跳

-
没有信号时,也能确认系统仍在正常扫盘
-
-
-
最近心跳-
-
最近轮次-
-
当前进度-
-
下一次运行-
-
-
-
-
运行摘要
-
正在整理分析状态...
-
-
-
执行监管器
-
正在整理执行监管状态...
-
-
-
+
+
+
+ +

运行与执行日志

+
日志保留在独立菜单,按需查看
+
+
+ + +
-
-
-
- -

执行监管目标

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

运行与执行日志

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

补充工作区

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

Crypto Agent 状态

-
最近信号、平台停机、执行层状态
-
-
-
正在读取 Agent 状态...
-
-
-
-
- -

Agent / 账户协同

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

最近信号缓存

-
用于核对 Agent 最近缓存的信号,不再重复展示完整主信号流
-
-
-
- 主信号流已迁移到指挥台 -
最近信号流已放到上方“总览”主标签,这里保留为信号缓存核对区。
-
-
-
-
- -
-
-
- -

资产与挂单

-
统一持仓和统一挂单改为切换查看,减少表格同时占屏
-
-
- - -
-
- -
-
- -

统一持仓视图

-
三端持仓合并,优先看风险与盈亏
-
-
-
-
正在整理跨平台持仓...
-
-
- -
-
- -

统一挂单视图

-
入场单、保护单、资金占用一屏观察
-
-
-
-
正在整理跨平台挂单...
-
-
-