diff --git a/backend/api/routes/trades.py b/backend/api/routes/trades.py index e154d13..fe7e50c 100644 --- a/backend/api/routes/trades.py +++ b/backend/api/routes/trades.py @@ -79,7 +79,7 @@ async def get_trades( 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'"), - time_filter: str = Query("exit", description="时间范围按哪种时间筛选: 'exit'(按平仓时间,今日=今天平掉的单+今天开的未平仓), 'entry'(按开仓时间), 'both'(原逻辑)"), + time_filter: str = Query("exit", description="时间范围按哪种时间筛选: 'exit'(按平仓时间), 'entry'(按开仓时间,适合策略分析), 'created'(按创建时间), 'both'"), include_sync: bool = Query(True, description="是否包含 entry_reason 为 sync_recovered 的补建/同步单(默认包含,便于订单记录与币安一致)"), reconciled_only: bool = Query(True, description="仅返回可对账记录(有 entry_order_id,已平仓的还有 exit_order_id),与币安一致,统计真实"), limit: int = Query(100, ge=1, le=1000, description="返回记录数限制"), @@ -143,6 +143,10 @@ async def get_trades( 'manual': '手动平仓', 'stop_loss': '自动平仓(止损)', 'take_profit': '自动平仓(止盈)', + 'take_profit_partial_then_take_profit': '自动平仓(分步止盈后止盈)', + 'take_profit_partial_then_stop': '自动平仓(分步止盈后止损)', + 'take_profit_partial_then_trailing_stop': '自动平仓(分步止盈后移动止损)', + 'early_take_profit': '自动平仓(早止盈)', 'trailing_stop': '自动平仓(移动止损)', 'sync': '同步平仓' } @@ -191,7 +195,7 @@ async def get_trade_stats( 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', 'today', 'week', 'month'"), symbol: Optional[str] = Query(None, description="交易对筛选"), - time_filter: str = Query("exit", description="时间范围按哪种时间: 'exit'(按平仓时间,今日统计=今日平仓的盈亏), 'entry'(按开仓时间), 'both'"), + time_filter: str = Query("exit", description="时间范围按哪种时间: 'exit'(按平仓时间), 'entry'(按开仓时间), 'created'(按创建时间), 'both'"), include_sync: bool = Query(True, description="是否包含 entry_reason 为 sync_recovered 的补建/同步单(默认与订单记录一致)"), reconciled_only: bool = Query(True, description="仅统计可对账记录,与币安一致,避免系统盈利/币安亏损偏差"), ): diff --git a/backend/database/models.py b/backend/database/models.py index 7adf5d0..8331b63 100644 --- a/backend/database/models.py +++ b/backend/database/models.py @@ -1048,7 +1048,8 @@ class Trade: 获取交易记录。 time_filter: 时间范围按哪种时间筛选 - "exit": 按平仓时间(已平仓用 exit_time,未平仓用 entry_time)。选「今天」= 今天平掉的单 + 今天开的未平仓,更符合直觉。 - - "entry": 按开仓时间。 + - "entry": 按开仓时间(实际入场时间,适合策略分析:何时进场、持仓时长)。 + - "created": 按创建时间(记录写入 DB 的时间,略早于或等于成交时间;无 created_at 时回退为 entry)。 - "both": 原逻辑,COALESCE(exit_time, entry_time)。 limit: 最多返回条数,None 表示不限制。 reconciled_only: 仅可对账(有 entry_order_id,已平仓的还有 exit_order_id),在 SQL 中过滤以减轻负载。 @@ -1072,6 +1073,10 @@ class Trade: query += " AND (entry_reason IS NULL OR entry_reason != 'sync_recovered')" query += " AND (exit_reason IS NULL OR exit_reason != 'sync')" + # 按创建时间筛选时若表无 created_at 则回退为 entry_time + use_created = (time_filter == "created" and _table_has_column("trades", "created_at")) + time_col = "COALESCE(created_at, entry_time)" if use_created else None + if start_timestamp is not None and end_timestamp is not None: if time_filter == "exit": query += " AND ((status = 'closed' AND exit_time >= %s AND exit_time <= %s) OR (status != 'closed' AND entry_time >= %s AND entry_time <= %s))" @@ -1079,6 +1084,9 @@ class Trade: elif time_filter == "entry": query += " AND entry_time >= %s AND entry_time <= %s" params.extend([start_timestamp, end_timestamp]) + elif use_created: + query += " AND " + time_col + " >= %s AND " + time_col + " <= %s" + params.extend([start_timestamp, end_timestamp]) else: query += " AND COALESCE(exit_time, entry_time) >= %s AND COALESCE(exit_time, entry_time) <= %s" params.extend([start_timestamp, end_timestamp]) @@ -1089,6 +1097,9 @@ class Trade: elif time_filter == "entry": query += " AND entry_time >= %s" params.append(start_timestamp) + elif use_created: + query += " AND " + time_col + " >= %s" + params.append(start_timestamp) else: query += " AND COALESCE(exit_time, entry_time) >= %s" params.append(start_timestamp) @@ -1099,6 +1110,9 @@ class Trade: elif time_filter == "entry": query += " AND entry_time <= %s" params.append(end_timestamp) + elif use_created: + query += " AND " + time_col + " <= %s" + params.append(end_timestamp) else: query += " AND COALESCE(exit_time, entry_time) <= %s" params.append(end_timestamp) @@ -1116,7 +1130,10 @@ class Trade: query += " AND exit_reason = %s" params.append(exit_reason) - query += " ORDER BY COALESCE(exit_time, entry_time) DESC, id DESC" + if use_created: + query += " ORDER BY " + time_col + " DESC, id DESC" + else: + query += " ORDER BY COALESCE(exit_time, entry_time) DESC, id DESC" if limit is not None and limit > 0: query += " LIMIT %s" params.append(int(limit)) diff --git a/frontend/src/components/StatsDashboard.jsx b/frontend/src/components/StatsDashboard.jsx index af8c56c..d96efce 100644 --- a/frontend/src/components/StatsDashboard.jsx +++ b/frontend/src/components/StatsDashboard.jsx @@ -741,6 +741,9 @@ const StatsDashboard = () => {
- 说明:每条记录代表一笔完整的交易(开仓+平仓),统计总盈亏时每条记录只计算一次。默认「仅可对账」:只显示有开仓/平仓订单号的记录,统计与币安一致。默认「按平仓时间」:选「今天」= 今天平掉的单 + 今天开的未平仓。 + 说明:每条记录代表一笔完整的交易(开仓+平仓),统计总盈亏时每条记录只计算一次。默认「仅可对账」:只显示有开仓/平仓订单号的记录,统计与币安一致。时间依据:按平仓时间=今日平仓+今日开仓未平仓;按开仓时间=实际入场时间,适合策略分析;按创建时间=记录写入 DB 时间。