From 1b48c06362c51e2ef3ab82223bc1052ef046702d Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Wed, 5 Nov 2025 01:27:09 +0800 Subject: [PATCH] fix(trader): restore TP/SL after partial_close to prevent unprotected positions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem - User reported: After partial_close, remaining position has NO TP/SL protection - Root cause: Binance automatically cancels TP/SL orders when position size changes - Impact: Remaining position exposed to unlimited risk (爆仓风险) ## Why TP/SL disappears ``` Opening: Position: 0.130 BTC TP order: 0.130 BTC ✓ SL order: 0.130 BTC ✓ After 50% partial_close: Position: 0.065 BTC (remaining) TP order: 0.130 BTC ❌ Quantity mismatch → Binance cancels SL order: 0.130 BTC ❌ Quantity mismatch → Binance cancels → Remaining position has NO protection ``` ## Solution ### Code changes (trader/auto_trader.go:1278-1305) After partial_close execution, restore TP/SL with new quantity: ```go // If AI provides new TP/SL prices if decision.NewStopLoss > 0 || decision.NewTakeProfit > 0 { // Re-set SL with remaining quantity at.trader.SetStopLoss(symbol, side, remainingQuantity, newStopLoss) // Re-set TP with remaining quantity at.trader.SetTakeProfit(symbol, side, remainingQuantity, newTakeProfit) } else { // WARNING: Remaining position has NO protection log.Printf("⚠️⚠️⚠️ 警告: 剩余仓位无止盈止损保护") } ``` ### Prompt updates **Updated files**: - prompts/adaptive.txt (line 171) - prompts/default.txt (line 190) - prompts/nof1.txt (line 334) **Key change**: Emphasize AI MUST provide `new_stop_loss` and `new_take_profit` in `partial_close` decisions: ```markdown - **partial_close**: Fill `close_percentage` (1-100), **STRONGLY RECOMMEND** providing `new_stop_loss` AND `new_take_profit` to protect remaining position (otherwise NO stop protection) ``` ## Benefits - ✅ Risk control: Remaining position protected after partial_close - ✅ Clear warning: Logs explicit warning if AI doesn't provide new TP/SL - ✅ AI guidance: Updated prompts emphasize importance of new TP/SL - ✅ Backwards compatible: Works even if AI doesn't provide (just warns) ## Testing checklist - [ ] Partial_close with new_stop_loss & new_take_profit → TP/SL restored - [ ] Check Binance UI shows TP/SL orders after partial_close - [ ] Partial_close without new TP/SL → Warning logged - [ ] Remaining quantity matches TP/SL order quantity Related: partial-close-tpsl-bug.md --- decision/engine.go | 4 ++-- prompts/adaptive.txt | 2 +- prompts/default.txt | 2 +- prompts/nof1.txt | 2 +- trader/auto_trader.go | 33 +++++++++++++++++++++++++++++++-- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/decision/engine.go b/decision/engine.go index 686c8259..4f0d5640 100644 --- a/decision/engine.go +++ b/decision/engine.go @@ -675,8 +675,8 @@ func validateDecision(d *Decision, accountEquity float64, btcEthLeverage, altcoi // ✅ 验证最小开仓金额(防止数量格式化为 0 的错误) // Binance 最小名义价值 10 USDT + 安全边际 - const minPositionSizeGeneral = 12.0 // 10 + 20% 安全边际 - const minPositionSizeBTCETH = 60.0 // BTC/ETH 因价格高和精度限制需要更大金额(更灵活) + const minPositionSizeGeneral = 12.0 // 10 + 20% 安全边际 + const minPositionSizeBTCETH = 60.0 // BTC/ETH 因价格高和精度限制需要更大金额(更灵活) if d.Symbol == "BTCUSDT" || d.Symbol == "ETHUSDT" { if d.PositionSizeUSD < minPositionSizeBTCETH { diff --git a/prompts/adaptive.txt b/prompts/adaptive.txt index bb962bd2..b3baeb6c 100644 --- a/prompts/adaptive.txt +++ b/prompts/adaptive.txt @@ -168,7 +168,7 @@ | `close_short`| 平掉空仓 | `reasoning` | | `update_stop_loss` | 调整止损 | `new_stop_loss`、`reasoning` | | `update_take_profit` | 调整止盈 | `new_take_profit`、`reasoning` | -| `partial_close` | 部分平仓(1-100%) | `close_percentage`、`reasoning` | +| `partial_close` | 部分平仓(1-100%) | `close_percentage`、`reasoning`、**强烈建议** `new_stop_loss` + `new_take_profit` 保护剩余仓位 | | `hold` | 维持持仓 | `reasoning` | | `wait` | 观望 | `reasoning` | diff --git a/prompts/default.txt b/prompts/default.txt index 0bc04138..ba189681 100644 --- a/prompts/default.txt +++ b/prompts/default.txt @@ -187,7 +187,7 @@ THINK: trend=down, 等待确认 - **开仓** (`open_long/open_short`):必须填写 `position_size_usd`、`leverage`、`stop_loss`、`take_profit`、`risk_usd`;`reasoning` 写出信号与风险回报。 - **平仓** (`close_long/close_short`):`reasoning` 说明平仓原因(达到目标、触发失效条件等)。 - **动态调整** (`update_stop_loss` / `update_take_profit`):相应填写 `new_stop_loss` 或 `new_take_profit`。 -- **部分平仓** (`partial_close`):需填写 `close_percentage`(1-100),说明目的(如锁定利润)。 +- **部分平仓** (`partial_close`):需填写 `close_percentage`(1-100),**强烈建议**同时提供 `new_stop_loss` 和 `new_take_profit` 保护剩余仓位,说明目的(如锁定利润、降低风险)。 - **观望或持有** (`wait/hold`):`reasoning` 必须说明观望或继续持有的原因(例如信号不足、冷却中、趋势未变)。 ### 仓位大小计算 diff --git a/prompts/nof1.txt b/prompts/nof1.txt index 3aa6eaec..32540c2f 100644 --- a/prompts/nof1.txt +++ b/prompts/nof1.txt @@ -331,7 +331,7 @@ Every decision must follow this structure: ### Required field rules - **open_long / open_short**: Fill all numeric fields; `risk_usd` ≤ account_value × 0.03, `confidence` ≥ 75 (use 0-100 scale); `reasoning` explains signal trigger and risk control. - **update_stop_loss / update_take_profit**: Provide `new_stop_loss` or `new_take_profit` with adjustment rationale (e.g., trailing stop, locking profits). -- **partial_close**: Fill `close_percentage` (1-100), describe purpose (profit lock / risk reduction) AND remaining position management plan. If adjusting TP/SL simultaneously, include `new_stop_loss` / `new_take_profit`. +- **partial_close**: Fill `close_percentage` (1-100), **STRONGLY RECOMMEND** providing `new_stop_loss` AND `new_take_profit` to protect remaining position (otherwise it will have NO stop protection). Describe purpose (profit lock / risk reduction) and remaining position management plan. - **close_long / close_short**: Use current position size; `reasoning` states exit reason (target hit, risk elevated, thesis invalidated). - **hold / wait**: `reasoning` must explain why continuing to hold or waiting (e.g., trend intact, cooling period, insufficient signal). diff --git a/trader/auto_trader.go b/trader/auto_trader.go index 7ec457f6..81c1121f 100644 --- a/trader/auto_trader.go +++ b/trader/auto_trader.go @@ -1275,6 +1275,35 @@ func (at *AutoTrader) executePartialCloseWithRecord(decision *decision.Decision, log.Printf(" ✓ 部分平仓成功: 平仓 %.4f (%.1f%%), 剩余 %.4f", closeQuantity, decision.ClosePercentage, remainingQuantity) + // 🔧 FIX: 部分平仓后重新设置止盈止损(基于剩余数量) + // 币安会自动取消原来的止盈止损订单(因为数量不匹配),所以必须重新设置 + if decision.NewStopLoss > 0 || decision.NewTakeProfit > 0 { + log.Printf(" 🎯 更新剩余仓位的止盈止损...") + + // 设置新止损(基于剩余数量) + if decision.NewStopLoss > 0 { + if err := at.trader.SetStopLoss(decision.Symbol, positionSide, remainingQuantity, decision.NewStopLoss); err != nil { + log.Printf(" ⚠️ 设置新止损失败: %v", err) + } else { + log.Printf(" ✓ 已设置新止损: %.4f (数量: %.4f)", decision.NewStopLoss, remainingQuantity) + } + } + + // 设置新止盈(基于剩余数量) + if decision.NewTakeProfit > 0 { + if err := at.trader.SetTakeProfit(decision.Symbol, positionSide, remainingQuantity, decision.NewTakeProfit); err != nil { + log.Printf(" ⚠️ 设置新止盈失败: %v", err) + } else { + log.Printf(" ✓ 已设置新止盈: %.4f (数量: %.4f)", decision.NewTakeProfit, remainingQuantity) + } + } + } else { + // ⚠️ AI 没有提供新的止盈止损,剩余仓位将失去保护 + log.Printf(" ⚠️⚠️⚠️ 警告: 部分平仓后AI未提供新的止盈止损价格") + log.Printf(" → 剩余仓位 %.4f (价值 %.2f USDT) 目前没有止盈止损保护", remainingQuantity, remainingValue) + log.Printf(" → 建议: 在 partial_close 决策中包含 new_stop_loss 和 new_take_profit 字段") + } + return nil } @@ -1594,8 +1623,8 @@ func normalizeSymbol(symbol string) string { // extractNewsSymbols 提取需要收集新闻的币种(持仓 + 候选币前几个 + BTC) func (at *AutoTrader) extractNewsSymbols(positions []decision.PositionInfo, candidates []decision.CandidateCoin) []string { const ( - maxNewsSymbols = 10 // 最多收集10个币种的新闻(避免请求过多) - maxCandidatesForNews = 5 // 从候选币中取前5个 + maxNewsSymbols = 10 // 最多收集10个币种的新闻(避免请求过多) + maxCandidatesForNews = 5 // 从候选币中取前5个 ) symbolSet := make(map[string]bool)