""" 模拟交易 API """ from fastapi import APIRouter, HTTPException, Query from typing import Optional from datetime import datetime from pydantic import BaseModel from app.services.paper_trading_service import get_paper_trading_service from app.services.price_monitor_service import get_price_monitor_service from app.utils.logger import logger router = APIRouter(prefix="/api/paper-trading", tags=["模拟交易"]) class CloseOrderRequest(BaseModel): """手动平仓请求""" exit_price: float 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"), limit: int = Query(100, description="返回数量限制") ): """ 获取订单列表 - symbol: 可选,按交易对筛选 - status: 可选,active=活跃订单, closed=已平仓订单 - limit: 返回数量限制,默认100 """ try: service = get_paper_trading_service() if status == "active": orders = service.get_active_orders(symbol) elif status == "closed": orders = service.get_order_history(symbol, limit) else: # 返回所有订单 active = service.get_active_orders(symbol) history = service.get_order_history(symbol, limit) orders = active + history 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/active") async def get_active_orders( symbol: Optional[str] = Query(None, description="交易对筛选") ): """获取活跃订单""" try: service = get_paper_trading_service() orders = service.get_active_orders(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_paper_trading_service() order = service.get_order_by_id(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.post("/orders/{order_id}/close") async def close_order(order_id: str, request: CloseOrderRequest): """ 手动平仓 - order_id: 订单ID - exit_price: 平仓价格 """ try: service = get_paper_trading_service() result = service.close_order_manual(order_id, request.exit_price) if not result: raise HTTPException(status_code=404, detail="订单不存在或已平仓") return { "success": True, "message": "平仓成功", "result": result } except HTTPException: raise except Exception as e: logger.error(f"手动平仓失败: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/statistics") async def get_statistics( symbol: Optional[str] = Query(None, description="交易对筛选"), start_date: Optional[str] = Query(None, description="开始日期 (YYYY-MM-DD)"), end_date: Optional[str] = Query(None, description="结束日期 (YYYY-MM-DD)") ): """ 获取交易统计 - symbol: 可选,按交易对筛选 - start_date: 可选,开始日期 - end_date: 可选,结束日期 """ try: service = get_paper_trading_service() # 解析日期 start = datetime.strptime(start_date, "%Y-%m-%d") if start_date else None end = datetime.strptime(end_date, "%Y-%m-%d") if end_date else None stats = service.calculate_statistics(symbol, start, end) return { "success": True, "statistics": stats } except ValueError as e: raise HTTPException(status_code=400, detail=f"日期格式错误: {e}") except Exception as e: logger.error(f"获取统计数据失败: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/statistics/by-grade") async def get_statistics_by_grade(): """按信号等级获取统计""" try: service = get_paper_trading_service() stats = service.calculate_statistics() return { "success": True, "by_grade": stats.get("by_grade", {}) } except Exception as e: logger.error(f"获取等级统计失败: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/statistics/by-symbol") async def get_statistics_by_symbol(): """按交易对获取统计""" try: service = get_paper_trading_service() stats = service.calculate_statistics() return { "success": True, "by_symbol": stats.get("by_symbol", {}) } except Exception as e: logger.error(f"获取交易对统计失败: {e}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/monitor/status") async def get_monitor_status(): """获取价格监控状态""" try: monitor = get_price_monitor_service() return { "success": True, "running": monitor.is_running(), "subscribed_symbols": monitor.get_subscribed_symbols(), "latest_prices": monitor.latest_prices } except Exception as e: logger.error(f"获取监控状态失败: {e}") raise HTTPException(status_code=500, detail=str(e))