""" 实盘交易 API """ from fastapi import APIRouter, HTTPException, Query from typing import Optional from datetime import datetime from pydantic import BaseModel from app.services.real_trading_service import get_real_trading_service from app.services.bitget_trading_api_sdk import get_bitget_trading_api from app.utils.logger import logger router = APIRouter(prefix="/api/real-trading", tags=["实盘交易"]) class OrderResponse(BaseModel): """订单响应""" success: bool message: str data: Optional[dict] = None @router.get("/orders") async def get_orders( symbol: Optional[str] = Query(None, description="交易对筛选"), status: Optional[str] = Query(None, description="状态筛选: active, closed, exchange"), limit: int = Query(100, description="返回数量限制") ): """ 获取实盘订单列表 - symbol: 可选,按交易对筛选 - status: 可选 - active: 本地数据库的活跃订单 - closed: 本地数据库的历史订单 - exchange: 交易所的历史订单(推荐) - limit: 返回数量限制,默认100 """ try: # 如果请求交易所历史订单,直接从交易所获取 if status == "exchange": trading_api = get_bitget_trading_api() if not trading_api: return { "success": False, "message": "Bitget API 未配置", "count": 0, "orders": [] } orders = trading_api.get_closed_orders(symbol, limit) return { "success": True, "count": len(orders), "orders": orders, "source": "exchange" } # 否则从本地数据库获取 service = get_real_trading_service() if not service: return { "success": False, "message": "实盘交易服务未启用", "count": 0, "orders": [] } if status == "active": orders = service.get_active_orders() elif status == "closed": # 从数据库获取历史订单 from app.services.db_service import db_service from app.models.real_trading import RealOrder from app.models.paper_trading import OrderStatus db = db_service.get_session() try: query = db.query(RealOrder).filter( RealOrder.status.in_([OrderStatus.CLOSED, OrderStatus.CANCELLED]) ) if symbol: query = query.filter(RealOrder.symbol == symbol) orders = [order.to_dict() for order in query.order_by( RealOrder.created_at.desc() ).limit(limit).all()] finally: db.close() else: # 返回所有订单 active = service.get_active_orders() # TODO: 获取历史订单 orders = active return { "success": True, "count": len(orders), "orders": orders, "source": "database" } except Exception as e: logger.error(f"获取实盘订单列表失败: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/orders/active") async def get_active_orders( symbol: Optional[str] = Query(None, description="交易对筛选") ): """获取活跃实盘订单""" try: service = get_real_trading_service() if not service: return { "success": False, "message": "实盘交易服务未启用", "count": 0, "orders": [] } orders = service.get_active_orders() # 如果指定了交易对,进行过滤 if symbol: orders = [o for o in orders if o.get('symbol') == symbol] return { "success": True, "count": len(orders), "orders": orders } except Exception as e: logger.error(f"获取活跃实盘订单失败: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/orders/{order_id}") async def get_order(order_id: str): """获取实盘订单详情""" try: service = get_real_trading_service() if not service: raise HTTPException(status_code=404, detail="实盘交易服务未启用") order = service.get_order(order_id) if not order: raise HTTPException(status_code=404, detail="订单不存在") return { "success": True, "order": order } except HTTPException: raise except Exception as e: logger.error(f"获取实盘订单详情失败: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/positions") async def get_positions(): """获取实盘持仓(从交易所同步)""" try: # 即使实盘交易未启用,也可以查看交易所持仓 trading_api = get_bitget_trading_api() if not trading_api: return { "success": False, "message": "Bitget API 未配置", "positions": [] } positions = trading_api.get_position() return { "success": True, "count": len(positions), "positions": positions } except Exception as e: logger.error(f"获取实盘持仓失败: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/account") async def get_account_status(): """获取实盘账户状态(即使实盘交易未启用也可查看)""" try: # 直接使用交易 API,不依赖实盘交易服务 trading_api = get_bitget_trading_api() if not trading_api: return { "success": False, "message": "Bitget API 未配置", "account": None } # 获取账户余额 balance_info = trading_api.get_balance() usdt_info = balance_info.get('USDT', {}) available = float(usdt_info.get('available', 0)) frozen = float(usdt_info.get('frozen', 0)) locked = float(usdt_info.get('locked', 0)) # 获取持仓价值 positions = trading_api.get_position() total_position_value = sum( float(p.get('notional', 0)) for p in positions ) account = { 'current_balance': available + frozen + locked, 'available': available, 'used_margin': frozen + locked, 'total_position_value': total_position_value } return { "success": True, "account": account } except Exception as e: logger.error(f"获取实盘账户状态失败: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/orders/{order_id}/close") async def close_order(order_id: str, exit_price: float = Query(..., description="平仓价格")): """ 手动平仓 注意:实盘交易通常由交易所自动执行止损/止盈, 此接口主要用于紧急情况下的手动平仓 """ try: service = get_real_trading_service() if not service: raise HTTPException(status_code=404, detail="实盘交易服务未启用") # 获取订单 order = service.get_order(order_id) if not order: raise HTTPException(status_code=404, detail="订单不存在") # 调用交易所API平仓 # TODO: 实现手动平仓逻辑 return { "success": True, "message": "平仓指令已发送", "order_id": order_id } except HTTPException: raise except Exception as e: logger.error(f"实盘手动平仓失败: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/stats") async def get_trading_stats(): """获取实盘交易统计""" try: service = get_real_trading_service() if not service: return { "success": False, "message": "实盘交易服务未启用", "stats": None } # 获取账户信息 account = service.get_account_status() # 获取历史订单统计 from app.services.db_service import db_service from app.models.real_trading import RealOrder from app.models.paper_trading import OrderStatus db = db_service.get_session() try: # 获取已平仓订单 closed_orders = db.query(RealOrder).filter( RealOrder.status == OrderStatus.CLOSED ).all() # 计算统计数据 total_trades = len(closed_orders) winning_trades = len([o for o in closed_orders if o.pnl > 0]) losing_trades = len([o for o in closed_orders if o.pnl < 0]) total_pnl = sum([o.pnl or 0 for o in closed_orders]) win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0 # 计算最大回撤等指标 # TODO: 实现更详细的统计 stats = { "total_trades": total_trades, "winning_trades": winning_trades, "losing_trades": losing_trades, "win_rate": round(win_rate, 2), "total_pnl": round(total_pnl, 2), "current_balance": account.get('current_balance', 0), "available": account.get('available', 0), "used_margin": account.get('used_margin', 0), "total_position_value": account.get('total_position_value', 0), } finally: db.close() return { "success": True, "stats": stats } except Exception as e: logger.error(f"获取实盘交易统计失败: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/status") async def get_service_status(): """获取实盘交易服务状态""" try: from app.config import get_settings settings = get_settings() service = get_real_trading_service() status = { "enabled": settings.real_trading_enabled, "api_configured": bool(settings.bitget_api_key and settings.bitget_api_secret), "use_testnet": settings.bitget_use_testnet, "service_running": service is not None, "max_single_position": settings.real_trading_max_single_position, "default_leverage": settings.real_trading_default_leverage, "max_orders": settings.real_trading_max_orders, } if service: account = service.get_account_status() status["account"] = account return { "success": True, "status": status } except Exception as e: logger.error(f"获取实盘交易服务状态失败: {e}") raise HTTPException(status_code=500, detail=str(e))