From 6f9e55aaee6c5108d7134aa810ff178493af6676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Sat, 21 Feb 2026 00:31:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(trades,=20database,=20frontend):=20?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E6=97=B6=E9=97=B4=E7=AD=9B=E9=80=89=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E4=B8=8E=E4=BA=A4=E6=98=93=E8=AE=B0=E5=BD=95=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 `trades.py` 中更新了时间筛选逻辑,新增 `created` 选项以支持按创建时间筛选交易记录。在 `models.py` 中调整了查询逻辑,确保在无 `created_at` 字段时回退为 `entry_time`。前端组件 `StatsDashboard.jsx` 和 `TradeList.jsx` 中相应更新了展示逻辑,增加了创建时间的显示,提升了用户体验与数据准确性。 --- backend/api/routes/trades.py | 8 +++++-- backend/database/models.py | 21 +++++++++++++++-- frontend/src/components/StatsDashboard.jsx | 3 +++ frontend/src/components/TradeList.jsx | 26 ++++++++++++++++++---- 4 files changed, 50 insertions(+), 8 deletions(-) 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 = () => {
开仓时间: {(trade.entry_time || trade.created_at) ? formatEntryTime(trade.entry_time || trade.created_at) : '—'}
+
+ 创建时间: {trade.created_at ? formatEntryTime(trade.created_at) : '—'} +
diff --git a/frontend/src/components/TradeList.jsx b/frontend/src/components/TradeList.jsx index 9ecebf7..302bd75 100644 --- a/frontend/src/components/TradeList.jsx +++ b/frontend/src/components/TradeList.jsx @@ -361,7 +361,7 @@ const TradeList = () => {

交易记录

- 说明:每条记录代表一笔完整的交易(开仓+平仓),统计总盈亏时每条记录只计算一次。默认「仅可对账」:只显示有开仓/平仓订单号的记录,统计与币安一致。默认「按平仓时间」:选「今天」= 今天平掉的单 + 今天开的未平仓。 + 说明:每条记录代表一笔完整的交易(开仓+平仓),统计总盈亏时每条记录只计算一次。默认「仅可对账」:只显示有开仓/平仓订单号的记录,统计与币安一致。时间依据:按平仓时间=今日平仓+今日开仓未平仓;按开仓时间=实际入场时间,适合策略分析;按创建时间=记录写入 DB 时间。

{ +
@@ -541,15 +548,20 @@ const TradeList = () => {
- +