176 lines
7.3 KiB
Python
176 lines
7.3 KiB
Python
"""
|
||
交易记录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_date,period 优先级更高
|
||
"""
|
||
try:
|
||
logger.info(f"获取交易记录请求: start_date={start_date}, end_date={end_date}, period={period}, symbol={symbol}, status={status}, limit={limit}, trade_type={trade_type}, exit_reason={exit_reason}")
|
||
# 如果提供了 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, trade_type, exit_reason)
|
||
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))
|