From b1e6215ae294cf17ce78f923623f6106814f3937 Mon Sep 17 00:00:00 2001
From: aaron <>
Date: Wed, 22 Apr 2026 11:03:24 +0800
Subject: [PATCH] 11
---
backend/app/api/system.py | 264 +++++++++++++++++--
frontend/admin.html | 37 +--
frontend/console.html | 308 ++++++++++++++++++++++-
frontend/css/style.css | 502 ++++++++++++++++++++++---------------
frontend/hyperliquid.html | 25 +-
frontend/index.html | 92 ++++---
frontend/login.html | 16 +-
frontend/real-trading.html | 33 ++-
frontend/signals.html | 77 ++----
frontend/status.html | 55 ++--
frontend/trading.html | 51 ++--
11 files changed, 1041 insertions(+), 419 deletions(-)
diff --git a/backend/app/api/system.py b/backend/app/api/system.py
index e067485..cde9433 100644
--- a/backend/app/api/system.py
+++ b/backend/app/api/system.py
@@ -28,6 +28,162 @@ def _parse_signal_timestamp(value: Any) -> datetime | None:
return None
+def _safe_float(value: Any, default: float = 0.0) -> float:
+ try:
+ if value is None or value == "":
+ return default
+ return float(value)
+ except (TypeError, ValueError):
+ return default
+
+
+def _normalize_platform_position(platform: str, position: Dict[str, Any]) -> Dict[str, Any]:
+ side_raw = str(position.get("side") or "").lower()
+ side = "long" if side_raw in {"buy", "long"} else "short"
+ symbol = position.get("symbol") or position.get("coin") or "-"
+ if isinstance(symbol, str) and symbol.endswith("USDTUSDT"):
+ symbol = symbol.replace("USDTUSDT", "USDT")
+
+ entry_price = _safe_float(position.get("entry_price") or position.get("filled_price"))
+ mark_price = _safe_float(position.get("mark_price") or position.get("current_price"))
+ if mark_price <= 0:
+ mark_price = entry_price
+
+ size = abs(_safe_float(position.get("size") or position.get("quantity")))
+ leverage = _safe_float(position.get("leverage"))
+ margin = _safe_float(position.get("margin") or position.get("initialMargin") or position.get("initial_margin"))
+ unrealized_pnl = _safe_float(position.get("unrealized_pnl") or position.get("pnl_amount"))
+ pnl_percent = _safe_float(position.get("unrealized_pnl_pct") or position.get("pnl_percent") or position.get("percentage"))
+
+ return {
+ "platform": platform,
+ "symbol": symbol,
+ "side": side,
+ "size": size,
+ "entry_price": entry_price,
+ "mark_price": mark_price,
+ "leverage": leverage,
+ "margin": margin,
+ "unrealized_pnl": unrealized_pnl,
+ "pnl_percent": pnl_percent,
+ "take_profit": position.get("take_profit"),
+ "stop_loss": position.get("stop_loss"),
+ "liquidation_price": position.get("liquidation_price"),
+ "opened_at": position.get("opened_at") or position.get("created_at"),
+ }
+
+
+def _normalize_platform_order(platform: str, order: Dict[str, Any]) -> Dict[str, Any]:
+ side_raw = str(order.get("side") or "").lower()
+ side = "long" if side_raw in {"buy", "long", "b"} else "short"
+ symbol = order.get("symbol") or order.get("coin") or "-"
+ if isinstance(symbol, str) and symbol.endswith("USDTUSDT"):
+ symbol = symbol.replace("USDTUSDT", "USDT")
+
+ price = _safe_float(order.get("price") or order.get("entry_price"))
+ size = abs(_safe_float(order.get("size") or order.get("quantity")))
+ order_type = str(order.get("order_type") or order.get("entry_type") or order.get("type") or "").lower()
+ status = str(order.get("status") or "").lower()
+ is_reduce_only = bool(order.get("is_reduce_only"))
+
+ category = "tp_sl" if is_reduce_only else "entry"
+ if platform == "paper" and status == "pending":
+ category = "entry"
+
+ return {
+ "platform": platform,
+ "symbol": symbol,
+ "side": side,
+ "category": category,
+ "price": price,
+ "size": size,
+ "leverage": _safe_float(order.get("leverage")),
+ "margin": _safe_float(order.get("margin")),
+ "order_type": order_type,
+ "status": status,
+ "stop_loss": order.get("stop_loss"),
+ "take_profit": order.get("take_profit"),
+ "signal_grade": order.get("signal_grade"),
+ "signal_type": order.get("signal_type"),
+ "confidence": _safe_float(order.get("confidence")),
+ "created_at": order.get("created_at") or order.get("timestamp"),
+ }
+
+
+def _build_attention_items(
+ platform_halts: Dict[str, Any],
+ platforms: Dict[str, Any],
+ unified_positions: list[Dict[str, Any]],
+ unified_orders: list[Dict[str, Any]],
+ execution_events: list[Dict[str, Any]],
+) -> list[Dict[str, Any]]:
+ items: list[Dict[str, Any]] = []
+
+ for platform, halt in (platform_halts or {}).items():
+ if halt and halt.get("halted"):
+ items.append({
+ "severity": "danger",
+ "title": f"{platform} 已停机",
+ "detail": halt.get("reason") or "平台已触发停机/熔断",
+ "timestamp": halt.get("halted_at"),
+ })
+
+ for platform_name, payload in (platforms or {}).items():
+ if payload.get("enabled") is False:
+ continue
+
+ risk = payload.get("risk") or {}
+ current_leverage = _safe_float(risk.get("current_leverage"))
+ max_leverage = _safe_float(risk.get("max_leverage"))
+ drawdown_pct = _safe_float(risk.get("drawdown_percent") or risk.get("drawdown"))
+ breaker_pct = _safe_float(risk.get("circuit_breaker_threshold"), 25.0)
+
+ if breaker_pct > 0 and drawdown_pct >= breaker_pct * 0.7:
+ items.append({
+ "severity": "warning",
+ "title": f"{platform_name} 回撤接近熔断",
+ "detail": f"当前回撤 {drawdown_pct:.1f}% / 阈值 {breaker_pct:.1f}%",
+ "timestamp": None,
+ })
+
+ if max_leverage > 0 and current_leverage >= max_leverage * 0.8:
+ items.append({
+ "severity": "warning",
+ "title": f"{platform_name} 杠杆占用偏高",
+ "detail": f"当前总杠杆 {current_leverage:.2f}x / 上限 {max_leverage:.2f}x",
+ "timestamp": None,
+ })
+
+ for pos in unified_positions:
+ if not pos.get("stop_loss") or not pos.get("take_profit"):
+ items.append({
+ "severity": "warning",
+ "title": f"{pos['platform']} {pos['symbol']} 风控不完整",
+ "detail": "持仓缺少止盈或止损,请确认执行层保护单状态",
+ "timestamp": pos.get("opened_at"),
+ })
+
+ pending_entry_count = sum(1 for order in unified_orders if order.get("category") == "entry")
+ if pending_entry_count > 0:
+ items.append({
+ "severity": "info",
+ "title": "存在待成交入场单",
+ "detail": f"当前共有 {pending_entry_count} 笔待成交入场单,建议关注价格接近度和资金占用。",
+ "timestamp": None,
+ })
+
+ for event in (execution_events or [])[:8]:
+ if event.get("status") in {"error", "warning"}:
+ items.append({
+ "severity": event.get("status"),
+ "title": f"{event.get('platform', '-')}: {event.get('event_type', '-')}",
+ "detail": event.get("reason") or "最近执行事件需要关注",
+ "timestamp": event.get("timestamp"),
+ })
+
+ return items[:12]
+
+
@router.get("/status", response_model=Dict[str, Any])
async def get_system_status():
"""
@@ -221,41 +377,101 @@ async def get_console_snapshot():
if (_parse_signal_timestamp(signal.get("created_at")) or datetime.min) >= recent_cutoff
)
+ paper_position_items = [
+ _normalize_platform_position("paper", pos)
+ for pos in paper_service.get_open_positions()[:12]
+ ]
+ paper_order_items = [
+ _normalize_platform_order("paper", order)
+ for order in paper_pending[:12]
+ ]
+
+ bitget_position_items = [
+ _normalize_platform_position("bitget", pos)
+ for pos in (bg_positions[:12] if bitget_service is not None else [])
+ ]
+ bitget_order_items = [
+ _normalize_platform_order("bitget", order)
+ for order in (bg_orders[:12] if bitget_service is not None else [])
+ ]
+
+ hyperliquid_position_items = [
+ _normalize_platform_position("hyperliquid", pos)
+ for pos in (hl_positions[:12] if hyperliquid_service is not None else [])
+ ]
+ hyperliquid_order_items = [
+ _normalize_platform_order("hyperliquid", order)
+ for order in (hl_orders[:12] if hyperliquid_service is not None else [])
+ ]
+
+ unified_positions = sorted(
+ paper_position_items + bitget_position_items + hyperliquid_position_items,
+ key=lambda item: _parse_signal_timestamp(item.get("opened_at")) or datetime.min,
+ reverse=True,
+ )
+ unified_orders = sorted(
+ paper_order_items + bitget_order_items + hyperliquid_order_items,
+ key=lambda item: _parse_signal_timestamp(item.get("created_at")) or datetime.min,
+ reverse=True,
+ )
+
+ platforms_payload = {
+ "paper": {
+ "enabled": True,
+ "account": paper_account,
+ "positions": {
+ "count": len(paper_positions),
+ "items": paper_positions[:8],
+ },
+ "orders": {
+ "count": len(paper_orders),
+ "pending_count": len(paper_pending),
+ "items": paper_pending[:8],
+ },
+ "statistics": {
+ "win_rate": paper_stats.get("win_rate", 0),
+ "total_trades": paper_stats.get("total_trades", 0),
+ "total_pnl": paper_stats.get("total_pnl", 0),
+ "max_drawdown": paper_stats.get("max_drawdown", 0),
+ "by_grade": paper_stats.get("by_grade", {}),
+ },
+ "risk": {
+ "current_leverage": paper_account.get("current_total_leverage", 0),
+ "max_leverage": paper_account.get("max_total_leverage", 0),
+ "drawdown_percent": paper_account.get("max_drawdown", 0),
+ "circuit_breaker_threshold": 25,
+ },
+ },
+ "bitget": bitget_summary,
+ "hyperliquid": hyperliquid_summary,
+ }
+
+ execution_events = crypto_agent.get_recent_execution_events(limit=40)
+ attention_items = _build_attention_items(
+ crypto_status.get("platform_halts", {}),
+ platforms_payload,
+ unified_positions,
+ unified_orders,
+ execution_events,
+ )
+
return {
"status": "success",
"data": {
"generated_at": now.isoformat(),
"system": summary,
"crypto_agent": crypto_status,
- "execution_events": crypto_agent.get_recent_execution_events(limit=40),
+ "execution_events": execution_events,
"signals": {
"stats_7d": signal_stats,
"latest": latest_signals,
"recent_30m_count": recent_signal_count,
},
- "platforms": {
- "paper": {
- "enabled": True,
- "account": paper_account,
- "positions": {
- "count": len(paper_positions),
- "items": paper_positions[:8],
- },
- "orders": {
- "count": len(paper_orders),
- "pending_count": len(paper_pending),
- "items": paper_pending[:8],
- },
- "statistics": {
- "win_rate": paper_stats.get("win_rate", 0),
- "total_trades": paper_stats.get("total_trades", 0),
- "total_pnl": paper_stats.get("total_pnl", 0),
- "max_drawdown": paper_stats.get("max_drawdown", 0),
- "by_grade": paper_stats.get("by_grade", {}),
- },
- },
- "bitget": bitget_summary,
- "hyperliquid": hyperliquid_summary,
+ "platforms": platforms_payload,
+ "management": {
+ "positions": unified_positions[:18],
+ "orders": unified_orders[:24],
+ "attention_items": attention_items,
},
}
}
diff --git a/frontend/admin.html b/frontend/admin.html
index 1b5f964..0036319 100644
--- a/frontend/admin.html
+++ b/frontend/admin.html
@@ -11,7 +11,7 @@
-
+
diff --git a/frontend/trading.html b/frontend/trading.html
index 6464a20..7837d9d 100644
--- a/frontend/trading.html
+++ b/frontend/trading.html
@@ -11,20 +11,21 @@
-
+