diff --git a/backend/api/routes/trades.py b/backend/api/routes/trades.py index c25d87f..5caee2c 100644 --- a/backend/api/routes/trades.py +++ b/backend/api/routes/trades.py @@ -80,7 +80,7 @@ async def get_trades( 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'(原逻辑)"), - include_sync: bool = Query(False, description="是否包含 entry_reason 为 sync_recovered 的历史同步单"), + 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="返回记录数限制"), ): @@ -173,6 +173,9 @@ async def get_trades( "reconciled_only": reconciled_only, } } + # 仅可对账且无记录时提示:可先同步币安订单补全订单号,或取消「仅可对账」查看全部 + if reconciled_only and len(formatted_trades) == 0: + result["hint"] = "当前没有可对账记录。可尝试:1) 调用「同步币安订单」接口补全开仓/平仓订单号;2) 使用 reconciled_only=false 查看全部记录(含无订单号的补建单)。" logger.debug(f"返回交易记录: {len(result['trades'])} 条") return result @@ -189,7 +192,7 @@ async def get_trade_stats( 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'"), - include_sync: bool = Query(False, description="是否包含 entry_reason 为 sync_recovered 的历史同步单"), + include_sync: bool = Query(True, description="是否包含 entry_reason 为 sync_recovered 的补建/同步单(默认与订单记录一致)"), reconciled_only: bool = Query(True, description="仅统计可对账记录,与币安一致,避免系统盈利/币安亏损偏差"), ): """获取交易统计(默认按平仓时间统计:今日=今日平仓的盈亏,与订单记录筛选一致)""" @@ -759,19 +762,25 @@ async def sync_trades_from_binance( time_window_start = order_time_sec - 3600 time_window_end = order_time_sec + 3600 - trades_no_entry_id = Trade.get_all( + trades_in_window = Trade.get_all( account_id=aid, symbol=symbol, start_timestamp=time_window_start, end_timestamp=time_window_end, - time_filter="entry", # 使用 entry_time 过滤 + time_filter="entry", + reconciled_only=False, ) - - # 过滤出没有 entry_order_id 的记录 trades_no_entry_id = [ - t for t in trades_no_entry_id + t for t in trades_in_window if not t.get("entry_order_id") or str(t.get("entry_order_id")).strip() in ("", "0") ] + # 并入缺少 entry_order_id 的其它记录(含 entry_time 为 NULL 的旧记录),便于补全后「仅可对账」能显示 + extra = Trade.get_trades_missing_entry_order_id(symbol, aid, limit=50) + seen_ids = {t["id"] for t in trades_no_entry_id} + for t in extra: + if t.get("id") not in seen_ids: + trades_no_entry_id.append(t) + seen_ids.add(t["id"]) # 按价格和数量匹配(允许 5% 误差) matched_trade = None diff --git a/backend/database/models.py b/backend/database/models.py index 76e1855..2455b22 100644 --- a/backend/database/models.py +++ b/backend/database/models.py @@ -906,6 +906,38 @@ class Trade: (exit_order_id,) ) + @staticmethod + def get_trades_missing_entry_order_id(symbol: str, account_id: int, limit: int = 50): + """ + 获取该 symbol+account 下缺少 entry_order_id 的记录(用于从币安同步订单时补全)。 + 包含 entry_time 为 NULL 的旧记录,避免「仅可对账」下看不到今日有单却未补全的情况。 + """ + if not symbol or account_id is None: + return [] + try: + if not _table_has_column("trades", "entry_order_id"): + return [] + if _table_has_column("trades", "account_id"): + return db.execute_query( + """SELECT * FROM trades + WHERE account_id = %s AND symbol = %s + AND (entry_order_id IS NULL OR entry_order_id = 0 OR entry_order_id = '') + ORDER BY id DESC + LIMIT %s""", + (int(account_id), symbol.strip(), int(limit)), + ) + return db.execute_query( + """SELECT * FROM trades + WHERE symbol = %s + AND (entry_order_id IS NULL OR entry_order_id = 0 OR entry_order_id = '') + ORDER BY id DESC + LIMIT %s""", + (symbol.strip(), int(limit)), + ) + except Exception as e: + logger.debug(f"get_trades_missing_entry_order_id 失败: {e}") + return [] + @staticmethod def get_by_client_order_id(client_order_id, account_id: int = None): """根据 clientOrderId 获取交易记录(可选按 account_id 隔离)""" diff --git a/frontend/src/components/StatsDashboard.jsx b/frontend/src/components/StatsDashboard.jsx index 6606668..7443114 100644 --- a/frontend/src/components/StatsDashboard.jsx +++ b/frontend/src/components/StatsDashboard.jsx @@ -738,9 +738,9 @@ const StatsDashboard = () => { {/* 止损止盈比例 */} - {trade.entry_time && ( -
+ 若币安今日有订单但此处为空,可先点击右上角「同步订单」补全开仓/平仓订单号,或取消勾选「仅可对账」查看全部记录。 +
+ )} +