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 = () => {