From a52b8c473804ad2ffdfac3f812fd9ad729f6e7be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Sat, 14 Feb 2026 18:18:07 +0800 Subject: [PATCH] 1 --- backend/api/routes/account.py | 53 +++++++++++++++++++++- frontend/src/components/StatsDashboard.jsx | 5 +- trading_system/position_manager.py | 12 +++++ 3 files changed, 67 insertions(+), 3 deletions(-) diff --git a/backend/api/routes/account.py b/backend/api/routes/account.py index a5af439..614ae56 100644 --- a/backend/api/routes/account.py +++ b/backend/api/routes/account.py @@ -1750,17 +1750,66 @@ async def sync_positions(account_id: int = Depends(get_account_id)): else: logger.info("✓ 数据库与币安状态一致,无需更新") - # 4. 检查币安有但数据库没有记录的持仓 + # 4. 币安有仓但数据库无记录:从币安成交里取 orderId 并补建交易记录,便于在「订单记录」和统计中展示(含系统挂单/条件单) missing_in_db = binance_symbols - db_open_symbols + recovered_count = 0 if missing_in_db: logger.info(f"发现 {len(missing_in_db)} 个持仓在币安存在但数据库中没有记录: {', '.join(missing_in_db)}") - logger.info(" 这些持仓可能是手动开仓的,建议手动处理") + for symbol in missing_in_db: + try: + pos = next((p for p in binance_positions if p.get('symbol') == symbol), None) + if not pos or float(pos.get('positionAmt', 0)) == 0: + continue + position_amt = float(pos['positionAmt']) + quantity = abs(position_amt) + side = 'BUY' if position_amt > 0 else 'SELL' + entry_price = float(pos.get('entryPrice', 0)) + leverage = int(pos.get('leverage', 10)) or 10 + notional = quantity * entry_price + if notional < 1.0: + continue + entry_order_id = None + try: + trades = await client.get_recent_trades(symbol, limit=30) + if trades: + same_side = [t for t in trades if str(t.get('side', '')).upper() == side] + if same_side: + same_side.sort(key=lambda x: int(x.get('time', 0)), reverse=True) + entry_order_id = same_side[0].get('orderId') + except Exception as e: + logger.debug(f"获取 {symbol} 成交记录失败: {e}") + if entry_order_id and hasattr(Trade, 'get_by_entry_order_id'): + try: + existing = Trade.get_by_entry_order_id(entry_order_id) + if existing: + continue + except Exception: + pass + trade_id = Trade.create( + symbol=symbol, + side=side, + quantity=quantity, + entry_price=entry_price, + leverage=leverage, + entry_reason='sync_recovered', + entry_order_id=entry_order_id, + notional_usdt=notional, + margin_usdt=(notional / leverage) if leverage > 0 else None, + account_id=account_id, + ) + recovered_count += 1 + logger.info(f" ✓ {symbol} 已补建交易记录 (ID: {trade_id}, orderId: {entry_order_id or '-'})") + except Exception as e: + logger.warning(f" ✗ {symbol} 补建记录失败: {e}") + if recovered_count > 0: + logger.info(f"共补建 {recovered_count} 条交易记录,将在订单记录与统计中展示") result = { "message": "持仓状态同步完成", "binance_positions": len(binance_symbols), "db_open_positions": len(db_open_symbols), "updated_to_closed": updated_count, + "recovered_count": recovered_count, "missing_in_binance": list(missing_in_binance), "missing_in_db": list(missing_in_db) } diff --git a/frontend/src/components/StatsDashboard.jsx b/frontend/src/components/StatsDashboard.jsx index c9471c7..6606668 100644 --- a/frontend/src/components/StatsDashboard.jsx +++ b/frontend/src/components/StatsDashboard.jsx @@ -234,7 +234,10 @@ const StatsDashboard = () => { if (result.updated_to_closed > 0) { message += `,已更新 ${result.updated_to_closed} 条记录为已平仓` } - if (result.missing_in_db && result.missing_in_db.length > 0) { + if (result.recovered_count > 0) { + message += `,已为 ${result.recovered_count} 个持仓补建交易记录(将出现在订单记录与统计中)` + } + if (result.missing_in_db && result.missing_in_db.length > 0 && (result.recovered_count || 0) === 0) { message += `,发现 ${result.missing_in_db.length} 个币安持仓在数据库中没有记录` } diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index b282b6d..1899b8b 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -2818,6 +2818,17 @@ class PositionManager: f"{symbol} [状态同步] 检测到手动开仓,创建数据库记录... " f"({side} {quantity:.4f} @ {entry_price:.4f})" ) + # 尽量从币安成交取 entry_order_id,便于订单记录/统计对账 + entry_order_id = None + try: + recent = await self.client.get_recent_trades(symbol, limit=30) + if recent: + same_side = [t for t in recent if str(t.get('side', '')).upper() == side] + if same_side: + same_side.sort(key=lambda x: int(x.get('time', 0)), reverse=True) + entry_order_id = same_side[0].get('orderId') + except Exception: + pass # 创建数据库记录(显式传入 account_id,避免多账号混用) trade_id = Trade.create( symbol=symbol, @@ -2826,6 +2837,7 @@ class PositionManager: entry_price=entry_price, leverage=binance_position.get('leverage', 10), entry_reason='manual_entry', # 标记为手动开仓 + entry_order_id=entry_order_id, notional_usdt=notional, margin_usdt=(notional / float(binance_position.get('leverage', 10) or 10)) if float(binance_position.get('leverage', 10) or 0) > 0 else None, account_id=self.account_id,