""" 实盘交易 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="数据源: trades=成交记录, orders=历史订单, exchange=历史订单"), limit: int = Query(100, description="返回数量限制") ): """ 获取实盘交易历史数据 - symbol: 可选,按交易对筛选 - status: 可选 - trades: 交易所成交记录(包含每笔成交和手续费) - orders: 交易所历史订单(包含订单状态) - exchange: 交易所历史订单(同 orders) - limit: 返回数量限制,默认100 """ try: trading_api = get_bitget_trading_api() if not trading_api: return { "success": False, "message": "Bitget API 未配置", "count": 0, "orders": [] } # 获取成交记录(推荐,包含盈亏信息) if status == "trades": orders = trading_api.get_closed_orders(symbol, limit) return { "success": True, "count": len(orders), "orders": orders, "source": "trades" } # 获取历史订单 if status in ["orders", "exchange"]: try: if symbol: ccxt_symbol = trading_api._standardize_symbol(symbol) orders = trading_api.exchange.fetch_closed_orders(ccxt_symbol, limit=limit) else: orders = trading_api.exchange.fetch_closed_orders(limit=limit) return { "success": True, "count": len(orders), "orders": orders, "source": "orders" } except Exception as e: logger.error(f"获取历史订单失败: {e}") return { "success": False, "message": f"获取历史订单失败: {str(e)}", "count": 0, "orders": [] } # 默认返回成交记录 orders = trading_api.get_closed_orders(symbol, limit) return { "success": True, "count": len(orders), "orders": orders, "source": "trades" } 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: logger.info("[stats] 开始获取统计数据") # 获取账户信息 account = {} trading_api = get_bitget_trading_api() logger.info(f"[stats] trading_api: {trading_api}") if trading_api: try: logger.info("[stats] 开始获取账户信息") balance_info = trading_api.get_balance() logger.info(f"[stats] balance_info: {balance_info}") 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)) # 获取持仓价值 logger.info("[stats] 开始获取持仓") positions = trading_api.get_position() logger.info(f"[stats] positions count: {len(positions)}") 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 } logger.info(f"[stats] account: {account}") except Exception as e: logger.error(f"[stats] 获取账户信息失败: {e}") import traceback logger.error(traceback.format_exc()) account = {} # 尝试从数据库获取统计 stats = { "total_trades": 0, "winning_trades": 0, "losing_trades": 0, "win_rate": 0, "total_pnl": 0, "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), } logger.info(f"[stats] 返回统计数据: {stats}") return { "success": True, "stats": stats } except Exception as e: logger.error(f"获取实盘交易统计失败: {e}") import traceback logger.error(traceback.format_exc()) 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 status["auto_trading_enabled"] = service.get_auto_trading_status() return { "success": True, "status": status } except Exception as e: logger.error(f"获取实盘交易服务状态失败: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.post("/auto-trading") async def set_auto_trading(enabled: bool = Query(..., description="是否启用自动交易")): """ 设置实盘自动交易开关 Args: enabled: true=启用自动交易,false=禁用自动交易 """ try: service = get_real_trading_service() if not service: raise HTTPException(status_code=404, detail="实盘交易服务未初始化,请检查 API 配置") success = service.set_auto_trading(enabled) if success: status_text = "启用" if enabled else "禁用" return { "success": True, "message": f"实盘自动交易已{status_text}", "auto_trading_enabled": enabled } else: raise HTTPException(status_code=500, detail="设置自动交易失败") except HTTPException: raise except Exception as e: logger.error(f"设置自动交易失败: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/auto-trading") async def get_auto_trading_status(): """获取实盘自动交易状态""" try: service = get_real_trading_service() if not service: return { "success": False, "message": "实盘交易服务未初始化", "auto_trading_enabled": False } enabled = service.get_auto_trading_status() return { "success": True, "auto_trading_enabled": enabled } except Exception as e: logger.error(f"获取自动交易状态失败: {e}") raise HTTPException(status_code=500, detail=str(e))