""" 统计分析API """ from fastapi import APIRouter, Query import sys from pathlib import Path from datetime import datetime, timedelta import logging project_root = Path(__file__).parent.parent.parent.parent sys.path.insert(0, str(project_root)) sys.path.insert(0, str(project_root / 'backend')) from database.models import AccountSnapshot, Trade, MarketScan, TradingSignal from fastapi import HTTPException logger = logging.getLogger(__name__) router = APIRouter() @router.get("/performance") async def get_performance_stats(days: int = Query(7, ge=1, le=365)): """获取性能统计""" try: # 账户快照 snapshots = AccountSnapshot.get_recent(days) # 交易统计 start_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d') trades = Trade.get_all(start_date=start_date) return { "snapshots": snapshots, "trades": trades, "period": f"Last {days} days" } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/dashboard") async def get_dashboard_data(): """获取仪表板数据""" try: account_data = None account_error = None # 优先尝试获取实时账户数据 try: from api.routes.account import get_realtime_account_data account_data = await get_realtime_account_data() logger.info("成功获取实时账户数据") except HTTPException as e: # HTTPException 需要特殊处理,提取错误信息 account_error = e.detail logger.warning(f"获取实时账户数据失败 (HTTP {e.status_code}): {account_error}") # 回退到数据库快照 try: snapshots = AccountSnapshot.get_recent(1) if snapshots: account_data = { "total_balance": snapshots[0].get('total_balance', 0), "available_balance": snapshots[0].get('available_balance', 0), "total_position_value": snapshots[0].get('total_position_value', 0), "total_pnl": snapshots[0].get('total_pnl', 0), "open_positions": snapshots[0].get('open_positions', 0) } logger.info("使用数据库快照作为账户数据") else: logger.warning("数据库中没有账户快照数据") except Exception as db_error: logger.error(f"从数据库获取账户快照失败: {db_error}") except Exception as e: account_error = str(e) logger.warning(f"获取实时账户数据失败: {account_error}", exc_info=True) # 回退到数据库快照 try: snapshots = AccountSnapshot.get_recent(1) if snapshots: account_data = { "total_balance": snapshots[0].get('total_balance', 0), "available_balance": snapshots[0].get('available_balance', 0), "total_position_value": snapshots[0].get('total_position_value', 0), "total_pnl": snapshots[0].get('total_pnl', 0), "open_positions": snapshots[0].get('open_positions', 0) } logger.info("使用数据库快照作为账户数据") except Exception as db_error: logger.error(f"从数据库获取账户快照失败: {db_error}") # 获取持仓数据(优先实时,回退到数据库) open_trades = [] positions_error = None try: from api.routes.account import get_realtime_positions positions = await get_realtime_positions() # 转换为前端需要的格式 open_trades = positions logger.info(f"成功获取实时持仓数据: {len(open_trades)} 个持仓") except HTTPException as e: positions_error = e.detail logger.warning(f"获取实时持仓失败 (HTTP {e.status_code}): {positions_error}") # 回退到数据库记录 try: db_trades = Trade.get_all(status='open')[:10] # 格式化数据库记录,添加 entry_value_usdt 字段 open_trades = [] for trade in db_trades: entry_value_usdt = float(trade.get('quantity', 0)) * float(trade.get('entry_price', 0)) formatted_trade = { **trade, 'entry_value_usdt': entry_value_usdt, 'mark_price': trade.get('entry_price', 0), # 数据库中没有标记价,使用入场价 'pnl': trade.get('pnl', 0), 'pnl_percent': trade.get('pnl_percent', 0) } open_trades.append(formatted_trade) logger.info(f"使用数据库记录作为持仓数据: {len(open_trades)} 个持仓") except Exception as db_error: logger.error(f"从数据库获取持仓记录失败: {db_error}") except Exception as e: positions_error = str(e) logger.warning(f"获取实时持仓失败: {positions_error}", exc_info=True) # 回退到数据库记录 try: db_trades = Trade.get_all(status='open')[:10] # 格式化数据库记录,添加 entry_value_usdt 字段 open_trades = [] for trade in db_trades: entry_value_usdt = float(trade.get('quantity', 0)) * float(trade.get('entry_price', 0)) formatted_trade = { **trade, 'entry_value_usdt': entry_value_usdt, 'mark_price': trade.get('entry_price', 0), # 数据库中没有标记价,使用入场价 'pnl': trade.get('pnl', 0), 'pnl_percent': trade.get('pnl_percent', 0) } open_trades.append(formatted_trade) logger.info(f"使用数据库记录作为持仓数据: {len(open_trades)} 个持仓") except Exception as db_error: logger.error(f"从数据库获取持仓记录失败: {db_error}") # 最近的扫描记录 recent_scans = [] try: recent_scans = MarketScan.get_recent(10) except Exception as e: logger.error(f"获取扫描记录失败: {e}") # 最近的信号 recent_signals = [] try: recent_signals = TradingSignal.get_recent(20) except Exception as e: logger.error(f"获取交易信号失败: {e}") result = { "account": account_data, "open_trades": open_trades, "recent_scans": recent_scans, "recent_signals": recent_signals } # 如果有错误,在响应中包含错误信息(但不影响返回) if account_error or positions_error: result["warnings"] = {} if account_error: result["warnings"]["account"] = account_error if positions_error: result["warnings"]["positions"] = positions_error return result except Exception as e: logger.error(f"获取仪表板数据失败: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"获取仪表板数据失败: {str(e)}")