From bb1490b909b05547870c8a52e27201fdc5d9cebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Fri, 16 Jan 2026 13:11:49 +0800 Subject: [PATCH] a --- backend/api/routes/trades.py | 32 +++++++++++----- backend/database/add_order_ids.sql | 16 ++++++++ backend/database/models.py | 54 ++++++++++++++++++++++----- frontend/src/components/TradeList.css | 6 +++ frontend/src/components/TradeList.jsx | 18 ++++++++- trading_system/position_manager.py | 32 +++++++++++++--- 6 files changed, 133 insertions(+), 25 deletions(-) create mode 100644 backend/database/add_order_ids.sql diff --git a/backend/api/routes/trades.py b/backend/api/routes/trades.py index a6a8561..d655483 100644 --- a/backend/api/routes/trades.py +++ b/backend/api/routes/trades.py @@ -276,7 +276,13 @@ async def sync_trades_from_binance( try: if reduce_only: - # 这是平仓订单,更新数据库中的对应记录 + # 这是平仓订单 + # 首先检查是否已经通过订单号同步过(避免重复) + existing_trade = Trade.get_by_exit_order_id(order_id) + if existing_trade: + logger.debug(f"订单 {order_id} 已同步过,跳过") + continue + # 查找数据库中该交易对的open状态记录 open_trades = Trade.get_by_symbol(symbol, status='open') if open_trades: @@ -298,30 +304,36 @@ async def sync_trades_from_binance( pnl = (entry_price - avg_price) * actual_quantity pnl_percent = ((entry_price - avg_price) / entry_price) * 100 - # 更新数据库 + # 更新数据库(包含订单号) Trade.update_exit( trade_id=trade_id, exit_price=avg_price, exit_reason='sync', pnl=pnl, - pnl_percent=pnl_percent + pnl_percent=pnl_percent, + exit_order_id=order_id # 保存订单号,确保唯一性 ) updated_count += 1 - logger.debug(f"✓ 更新平仓记录: {symbol} (ID: {trade_id}, 成交价: {avg_price:.4f})") + logger.debug(f"✓ 更新平仓记录: {symbol} (ID: {trade_id}, 订单号: {order_id}, 成交价: {avg_price:.4f})") else: - # 这是开仓订单,检查数据库中是否已存在 - # 这里可以添加逻辑来创建缺失的开仓记录 - # 但为了简化,暂时只处理平仓订单 - pass + # 这是开仓订单,检查数据库中是否已存在(通过订单号) + existing_trade = Trade.get_by_entry_order_id(order_id) + if not existing_trade: + # 如果不存在,可以创建新记录(但需要更多信息,暂时跳过) + logger.debug(f"发现新的开仓订单 {order_id},但缺少必要信息,跳过创建") + else: + logger.debug(f"开仓订单 {order_id} 已存在,跳过") except Exception as e: logger.warning(f"同步订单失败 {symbol} (订单ID: {order_id}): {e}") continue result = { "success": True, - "message": f"同步完成:更新了 {updated_count} 条平仓记录", + "message": f"同步完成:更新了 {updated_count} 条平仓记录(基于订单号匹配,确保唯一性)", "total_orders": len(all_orders), - "updated_trades": updated_count + "updated_trades": updated_count, + "close_orders": len([o for o in all_orders if o.get('reduceOnly', False)]), + "open_orders": len([o for o in all_orders if not o.get('reduceOnly', False)]) } logger.info(f"✓ 同步完成:处理了 {len(all_orders)} 个订单,更新了 {updated_count} 条记录") diff --git a/backend/database/add_order_ids.sql b/backend/database/add_order_ids.sql new file mode 100644 index 0000000..4a767d1 --- /dev/null +++ b/backend/database/add_order_ids.sql @@ -0,0 +1,16 @@ +-- 添加币安订单号字段,用于与币安订单同步和对账 + +USE `auto_trade_sys`; + +-- 添加开仓订单号字段(币安开仓订单ID) +ALTER TABLE `trades` +ADD COLUMN `entry_order_id` BIGINT UNIQUE COMMENT '币安开仓订单号(唯一)' AFTER `leverage`; + +-- 添加平仓订单号字段(币安平仓订单ID) +ALTER TABLE `trades` +ADD COLUMN `exit_order_id` BIGINT UNIQUE COMMENT '币安平仓订单号(唯一)' AFTER `entry_order_id`; + +-- 添加索引以便快速查询 +ALTER TABLE `trades` +ADD INDEX `idx_entry_order_id` (`entry_order_id`), +ADD INDEX `idx_exit_order_id` (`exit_order_id`); diff --git a/backend/database/models.py b/backend/database/models.py index cb771cc..de15ad3 100644 --- a/backend/database/models.py +++ b/backend/database/models.py @@ -88,27 +88,63 @@ class Trade: """交易记录模型""" @staticmethod - def create(symbol, side, quantity, entry_price, leverage=10, entry_reason=None): - """创建交易记录(使用北京时间)""" + def create(symbol, side, quantity, entry_price, leverage=10, entry_reason=None, entry_order_id=None): + """创建交易记录(使用北京时间) + + Args: + symbol: 交易对 + side: 方向 + quantity: 数量 + entry_price: 入场价 + leverage: 杠杆 + entry_reason: 入场原因 + entry_order_id: 币安开仓订单号(可选,用于对账) + """ entry_time = get_beijing_time() db.execute_update( """INSERT INTO trades - (symbol, side, quantity, entry_price, leverage, entry_reason, status, entry_time) - VALUES (%s, %s, %s, %s, %s, %s, 'open', %s)""", - (symbol, side, quantity, entry_price, leverage, entry_reason, entry_time) + (symbol, side, quantity, entry_price, leverage, entry_reason, status, entry_time, entry_order_id) + VALUES (%s, %s, %s, %s, %s, %s, 'open', %s, %s)""", + (symbol, side, quantity, entry_price, leverage, entry_reason, entry_time, entry_order_id) ) return db.execute_one("SELECT LAST_INSERT_ID() as id")['id'] @staticmethod - def update_exit(trade_id, exit_price, exit_reason, pnl, pnl_percent): - """更新平仓信息(使用北京时间)""" + def update_exit(trade_id, exit_price, exit_reason, pnl, pnl_percent, exit_order_id=None): + """更新平仓信息(使用北京时间) + + Args: + trade_id: 交易记录ID + exit_price: 出场价 + exit_reason: 平仓原因 + pnl: 盈亏 + pnl_percent: 盈亏百分比 + exit_order_id: 币安平仓订单号(可选,用于对账) + """ exit_time = get_beijing_time() db.execute_update( """UPDATE trades SET exit_price = %s, exit_time = %s, - exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed' + exit_reason = %s, pnl = %s, pnl_percent = %s, status = 'closed', + exit_order_id = %s WHERE id = %s""", - (exit_price, exit_time, exit_reason, pnl, pnl_percent, trade_id) + (exit_price, exit_time, exit_reason, pnl, pnl_percent, exit_order_id, trade_id) + ) + + @staticmethod + def get_by_entry_order_id(entry_order_id): + """根据开仓订单号获取交易记录""" + return db.execute_one( + "SELECT * FROM trades WHERE entry_order_id = %s", + (entry_order_id,) + ) + + @staticmethod + def get_by_exit_order_id(exit_order_id): + """根据平仓订单号获取交易记录""" + return db.execute_one( + "SELECT * FROM trades WHERE exit_order_id = %s", + (exit_order_id,) ) @staticmethod diff --git a/frontend/src/components/TradeList.css b/frontend/src/components/TradeList.css index 6572739..f13b865 100644 --- a/frontend/src/components/TradeList.css +++ b/frontend/src/components/TradeList.css @@ -365,6 +365,12 @@ color: #2c3e50; } +.order-id { + font-family: 'Courier New', monospace; + font-size: 0.9em; + color: #666; +} + .trade-card-footer { display: flex; justify-content: space-between; diff --git a/frontend/src/components/TradeList.jsx b/frontend/src/components/TradeList.jsx index 58b8065..c6411e3 100644 --- a/frontend/src/components/TradeList.jsx +++ b/frontend/src/components/TradeList.jsx @@ -250,6 +250,8 @@ const TradeList = () => { 盈亏比例 状态 平仓类型 + 开仓订单号 + 平仓订单号 入场时间 平仓时间 @@ -304,6 +306,8 @@ const TradeList = () => { {trade.status === 'open' ? '持仓中' : trade.status === 'closed' ? '已平仓' : '已取消'} {trade.exit_reason_display || '-'} + {trade.entry_order_id || '-'} + {trade.exit_order_id || '-'} {formatTime(trade.entry_time)} {trade.exit_time ? formatTime(trade.exit_time) : '-'} @@ -385,8 +389,20 @@ const TradeList = () => { {trade.exit_reason_display} )} + {trade.entry_order_id && ( +
+ 开仓订单号 + {trade.entry_order_id} +
+ )} + {trade.exit_order_id && ( +
+ 平仓订单号 + {trade.exit_order_id} +
+ )} -
+
入场: {formatTime(trade.entry_time)} diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index 0a21e47..0f1bf6a 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -179,6 +179,11 @@ class PositionManager: ) if order: + # 获取开仓订单号 + entry_order_id = order.get('orderId') + if entry_order_id: + logger.info(f"{symbol} [开仓] 币安订单号: {entry_order_id}") + # 记录到数据库 trade_id = None if DB_AVAILABLE and Trade: @@ -190,9 +195,10 @@ class PositionManager: quantity=quantity, entry_price=entry_price, leverage=leverage, - entry_reason=entry_reason + entry_reason=entry_reason, + entry_order_id=entry_order_id # 保存币安订单号 ) - logger.info(f"✓ {symbol} 交易记录已保存到数据库 (ID: {trade_id})") + logger.info(f"✓ {symbol} 交易记录已保存到数据库 (ID: {trade_id}, 订单号: {entry_order_id})") except Exception as e: logger.error(f"❌ 保存交易记录到数据库失败: {e}") logger.error(f" 错误类型: {type(e).__name__}") @@ -299,12 +305,14 @@ class PositionManager: pnl = (entry_price - exit_price) * quantity pnl_percent = ((entry_price - exit_price) / entry_price) * 100 + # 同步平仓时没有订单号,设为None Trade.update_exit( trade_id=trade_id, exit_price=exit_price, exit_reason=reason, pnl=pnl, - pnl_percent=pnl_percent + pnl_percent=pnl_percent, + exit_order_id=None # 同步平仓时没有订单号 ) logger.info(f"{symbol} [平仓] ✓ 数据库状态已更新") except Exception as e: @@ -415,12 +423,18 @@ class PositionManager: pnl = (entry_price - exit_price_float) * quantity_float pnl_percent = ((entry_price - exit_price_float) / entry_price) * 100 + # 获取平仓订单号 + exit_order_id = order.get('orderId') + if exit_order_id: + logger.info(f"{symbol} [平仓] 币安订单号: {exit_order_id}") + Trade.update_exit( trade_id=trade_id, exit_price=exit_price_float, exit_reason=reason, pnl=pnl, - pnl_percent=pnl_percent + pnl_percent=pnl_percent, + exit_order_id=exit_order_id # 保存币安平仓订单号 ) logger.info( f"{symbol} [平仓] ✓ 数据库记录已更新 " @@ -916,12 +930,20 @@ class PositionManager: pnl = (entry_price - exit_price) * quantity pnl_percent = ((entry_price - exit_price) / entry_price) * 100 + # 从历史订单中获取平仓订单号 + exit_order_id = None + if close_orders: + exit_order_id = close_orders[0].get('orderId') + if exit_order_id: + logger.info(f"{symbol} [状态同步] 找到平仓订单号: {exit_order_id}") + Trade.update_exit( trade_id=trade_id, exit_price=exit_price, exit_reason='sync', # 标记为同步更新 pnl=pnl, - pnl_percent=pnl_percent + pnl_percent=pnl_percent, + exit_order_id=exit_order_id # 保存币安平仓订单号 ) logger.info(