251 lines
7.5 KiB
Python
251 lines
7.5 KiB
Python
"""
|
||
模拟交易 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.services.binance_service import binance_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()
|
||
paper_trading = get_paper_trading_service()
|
||
|
||
# 获取活跃订单的交易对
|
||
active_orders = paper_trading.get_active_orders()
|
||
symbols_needed = set(order.get('symbol') for order in active_orders if order.get('symbol'))
|
||
|
||
# 获取价格 - 优先使用监控服务的缓存价格
|
||
latest_prices = dict(monitor.latest_prices)
|
||
|
||
# 如果监控服务没有价格数据,直接从 Binance 获取
|
||
for symbol in symbols_needed:
|
||
if symbol not in latest_prices or latest_prices[symbol] is None:
|
||
price = binance_service.get_current_price(symbol)
|
||
if price:
|
||
latest_prices[symbol] = price
|
||
|
||
# 注意:止盈止损检查由后台任务 (main.py price_monitor_loop) 处理
|
||
# 这里只返回价格数据供前端显示
|
||
|
||
return {
|
||
"success": True,
|
||
"running": True, # 后台任务始终运行
|
||
"subscribed_symbols": list(symbols_needed),
|
||
"latest_prices": latest_prices
|
||
}
|
||
except Exception as e:
|
||
logger.error(f"获取监控状态失败: {e}")
|
||
raise HTTPException(status_code=500, detail=str(e))
|
||
|
||
|
||
@router.post("/reset")
|
||
async def reset_paper_trading():
|
||
"""
|
||
重置所有模拟交易数据
|
||
|
||
警告:此操作将删除所有订单记录,不可恢复!
|
||
"""
|
||
try:
|
||
service = get_paper_trading_service()
|
||
result = service.reset_all_data()
|
||
|
||
return {
|
||
"success": True,
|
||
"message": f"模拟交易数据已重置,删除 {result['deleted_count']} 条订单",
|
||
"result": result
|
||
}
|
||
except Exception as e:
|
||
logger.error(f"重置模拟交易数据失败: {e}")
|
||
raise HTTPException(status_code=500, detail=str(e))
|