auto_trade_sys/backend/api/routes/trades.py
薇薇安 209a5cd376 a
2026-01-15 10:06:32 +08:00

176 lines
7.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
交易记录API
"""
from fastapi import APIRouter, Query, HTTPException
from typing import Optional
from datetime import datetime, timedelta
import sys
from pathlib import Path
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 Trade
router = APIRouter()
# 在模块级别创建logger与其他路由文件保持一致
logger = logging.getLogger(__name__)
def get_date_range(period: Optional[str] = None):
"""
根据时间段参数返回开始和结束日期
Args:
period: 时间段 ('1d', '7d', '30d', 'custom')
Returns:
(start_date, end_date) 元组,格式为 'YYYY-MM-DD HH:MM:SS'
"""
end_date = datetime.now()
if period == '1d':
start_date = end_date - timedelta(days=1)
elif period == '7d':
start_date = end_date - timedelta(days=7)
elif period == '30d':
start_date = end_date - timedelta(days=30)
else:
return None, None
return start_date.strftime('%Y-%m-%d 00:00:00'), end_date.strftime('%Y-%m-%d %H:%M:%S')
@router.get("")
@router.get("/")
async def get_trades(
start_date: Optional[str] = Query(None, description="开始日期 (YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)"),
end_date: Optional[str] = Query(None, description="结束日期 (YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)"),
period: Optional[str] = Query(None, description="快速时间段筛选: '1d'(最近1天), '7d'(最近7天), '30d'(最近30天)"),
symbol: Optional[str] = Query(None, description="交易对筛选"),
trade_type: Optional[str] = Query(None, description="交易类型筛选: 'buy', 'sell'"),
exit_reason: Optional[str] = Query(None, description="平仓原因筛选: 'stop_loss', 'take_profit', 'trailing_stop', 'manual', 'sync'"),
status: Optional[str] = Query(None, description="状态筛选: 'open', 'closed', 'cancelled'"),
limit: int = Query(100, ge=1, le=1000, description="返回记录数限制")
):
"""
获取交易记录
支持两种筛选方式:
1. 快速时间段筛选:使用 period 参数 ('1d', '7d', '30d')
2. 自定义时间段筛选:使用 start_date 和 end_date 参数
如果同时提供了 period 和 start_date/end_dateperiod 优先级更高
"""
try:
logger.info(f"获取交易记录请求: start_date={start_date}, end_date={end_date}, period={period}, symbol={symbol}, status={status}, limit={limit}")
# 如果提供了 period使用快速时间段筛选
if period:
period_start, period_end = get_date_range(period)
if period_start and period_end:
start_date = period_start
end_date = period_end
logger.info(f"使用快速时间段筛选: {period} -> {start_date}{end_date}")
# 格式化日期(如果只提供了日期,添加时间部分)
if start_date and len(start_date) == 10: # YYYY-MM-DD
start_date = f"{start_date} 00:00:00"
if end_date and len(end_date) == 10: # YYYY-MM-DD
end_date = f"{end_date} 23:59:59"
trades = Trade.get_all(start_date, end_date, symbol, status)
logger.info(f"查询到 {len(trades)} 条交易记录")
# 格式化交易记录,添加平仓类型的中文显示
formatted_trades = []
for trade in trades[:limit]:
formatted_trade = dict(trade)
# 将 exit_reason 转换为中文显示
exit_reason = trade.get('exit_reason', '')
if exit_reason:
exit_reason_map = {
'manual': '手动平仓',
'stop_loss': '自动平仓(止损)',
'take_profit': '自动平仓(止盈)',
'trailing_stop': '自动平仓(移动止损)',
'sync': '同步平仓'
}
formatted_trade['exit_reason_display'] = exit_reason_map.get(exit_reason, exit_reason)
else:
formatted_trade['exit_reason_display'] = ''
formatted_trades.append(formatted_trade)
result = {
"total": len(trades),
"trades": formatted_trades,
"filters": {
"start_date": start_date,
"end_date": end_date,
"period": period,
"symbol": symbol,
"status": status
}
}
logger.debug(f"返回交易记录: {len(result['trades'])} 条 (限制: {limit})")
return result
except Exception as e:
logger.error(f"获取交易记录失败: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get("/stats")
async def get_trade_stats(
start_date: Optional[str] = Query(None, description="开始日期 (YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)"),
end_date: Optional[str] = Query(None, description="结束日期 (YYYY-MM-DD 或 YYYY-MM-DD HH:MM:SS)"),
period: Optional[str] = Query(None, description="快速时间段筛选: '1d', '7d', '30d'"),
symbol: Optional[str] = Query(None, description="交易对筛选")
):
"""获取交易统计"""
try:
logger.info(f"获取交易统计请求: start_date={start_date}, end_date={end_date}, period={period}, symbol={symbol}")
# 如果提供了 period使用快速时间段筛选
if period:
period_start, period_end = get_date_range(period)
if period_start and period_end:
start_date = period_start
end_date = period_end
# 格式化日期
if start_date and len(start_date) == 10:
start_date = f"{start_date} 00:00:00"
if end_date and len(end_date) == 10:
end_date = f"{end_date} 23:59:59"
trades = Trade.get_all(start_date, end_date, symbol, None)
closed_trades = [t for t in trades if t['status'] == 'closed']
win_trades = [t for t in closed_trades if float(t['pnl']) > 0]
stats = {
"total_trades": len(trades),
"closed_trades": len(closed_trades),
"open_trades": len(trades) - len(closed_trades),
"win_trades": len(win_trades),
"loss_trades": len(closed_trades) - len(win_trades),
"win_rate": len(win_trades) / len(closed_trades) * 100 if closed_trades else 0,
"total_pnl": sum(float(t['pnl']) for t in closed_trades),
"avg_pnl": sum(float(t['pnl']) for t in closed_trades) / len(closed_trades) if closed_trades else 0,
"filters": {
"start_date": start_date,
"end_date": end_date,
"period": period,
"symbol": symbol
}
}
logger.info(f"交易统计: 总交易数={stats['total_trades']}, 已平仓={stats['closed_trades']}, 胜率={stats['win_rate']:.2f}%, 总盈亏={stats['total_pnl']:.2f} USDT")
return stats
except Exception as e:
logger.error(f"获取交易统计失败: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))