""" 系统状态 API """ from datetime import datetime, timedelta from fastapi import APIRouter, HTTPException from typing import Dict, Any from app.utils.logger import logger from app.utils.system_status import get_system_monitor 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_bitget_live_service from app.services.hyperliquid_trading_service import get_hyperliquid_service router = APIRouter() def _parse_signal_timestamp(value: Any) -> datetime | None: if value is None: return None if isinstance(value, datetime): return value.replace(tzinfo=None) if value.tzinfo else value text = str(value).replace("Z", "+00:00") try: parsed = datetime.fromisoformat(text) return parsed.replace(tzinfo=None) if parsed.tzinfo else parsed except ValueError: return None @router.get("/status", response_model=Dict[str, Any]) async def get_system_status(): """ 获取系统状态 返回所有 Agent 的运行状态和系统信息 """ try: monitor = get_system_monitor() summary = monitor.get_summary() # 添加额外的系统信息 response = { "status": "success", "data": summary } return response except Exception as e: logger.error(f"获取系统状态失败: {e}") raise HTTPException(status_code=500, detail=f"获取系统状态失败: {str(e)}") @router.get("/status/summary") async def get_status_summary(): """ 获取系统状态摘要 返回简化的状态信息,用于快速检查 """ try: monitor = get_system_monitor() summary = monitor.get_summary() return { "status": "success", "data": { "total_agents": summary["total_agents"], "running_agents": summary["running_agents"], "error_agents": summary["error_agents"], "uptime_seconds": summary["uptime_seconds"], "agents": { agent_id: { "name": info["name"], "status": info["status"] } for agent_id, info in summary["agents"].items() } } } except Exception as e: logger.error(f"获取状态摘要失败: {e}") raise HTTPException(status_code=500, detail=f"获取状态摘要失败: {str(e)}") @router.get("/status/agents/{agent_id}") async def get_agent_status(agent_id: str): """ 获取指定 Agent 的详细状态 Args: agent_id: Agent ID (如: crypto_agent, stock_agent) """ try: monitor = get_system_monitor() agent_info = monitor.get_agent_status(agent_id) if agent_info is None: raise HTTPException(status_code=404, detail=f"Agent '{agent_id}' 不存在") return { "status": "success", "data": agent_info.to_dict() } except HTTPException: raise except Exception as e: logger.error(f"获取 Agent 状态失败: {e}") raise HTTPException(status_code=500, detail=f"获取 Agent 状态失败: {str(e)}") @router.get("/console", response_model=Dict[str, Any]) async def get_console_snapshot(): """ 获取总控台快照 聚合系统运行态、信号统计、模拟盘与实盘平台状态,供总控台页面使用。 """ try: monitor = get_system_monitor() summary = monitor.get_summary() now = datetime.now() signal_db = get_signal_db_service() signal_stats = signal_db.get_signal_stats(days=7) latest_signals = signal_db.get_latest_signals(limit=12, days=3) crypto_agent = get_crypto_agent() crypto_status = crypto_agent.get_status() paper_service = get_paper_trading_service() paper_account = paper_service.get_account_status() paper_orders = paper_service.get_active_orders() paper_positions = [o for o in paper_orders if o.get('status') == 'open'] paper_pending = [o for o in paper_orders if o.get('status') == 'pending'] paper_stats = paper_service.calculate_statistics() bitget_service = get_bitget_live_service() bitget_summary = {"enabled": False} if bitget_service is not None: bg_account = bitget_service.get_account_state() bg_positions = bitget_service.get_open_positions() bg_orders = bitget_service.get_open_orders() bg_total_position_value = sum(abs(p["size"]) * p["entry_price"] for p in bg_positions) bg_drawdown = 0.0 if bitget_service.initial_balance and bitget_service.initial_balance > 0: bg_drawdown = (bitget_service.initial_balance - bg_account["account_value"]) / bitget_service.initial_balance * 100 bitget_summary = { "enabled": True, "account": { "account_value": bg_account.get("account_value", 0), "available_balance": bg_account.get("available_balance", 0), "total_margin_used": bg_account.get("total_margin_used", 0), "initial_balance": bitget_service.initial_balance, }, "positions": { "count": len(bg_positions), "total_value": bg_total_position_value, "items": bg_positions[:8], }, "orders": { "count": len(bg_orders), "entry_orders": len([o for o in bg_orders if not o.get("is_reduce_only")]), "tp_sl_orders": len([o for o in bg_orders if o.get("is_reduce_only")]), "items": bg_orders[:8], }, "risk": { "current_leverage": bg_total_position_value / bg_account["account_value"] if bg_account.get("account_value", 0) > 0 else 0, "max_leverage": bitget_service.max_total_leverage, "drawdown_percent": bg_drawdown, "circuit_breaker_threshold": bitget_service.circuit_breaker_drawdown * 100, }, } hyperliquid_service = get_hyperliquid_service() hyperliquid_summary = {"enabled": False} if hyperliquid_service is not None: hl_account = hyperliquid_service.get_account_state() hl_positions = hyperliquid_service.get_open_positions() hl_orders = hyperliquid_service.get_open_orders() hl_total_position_value = sum(abs(p["size"]) * p["entry_price"] for p in hl_positions) hl_drawdown = 0.0 if hyperliquid_service.initial_balance and hyperliquid_service.initial_balance > 0: hl_drawdown = (hyperliquid_service.initial_balance - hl_account["account_value"]) / hyperliquid_service.initial_balance * 100 hyperliquid_summary = { "enabled": True, "account": { "account_value": hl_account.get("account_value", 0), "available_balance": hl_account.get("available_balance", 0), "total_margin_used": hl_account.get("total_margin_used", 0), "initial_balance": hyperliquid_service.initial_balance, }, "positions": { "count": len(hl_positions), "total_value": hl_total_position_value, "items": hl_positions[:8], }, "orders": { "count": len(hl_orders), "entry_orders": len([o for o in hl_orders if not o.get("is_reduce_only")]), "tp_sl_orders": len([o for o in hl_orders if o.get("is_reduce_only")]), "items": hl_orders[:8], }, "risk": { "current_leverage": hl_total_position_value / hl_account["account_value"] if hl_account.get("account_value", 0) > 0 else 0, "max_leverage": hyperliquid_service.max_total_leverage, "drawdown_percent": hl_drawdown, "circuit_breaker_threshold": hyperliquid_service.circuit_breaker_drawdown * 100, }, } recent_cutoff = now - timedelta(minutes=30) recent_signal_count = sum( 1 for signal in latest_signals if (_parse_signal_timestamp(signal.get("created_at")) or datetime.min) >= recent_cutoff ) return { "status": "success", "data": { "generated_at": now.isoformat(), "system": summary, "crypto_agent": crypto_status, "execution_events": crypto_agent.get_recent_execution_events(limit=40), "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, }, } } except Exception as e: logger.error(f"获取总控台快照失败: {e}") raise HTTPException(status_code=500, detail=f"获取总控台快照失败: {str(e)}")