mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-06-06 05:51:19 +08:00
fix: 添加双向持仓防御性检查,避免误删除对向订单
在上一个修复(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 (原始修复)
This commit is contained in:
@@ -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)
|
||||
// 不中断执行,继续设置新止盈
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user