From 113a30f00708afab00a14ace69433d24c07351d6 Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Wed, 5 Nov 2025 04:03:20 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20update=5Fstop=5Flos?= =?UTF-8?q?s/update=5Ftake=5Fprofit=20=E6=9C=AA=E5=88=A0=E9=99=A4=E6=97=A7?= =?UTF-8?q?=E8=AE=A2=E5=8D=95=E7=9A=84BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题描述 更新止损止盈时,旧订单没有被删除,导致订单累积。 用户看到多个止损/止盈订单同时存在(如截图所示有4个订单)。 ## 根本原因 币安Futures采用双向持仓模式(Hedge Mode),每个symbol可以同时持有LONG和SHORT两个方向的仓位。 取消订单时: - 创建订单时指定了 PositionSide(LONG/SHORT) - 取消订单时未遍历所有订单,导致部分订单残留 ## 修复内容 ### 1. binance_futures.go - CancelStopLossOrders: 取消所有方向(LONG+SHORT)的止损订单 - CancelTakeProfitOrders: 取消所有方向(LONG+SHORT)的止盈订单 - 添加错误收集机制,记录每个失败的订单 - 增强日志输出,显示订单方向(PositionSide) - 仅当所有取消都失败时才返回错误 ### 2. aster_trader.go - 同步应用相同的修复逻辑 - 保持多交易所一致性 ## 预期效果 - 更新止损时,所有旧止损订单被删除 - 更新止盈时,所有旧止盈订单被删除 - 不会出现订单累积问题 - 更详细的日志输出,方便排查问题 ## 测试建议 1. 在双向持仓模式下测试 update_stop_loss 2. 验证旧订单是否全部删除 3. 检查日志中的 positionSide 输出 Related: 用户反馈截图显示4个订单同时存在 --- trader/aster_trader.go | 38 ++++++++++++++++++++++++++++---------- trader/binance_futures.go | 36 ++++++++++++++++++++++++++---------- 2 files changed, 54 insertions(+), 20 deletions(-) diff --git a/trader/aster_trader.go b/trader/aster_trader.go index ecabdb2a..9868e78c 100644 --- a/trader/aster_trader.go +++ b/trader/aster_trader.go @@ -1068,14 +1068,16 @@ func (t *AsterTrader) CancelStopLossOrders(symbol string) error { return fmt.Errorf("解析订单数据失败: %w", err) } - // 过滤出止损单并取消 + // 过滤出止损单并取消(取消所有方向的止损单,包括LONG和SHORT) canceledCount := 0 + var cancelErrors []error for _, order := range orders { orderType, _ := order["type"].(string) // 只取消止损订单(不取消止盈订单) if orderType == "STOP_MARKET" || orderType == "STOP" { orderID, _ := order["orderId"].(float64) + positionSide, _ := order["positionSide"].(string) cancelParams := map[string]interface{}{ "symbol": symbol, "orderId": int64(orderID), @@ -1083,21 +1085,28 @@ func (t *AsterTrader) CancelStopLossOrders(symbol string) error { _, err := t.request("DELETE", "/fapi/v1/order", cancelParams) if err != nil { - log.Printf(" ⚠ 取消止损单 %d 失败: %v", int64(orderID), err) + errMsg := fmt.Sprintf("订单ID %d: %v", int64(orderID), err) + cancelErrors = append(cancelErrors, fmt.Errorf(errMsg)) + log.Printf(" ⚠ 取消止损单失败: %s", errMsg) continue } canceledCount++ - log.Printf(" ✓ 已取消止损单 (订单ID: %d, 类型: %s)", int64(orderID), orderType) + log.Printf(" ✓ 已取消止损单 (订单ID: %d, 类型: %s, 方向: %s)", int64(orderID), orderType, positionSide) } } - if canceledCount == 0 { + if canceledCount == 0 && len(cancelErrors) == 0 { log.Printf(" ℹ %s 没有止损单需要取消", symbol) - } else { + } else if canceledCount > 0 { log.Printf(" ✓ 已取消 %s 的 %d 个止损单", symbol, canceledCount) } + // 如果所有取消都失败了,返回错误 + if len(cancelErrors) > 0 && canceledCount == 0 { + return fmt.Errorf("取消止损单失败: %v", cancelErrors) + } + return nil } @@ -1118,14 +1127,16 @@ func (t *AsterTrader) CancelTakeProfitOrders(symbol string) error { return fmt.Errorf("解析订单数据失败: %w", err) } - // 过滤出止盈单并取消 + // 过滤出止盈单并取消(取消所有方向的止盈单,包括LONG和SHORT) canceledCount := 0 + var cancelErrors []error for _, order := range orders { orderType, _ := order["type"].(string) // 只取消止盈订单(不取消止损订单) if orderType == "TAKE_PROFIT_MARKET" || orderType == "TAKE_PROFIT" { orderID, _ := order["orderId"].(float64) + positionSide, _ := order["positionSide"].(string) cancelParams := map[string]interface{}{ "symbol": symbol, "orderId": int64(orderID), @@ -1133,21 +1144,28 @@ func (t *AsterTrader) CancelTakeProfitOrders(symbol string) error { _, err := t.request("DELETE", "/fapi/v1/order", cancelParams) if err != nil { - log.Printf(" ⚠ 取消止盈单 %d 失败: %v", int64(orderID), err) + errMsg := fmt.Sprintf("订单ID %d: %v", int64(orderID), err) + cancelErrors = append(cancelErrors, fmt.Errorf(errMsg)) + log.Printf(" ⚠ 取消止盈单失败: %s", errMsg) continue } canceledCount++ - log.Printf(" ✓ 已取消止盈单 (订单ID: %d, 类型: %s)", int64(orderID), orderType) + log.Printf(" ✓ 已取消止盈单 (订单ID: %d, 类型: %s, 方向: %s)", int64(orderID), orderType, positionSide) } } - if canceledCount == 0 { + if canceledCount == 0 && len(cancelErrors) == 0 { log.Printf(" ℹ %s 没有止盈单需要取消", symbol) - } else { + } else if canceledCount > 0 { log.Printf(" ✓ 已取消 %s 的 %d 个止盈单", symbol, canceledCount) } + // 如果所有取消都失败了,返回错误 + if len(cancelErrors) > 0 && canceledCount == 0 { + return fmt.Errorf("取消止盈单失败: %v", cancelErrors) + } + return nil } diff --git a/trader/binance_futures.go b/trader/binance_futures.go index 4e41aa40..76d2797d 100644 --- a/trader/binance_futures.go +++ b/trader/binance_futures.go @@ -792,8 +792,9 @@ func (t *FuturesTrader) CancelStopLossOrders(symbol string) error { return fmt.Errorf("获取未完成订单失败: %w", err) } - // 过滤出止损单并取消 + // 过滤出止损单并取消(取消所有方向的止损单,包括LONG和SHORT) canceledCount := 0 + var cancelErrors []error for _, order := range orders { orderType := order.Type @@ -805,21 +806,28 @@ func (t *FuturesTrader) CancelStopLossOrders(symbol string) error { Do(context.Background()) if err != nil { - log.Printf(" ⚠ 取消止损单 %d 失败: %v", order.OrderID, err) + errMsg := fmt.Sprintf("订单ID %d: %v", order.OrderID, err) + cancelErrors = append(cancelErrors, fmt.Errorf(errMsg)) + log.Printf(" ⚠ 取消止损单失败: %s", errMsg) continue } canceledCount++ - log.Printf(" ✓ 已取消止损单 (订单ID: %d, 类型: %s)", order.OrderID, orderType) + log.Printf(" ✓ 已取消止损单 (订单ID: %d, 类型: %s, 方向: %s)", order.OrderID, orderType, order.PositionSide) } } - if canceledCount == 0 { + if canceledCount == 0 && len(cancelErrors) == 0 { log.Printf(" ℹ %s 没有止损单需要取消", symbol) - } else { + } else if canceledCount > 0 { log.Printf(" ✓ 已取消 %s 的 %d 个止损单", symbol, canceledCount) } + // 如果所有取消都失败了,返回错误 + if len(cancelErrors) > 0 && canceledCount == 0 { + return fmt.Errorf("取消止损单失败: %v", cancelErrors) + } + return nil } @@ -834,8 +842,9 @@ func (t *FuturesTrader) CancelTakeProfitOrders(symbol string) error { return fmt.Errorf("获取未完成订单失败: %w", err) } - // 过滤出止盈单并取消 + // 过滤出止盈单并取消(取消所有方向的止盈单,包括LONG和SHORT) canceledCount := 0 + var cancelErrors []error for _, order := range orders { orderType := order.Type @@ -847,21 +856,28 @@ func (t *FuturesTrader) CancelTakeProfitOrders(symbol string) error { Do(context.Background()) if err != nil { - log.Printf(" ⚠ 取消止盈单 %d 失败: %v", order.OrderID, err) + errMsg := fmt.Sprintf("订单ID %d: %v", order.OrderID, err) + cancelErrors = append(cancelErrors, fmt.Errorf(errMsg)) + log.Printf(" ⚠ 取消止盈单失败: %s", errMsg) continue } canceledCount++ - log.Printf(" ✓ 已取消止盈单 (订单ID: %d, 类型: %s)", order.OrderID, orderType) + log.Printf(" ✓ 已取消止盈单 (订单ID: %d, 类型: %s, 方向: %s)", order.OrderID, orderType, order.PositionSide) } } - if canceledCount == 0 { + if canceledCount == 0 && len(cancelErrors) == 0 { log.Printf(" ℹ %s 没有止盈单需要取消", symbol) - } else { + } else if canceledCount > 0 { log.Printf(" ✓ 已取消 %s 的 %d 个止盈单", symbol, canceledCount) } + // 如果所有取消都失败了,返回错误 + if len(cancelErrors) > 0 && canceledCount == 0 { + return fmt.Errorf("取消止盈单失败: %v", cancelErrors) + } + return nil }