265 lines
10 KiB
Python
265 lines
10 KiB
Python
"""
|
|
系统状态 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)}")
|