From ad603388d8662b97e683b85b2c7f638274f20715 Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Thu, 6 Nov 2025 00:45:02 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=E5=8F=8C=E5=90=91?= =?UTF-8?q?=E6=8C=81=E4=BB=93=E9=98=B2=E5=BE=A1=E6=80=A7=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8D=E8=AF=AF=E5=88=A0=E9=99=A4=E5=AF=B9?= =?UTF-8?q?=E5=90=91=E8=AE=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在上一个修复(113a30f)中,虽然解决了订单累积问题,但引入了新的风险: 如果用户同时持有同一symbol的多空双向持仓,update_stop_loss/update_take_profit 会误删除另一方向的保护订单。 ``` 假设: - BTCUSDT LONG 持仓(止损 95000) - BTCUSDT SHORT 持仓(止损 105000) AI 执行:update_stop_loss for SHORT → CancelStopLossOrders("BTCUSDT") 删除所有止损 → SetStopLoss("BTCUSDT", "SHORT", ...) 只设置 SHORT 止损 结果: - SHORT 止损正确更新 ✅ - LONG 止损被误删 ❌ 失去保护! ``` 1. ✅ 技术支持:Binance 设置为双向持仓模式(Hedge Mode) 2. ❌ 策略禁止:Prompt 明确禁止"对同一标的同时持有多空" 3. ❌ 代码保护:开仓时检查已有同向持仓并拒绝 理论上不应该出现双向持仓,但仍需防御: - 用户手动操作 - 并发bug - 遗留数据 在 auto_trader.go 的 update_stop_loss/update_take_profit 函数中: 1. 执行前检测是否存在对向持仓 2. 如果检测到双向持仓: - 记录 🚨 严重警告日志 - 说明这违反策略规则 - 提示可能的原因和建议 3. 继续执行当前逻辑(因为策略本身禁止双向持仓) - executeUpdateStopLossWithRecord: 添加双向持仓检测(第1175-1194行) - executeUpdateTakeProfitWithRecord: 添加双向持仓检测(第1259-1278行) ``` 🚨 警告:检测到 BTCUSDT 存在双向持仓(SHORT + LONG),这违反了策略规则 🚨 取消止损单将影响两个方向的订单,请检查是否为用户手动操作导致 🚨 建议:手动平掉其中一个方向的持仓,或检查系统是否有BUG ``` - 会影响所有实现类(binance/aster/hyperliquid) - 增加复杂度 - 策略已禁止双向持仓,属于异常场景 - 实现过于复杂 - 需要重新实现订单管理逻辑 - 策略禁止场景不应该出现 - ✅ 最小侵入性修改 - ✅ 及时警告异常情况 - ✅ 不影响正常流程 - ✅ 为调试提供线索 - 正常使用(单向持仓):无影响,正常工作 ✅ - 异常场景(双向持仓):记录警告,提示用户检查 ⚠️ Related: 113a30f (原始修复) --- trader/auto_trader.go | 52 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/trader/auto_trader.go b/trader/auto_trader.go index 6e269a5b..54c1b1b7 100644 --- a/trader/auto_trader.go +++ b/trader/auto_trader.go @@ -991,8 +991,30 @@ func (at *AutoTrader) executeUpdateStopLossWithRecord(decision *decision.Decisio return fmt.Errorf("空单止损必须高于当前价格 (当前: %.2f, 新止损: %.2f)", marketData.CurrentPrice, decision.NewStopLoss) } - // 取消旧的止损单(避免多个止损单共存) - if err := at.trader.CancelStopOrders(decision.Symbol); err != nil { + // ⚠️ 防御性检查:检测是否存在双向持仓(不应该出现,但提供保护) + var hasOppositePosition bool + oppositeSide := "" + for _, pos := range positions { + symbol, _ := pos["symbol"].(string) + posSide, _ := pos["side"].(string) + posAmt, _ := pos["positionAmt"].(float64) + if symbol == decision.Symbol && posAmt != 0 && strings.ToUpper(posSide) != positionSide { + hasOppositePosition = true + oppositeSide = strings.ToUpper(posSide) + break + } + } + + if hasOppositePosition { + log.Printf(" 🚨 警告:检测到 %s 存在双向持仓(%s + %s),这违反了策略规则", + decision.Symbol, positionSide, oppositeSide) + log.Printf(" 🚨 取消止损单将影响两个方向的订单,请检查是否为用户手动操作导致") + log.Printf(" 🚨 建议:手动平掉其中一个方向的持仓,或检查系统是否有BUG") + } + + // 取消旧的止损单(只删除止损单,不影响止盈单) + // 注意:如果存在双向持仓,这会删除两个方向的止损单 + if err := at.trader.CancelStopLossOrders(decision.Symbol); err != nil { log.Printf(" ⚠ 取消旧止损单失败: %v", err) // 不中断执行,继续设置新止损 } @@ -1053,8 +1075,30 @@ func (at *AutoTrader) executeUpdateTakeProfitWithRecord(decision *decision.Decis return fmt.Errorf("空单止盈必须低于当前价格 (当前: %.2f, 新止盈: %.2f)", marketData.CurrentPrice, decision.NewTakeProfit) } - // 取消旧的止盈单(避免多个止盈单共存) - if err := at.trader.CancelStopOrders(decision.Symbol); err != nil { + // ⚠️ 防御性检查:检测是否存在双向持仓(不应该出现,但提供保护) + var hasOppositePosition bool + oppositeSide := "" + for _, pos := range positions { + symbol, _ := pos["symbol"].(string) + posSide, _ := pos["side"].(string) + posAmt, _ := pos["positionAmt"].(float64) + if symbol == decision.Symbol && posAmt != 0 && strings.ToUpper(posSide) != positionSide { + hasOppositePosition = true + oppositeSide = strings.ToUpper(posSide) + break + } + } + + if hasOppositePosition { + log.Printf(" 🚨 警告:检测到 %s 存在双向持仓(%s + %s),这违反了策略规则", + decision.Symbol, positionSide, oppositeSide) + log.Printf(" 🚨 取消止盈单将影响两个方向的订单,请检查是否为用户手动操作导致") + log.Printf(" 🚨 建议:手动平掉其中一个方向的持仓,或检查系统是否有BUG") + } + + // 取消旧的止盈单(只删除止盈单,不影响止损单) + // 注意:如果存在双向持仓,这会删除两个方向的止盈单 + if err := at.trader.CancelTakeProfitOrders(decision.Symbol); err != nil { log.Printf(" ⚠ 取消旧止盈单失败: %v", err) // 不中断执行,继续设置新止盈 }