feat(trades, database): 优化交易记录查询与过滤逻辑

在 `trades.py` 中更新 `get_trades` 和 `get_trade_stats` 方法,增强了交易记录的查询功能,支持更多过滤选项(如 `limit`、`reconciled_only` 和 `include_sync`)。同时,调整了日志记录级别,从 `info` 改为 `debug`,以减少高负载时的日志输出。更新 `database/models.py` 中的 `get_all` 方法,新增参数以支持更灵活的查询,提升了系统的性能与稳定性。
This commit is contained in:
薇薇安 2026-02-18 22:22:53 +08:00
parent 7139b5de76
commit 33ac043324
2 changed files with 34 additions and 62 deletions

View File

@ -94,18 +94,16 @@ async def get_trades(
默认按平仓时间(time_filter=exit)今天= 今天平掉的单 + 今天开的未平仓更符合直觉
"""
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}")
logger.debug(f"获取交易记录: period={period}, symbol={symbol}, status={status}, limit={limit}, reconciled_only={reconciled_only}")
start_timestamp = None
end_timestamp = None
# 如果提供了 period使用快速时间段筛选
if period:
period_start, period_end = get_timestamp_range(period)
if period_start is not None and period_end is not None:
start_timestamp = period_start
end_timestamp = period_end
logger.info(f"使用快速时间段筛选: {period} -> {start_timestamp} ({datetime.fromtimestamp(start_timestamp)}) 至 {end_timestamp} ({datetime.fromtimestamp(end_timestamp)})")
elif start_date or end_date:
# 自定义时间段将日期字符串转换为Unix时间戳
beijing_tz = timezone(timedelta(hours=8))
@ -128,33 +126,14 @@ async def get_trades(
except ValueError:
logger.warning(f"无效的结束日期格式: {end_date}")
trades = Trade.get_all(start_timestamp, end_timestamp, symbol, status, trade_type, exit_reason, account_id=account_id, time_filter=time_filter or "exit")
if not include_sync:
trades = [
t for t in trades
if (t.get("entry_reason") or "") != "sync_recovered"
and (t.get("exit_reason") or "") != "sync"
]
# 仅可对账:有开仓订单号,已平仓的还须有平仓订单号,保证与币安可一一对应、统计真实
def _has_entry_order_id(t):
eid = t.get("entry_order_id")
return eid is not None and eid != "" and (eid != 0 if isinstance(eid, (int, float)) else True)
def _has_exit_order_id(t):
xid = t.get("exit_order_id")
return xid is not None and xid != "" and (xid != 0 if isinstance(xid, (int, float)) else True)
if reconciled_only:
before = len(trades)
trades = [
t for t in trades
if _has_entry_order_id(t)
and (t.get("status") != "closed" or _has_exit_order_id(t))
]
logger.info(f"可对账过滤: {before} -> {len(trades)}reconciled_only=True")
logger.info(f"查询到 {len(trades)} 条交易记录include_sync={include_sync}, reconciled_only={reconciled_only}")
trades = Trade.get_all(
start_timestamp, end_timestamp, symbol, status, trade_type, exit_reason,
account_id=account_id, time_filter=time_filter or "exit",
limit=limit, reconciled_only=reconciled_only, include_sync=include_sync,
)
# 格式化交易记录,添加平仓类型的中文显示
formatted_trades = []
for trade in trades[:limit]:
for trade in trades:
formatted_trade = dict(trade)
# 将 exit_reason 转换为中文显示
@ -181,7 +160,7 @@ async def get_trades(
formatted_trades.append(formatted_trade)
result = {
"total": len(trades),
"total": len(formatted_trades),
"trades": formatted_trades,
"filters": {
"start_timestamp": start_timestamp,
@ -195,7 +174,7 @@ async def get_trades(
}
}
logger.debug(f"返回交易记录: {len(result['trades'])} (限制: {limit})")
logger.debug(f"返回交易记录: {len(result['trades'])}")
return result
except Exception as e:
logger.error(f"获取交易记录失败: {e}", exc_info=True)
@ -215,12 +194,11 @@ async def get_trade_stats(
):
"""获取交易统计(默认按平仓时间统计:今日=今日平仓的盈亏,与订单记录筛选一致)"""
try:
logger.info(f"获取交易统计请求: start_date={start_date}, end_date={end_date}, period={period}, symbol={symbol}")
logger.debug(f"获取交易统计: period={period}, symbol={symbol}, reconciled_only={reconciled_only}")
start_timestamp = None
end_timestamp = None
# 如果提供了 period使用快速时间段筛选
if period:
period_start, period_end = get_timestamp_range(period)
if period_start is not None and period_end is not None:
@ -248,27 +226,12 @@ async def get_trade_stats(
except ValueError:
logger.warning(f"无效的结束日期格式: {end_date}")
trades = Trade.get_all(start_timestamp, end_timestamp, symbol, None, account_id=account_id, time_filter=time_filter or "exit")
if not include_sync:
trades = [
t for t in trades
if (t.get("entry_reason") or "") != "sync_recovered"
and (t.get("exit_reason") or "") != "sync"
]
if reconciled_only:
before = len(trades)
def _has_eid(t):
eid = t.get("entry_order_id")
return eid is not None and eid != "" and (eid != 0 if isinstance(eid, (int, float)) else True)
def _has_xid(t):
xid = t.get("exit_order_id")
return xid is not None and xid != "" and (xid != 0 if isinstance(xid, (int, float)) else True)
trades = [
t for t in trades
if _has_eid(t) and (t.get("status") != "closed" or _has_xid(t))
]
logger.info(f"统计可对账过滤: {before} -> {len(trades)}reconciled_only=True")
closed_trades = [t for t in trades if t['status'] == 'closed']
trades = Trade.get_all(
start_timestamp, end_timestamp, symbol, None,
account_id=account_id, time_filter=time_filter or "exit",
limit=None, reconciled_only=reconciled_only, include_sync=include_sync,
)
closed_trades = [t for t in trades if t.get("status") == "closed"]
# 辅助函数:计算净盈亏(优先使用 realized_pnl - commission
def get_net_pnl(t):
@ -379,12 +342,10 @@ async def get_trade_stats(
}
}
logger.info(
f"交易统计: 总交易数={stats['total_trades']}, 已平仓={stats['closed_trades']}, "
f"有意义交易={stats['meaningful_trades']}, 0盈亏交易={stats['zero_pnl_trades']}, "
f"胜率={stats['win_rate']:.2f}%, 总盈亏={stats['total_pnl']:.2f} USDT"
logger.debug(
f"交易统计: total={stats['total_trades']}, closed={stats['closed_trades']}, "
f"win_rate={stats['win_rate']:.2f}%, total_pnl={stats['total_pnl']:.2f}"
)
return stats
except Exception as e:
logger.error(f"获取交易统计失败: {e}", exc_info=True)

View File

@ -988,13 +988,16 @@ class Trade:
return False
@staticmethod
def get_all(start_timestamp=None, end_timestamp=None, symbol=None, status=None, trade_type=None, exit_reason=None, account_id: int = None, time_filter: str = "exit"):
def get_all(start_timestamp=None, end_timestamp=None, symbol=None, status=None, trade_type=None, exit_reason=None, account_id: int = None, time_filter: str = "exit", limit: int = None, reconciled_only: bool = False, include_sync: bool = True):
"""
获取交易记录
time_filter: 时间范围按哪种时间筛选
- "exit": 按平仓时间已平仓用 exit_time未平仓用 entry_time今天= 今天平掉的单 + 今天开的未平仓更符合直觉
- "entry": 按开仓时间
- "both": 原逻辑COALESCE(exit_time, entry_time)
limit: 最多返回条数None 表示不限制
reconciled_only: 仅可对账 entry_order_id已平仓的还有 exit_order_id SQL 中过滤以减轻负载
include_sync: 是否包含 entry_reason=sync_recovered / exit_reason=sync 的记录 SQL 中过滤
"""
query = "SELECT * FROM trades WHERE 1=1"
params = []
@ -1006,10 +1009,16 @@ class Trade:
params.append(int(account_id or DEFAULT_ACCOUNT_ID))
except Exception:
pass
if reconciled_only and _table_has_column("trades", "entry_order_id"):
query += " AND entry_order_id IS NOT NULL AND entry_order_id != 0"
query += " AND (status != 'closed' OR (exit_order_id IS NOT NULL AND exit_order_id != 0))"
if include_sync is False:
query += " AND (entry_reason IS NULL OR entry_reason != 'sync_recovered')"
query += " AND (exit_reason IS NULL OR exit_reason != 'sync')"
if start_timestamp is not None and end_timestamp is not None:
if time_filter == "exit":
# 按平仓时间:已平仓看 exit_time未平仓看 entry_time
query += " AND ((status = 'closed' AND exit_time >= %s AND exit_time <= %s) OR (status != 'closed' AND entry_time >= %s AND entry_time <= %s))"
params.extend([start_timestamp, end_timestamp, start_timestamp, end_timestamp])
elif time_filter == "entry":
@ -1052,9 +1061,11 @@ class Trade:
query += " AND exit_reason = %s"
params.append(exit_reason)
# 按平仓时间倒序(已平仓的按 exit_time未平仓的按 entry_time
query += " ORDER BY COALESCE(exit_time, entry_time) DESC, id DESC"
logger.info(f"查询交易记录: time_filter={time_filter}, {query}, {params}")
if limit is not None and limit > 0:
query += " LIMIT %s"
params.append(int(limit))
logger.debug(f"查询交易记录: time_filter={time_filter}, limit={limit}, reconciled_only={reconciled_only}, include_sync={include_sync}")
result = db.execute_query(query, params)
return result