From 60a7e1510071730cb4192cbceacad6d8db99e59b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Tue, 17 Feb 2026 23:02:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(trades,=20trade=5Flist):=20=E5=A2=9E?= =?UTF-8?q?=E5=BC=BA=E8=AE=A2=E5=8D=95=E5=90=8C=E6=AD=A5=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E4=B8=8E=E7=94=A8=E6=88=B7=E7=95=8C=E9=9D=A2=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 `trades.py` 中更新 `sync_trades_from_binance` 方法,改进日志记录以区分全量与增量同步模式,并添加对获取订单数为零的警告处理。更新 `TradeList.jsx` 组件,优化用户界面,新增订单同步选项和状态显示,提升用户体验。此改动增强了系统的灵活性和数据完整性。 --- backend/api/routes/trades.py | 68 +++++++++++++---- frontend/src/components/TradeList.jsx | 102 +++++++++++++++----------- 2 files changed, 111 insertions(+), 59 deletions(-) diff --git a/backend/api/routes/trades.py b/backend/api/routes/trades.py index d610d76..00682ee 100644 --- a/backend/api/routes/trades.py +++ b/backend/api/routes/trades.py @@ -501,8 +501,12 @@ async def sync_trades_from_binance( # 仅对上述 symbol 拉取订单 all_orders = [] try: - logger.info(f"仅对 {len(symbol_list)} 个有 DB 记录的 symbol 拉取订单(时间范围: {days}天,{datetime.fromtimestamp(start_ts_sec)} 至 {datetime.fromtimestamp(end_ts_sec)})") - for symbol in symbol_list: + if sync_all_symbols: + logger.info(f"全量同步模式:对 {len(symbol_list)} 个交易对拉取订单(时间范围: {days}天,{datetime.fromtimestamp(start_ts_sec)} 至 {datetime.fromtimestamp(end_ts_sec)})") + else: + logger.info(f"增量同步模式:对 {len(symbol_list)} 个有 DB 记录的 symbol 拉取订单(时间范围: {days}天,{datetime.fromtimestamp(start_ts_sec)} 至 {datetime.fromtimestamp(end_ts_sec)})") + + for idx, symbol in enumerate(symbol_list, 1): try: orders = await client.client.futures_get_all_orders( symbol=symbol, @@ -511,17 +515,35 @@ async def sync_trades_from_binance( ) filled_orders = [o for o in orders if o.get('status') == 'FILLED'] if filled_orders: - logger.debug(f" {symbol}: 获取到 {len(filled_orders)} 个已成交订单") + logger.info(f" [{idx}/{len(symbol_list)}] {symbol}: 获取到 {len(filled_orders)} 个已成交订单") + elif orders: + logger.debug(f" [{idx}/{len(symbol_list)}] {symbol}: 获取到 {len(orders)} 个订单,但无已成交订单") all_orders.extend(filled_orders) await asyncio.sleep(0.1) except Exception as e: - logger.warning(f"获取 {symbol} 订单失败: {e}") + logger.warning(f" [{idx}/{len(symbol_list)}] 获取 {symbol} 订单失败: {e}") continue - logger.info(f"从币安获取到 {len(all_orders)} 个已成交订单(涉及 {len(symbol_list)} 个交易对)") + logger.info(f"✓ 从币安获取到 {len(all_orders)} 个已成交订单(涉及 {len(symbol_list)} 个交易对)") except Exception as e: - logger.error(f"获取币安订单失败: {e}") + logger.error(f"获取币安订单失败: {e}", exc_info=True) raise HTTPException(status_code=500, detail=f"获取币安订单失败: {str(e)}") + if len(all_orders) == 0: + logger.warning(f"⚠️ 从币安获取到的订单数为 0,可能原因:1) 时间范围内确实没有订单 2) 交易对列表为空 3) API 调用失败") + return { + "success": True, + "message": f"从币安获取到 0 个订单(时间范围: {days}天,交易对数量: {len(symbol_list)})。可能原因:时间范围内确实没有订单,或请检查币安 API 连接。", + "total_orders": 0, + "updated_trades": 0, + "created_trades": 0, + "entry_order_id_filled": 0, + "exit_order_id_filled": 0, + "skipped_existing": 0, + "skipped_no_match": 0, + "close_orders": 0, + "open_orders": 0, + } + # 同步订单到数据库(仅当前账号) synced_count = 0 updated_count = 0 @@ -541,6 +563,7 @@ async def sync_trades_from_binance( logger.info(f"开始同步:平仓订单 {len(close_orders)} 个,开仓订单 {len(open_orders)} 个") # 1. 处理平仓订单 + logger.info(f"开始处理 {len(close_orders)} 个平仓订单...") for order in close_orders: symbol = order.get('symbol') order_id = order.get('orderId') @@ -562,8 +585,7 @@ async def sync_trades_from_binance( continue try: - if reduce_only: - # 这是平仓订单 + # 这是平仓订单(close_orders 已经筛选出 reduceOnly=True 的订单) # 首先检查是否已经通过订单号同步过(避免重复) existing_trade = Trade.get_by_exit_order_id(order_id) # 如果已有 exit_order_id 且 exit_reason 不是 sync,说明已完整同步,跳过 @@ -725,15 +747,29 @@ async def sync_trades_from_binance( else: skipped_no_match += 1 logger.debug(f"平仓订单 {order_id} ({symbol}) 无法匹配到现有记录(无 open 状态且无 exit_order_id 为空的 closed 记录),跳过") + # 2. 处理开仓订单 + logger.info(f"开始处理 {len(open_orders)} 个开仓订单...") + for order in open_orders: + symbol = order.get('symbol') + order_id = order.get('orderId') + side = order.get('side') + quantity = float(order.get('executedQty', 0)) + avg_price = float(order.get('avgPrice', 0)) + order_time = datetime.fromtimestamp(order.get('time', 0) / 1000) + otype = str(order.get('type') or order.get('origType') or '').upper() + + if quantity <= 0 or avg_price <= 0: + continue + + try: + # 这是开仓订单 + existing_trade = Trade.get_by_entry_order_id(order_id) + if existing_trade: + # 如果已存在,跳过(开仓订单信息通常已完整) + logger.debug(f"开仓订单 {order_id} 已存在,跳过") else: - # 这是开仓订单 - existing_trade = Trade.get_by_entry_order_id(order_id) - if existing_trade: - # 如果已存在,跳过(开仓订单信息通常已完整) - logger.debug(f"开仓订单 {order_id} 已存在,跳过") - else: - # 如果不存在,尝试查找没有 entry_order_id 的记录并补全,或创建新记录 - try: + # 如果不存在,尝试查找没有 entry_order_id 的记录并补全,或创建新记录 + try: # 查找该 symbol 下没有 entry_order_id 的记录(按时间匹配) order_time_ms = order.get('time', 0) order_time_sec = order_time_ms // 1000 if order_time_ms > 0 else 0 diff --git a/frontend/src/components/TradeList.jsx b/frontend/src/components/TradeList.jsx index 8c9ccdf..12e6602 100644 --- a/frontend/src/components/TradeList.jsx +++ b/frontend/src/components/TradeList.jsx @@ -354,10 +354,65 @@ const TradeList = () => { return (
-

交易记录

-

- 说明:每条记录代表一笔完整的交易(开仓+平仓),统计总盈亏时每条记录只计算一次。默认「仅可对账」:只显示有开仓/平仓订单号的记录,统计与币安一致。默认「按平仓时间」:选「今天」= 今天平掉的单 + 今天开的未平仓。 -

+
+
+

交易记录

+

+ 说明:每条记录代表一笔完整的交易(开仓+平仓),统计总盈亏时每条记录只计算一次。默认「仅可对账」:只显示有开仓/平仓订单号的记录,统计与币安一致。默认「按平仓时间」:选「今天」= 今天平掉的单 + 今天开的未平仓。 +

+
+
+
订单同步
+
+ + + +
+
+
{/* 筛选面板 */}
@@ -527,45 +582,6 @@ const TradeList = () => { -
- - - - -
{trades.length > 0 && ( <>