mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-11 23:07:01 +08:00
fix(trader): add backend safety checks for partial_close (#713)
* fix(trader): add backend safety checks for partial_close After PR #415 added partial_close functionality, production users reported two critical issues: 1. **Exchange minimum value error**: "Order must have minimum value of $10" when remaining position value falls below exchange threshold 2. **Unprotected positions after partial close**: Exchanges auto-cancel TP/SL orders when position size changes, leaving remaining position exposed to liquidation risk This PR adds **backend safety checks** as a safety net layer that complements the prompt-based rules from PR #712. **Protection**: Before executing partial_close, verify remaining position value > $10 ```go const MIN_POSITION_VALUE = 10.0 // Exchange底线 remainingValue := remainingQuantity * markPrice if remainingValue > 0 && remainingValue <= MIN_POSITION_VALUE { // 🔄 Auto-correct to full close decision.Action = "close_long" // or "close_short" return at.executeCloseLongWithRecord(decision, actionRecord) } ``` **Behavior**: - Position $20 → partial_close 50% → remaining $10 ≤ $10 → Auto full close ✅ - Position $30 → partial_close 50% → remaining $15 > $10 → Allow partial close ✅ **Protection**: Restore TP/SL orders for remaining position if AI provides new_stop_loss/new_take_profit ```go // Exchanges auto-cancel TP/SL when position size changes if decision.NewStopLoss > 0 { at.trader.SetStopLoss(symbol, side, remainingQuantity, decision.NewStopLoss) } if decision.NewTakeProfit > 0 { at.trader.SetTakeProfit(symbol, side, remainingQuantity, decision.NewTakeProfit) } // Warning if AI didn't provide new TP/SL if decision.NewStopLoss <= 0 && decision.NewTakeProfit <= 0 { log.Printf("⚠️⚠️⚠️ Warning: Remaining position has NO TP/SL protection") } ``` **Improvement**: Show position quantity and value to help AI make better decisions ``` Before: | 入场价100.00 当前价105.00 | 盈亏+5.00% | ... After: | 入场价100.00 当前价105.00 | 数量0.5000 | 仓位价值52.50 USDT | 盈亏+5.00% | ... ``` **Benefits**: - AI can now calculate: remaining_value = current_value × (1 - close_percentage/100) - AI can proactively avoid decisions that would violate $10 threshold - Added MIN_POSITION_VALUE check before execution - Auto-correct to full close if remaining value ≤ $10 - Restore TP/SL for remaining position - Warning logs when AI doesn't provide new TP/SL - Import "math" package - Calculate and display position value - Add quantity and position value to prompt - Complements PR #712 (Prompt v6.0.0 safety rules) - Addresses #301 (Backend layer) - Based on PR #415 (Core functionality) | Layer | Location | Purpose | |-------|----------|---------| | **Layer 1: AI Prompt** | PR #712 | Prevent bad decisions before they happen | | **Layer 2: Backend** | This PR | Auto-correct and safety net | **Together they provide**: - ✅ AI makes better decisions (sees position value, knows rules) - ✅ Backend catches edge cases (auto-corrects violations) - ✅ User-friendly warnings (explains what happened) - [x] Compiles successfully (`go build ./...`) - [x] MIN_POSITION_VALUE logic correct - [x] TP/SL restoration logic correct - [x] Position value display format validated - [x] Auto-correction flow tested This PR can be merged **independently** of PR #712, or together. Suggested merge order: 1. PR #712 (Prompt v6.0.0) - AI layer improvements 2. This PR (Backend safety) - Safety net layer Or merge together for complete two-layer protection. --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: add error handling for markPrice type assertion - Check type assertion success before using markPrice - Return error if markPrice is invalid or <= 0 - Addresses code review feedback from @xqliu in PR #713 * test(trader): add comprehensive unit tests for partial_close safety checks - Test minimum position value check (< 10 USDT triggers full close) - Test boundary condition (exactly 10 USDT also triggers full close) - Test stop-loss/take-profit recovery after partial close - Test edge cases (invalid close percentages) - Test integration scenarios with mock trader All 14 test cases passed, covering: 1. MinPositionCheck (5 cases): normal, small remainder, boundary, edge cases 2. StopLossTakeProfitRecovery (4 cases): both/SL only/TP only/none 3. EdgeCases (4 cases): zero/over 100/negative/normal percentages 4. Integration (2 cases): LONG with SL/TP, SHORT with auto full close 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * style: apply go fmt after rebase Only formatting changes: - api/server.go: fix indentation - manager/trader_manager.go: add blank line - trader/partial_close_test.go: align struct fields 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(test): rename MockTrader to MockPartialCloseTrader to avoid conflict Problem: - trader/partial_close_test.go defined MockTrader - trader/auto_trader_test.go already has MockTrader - Methods CloseLong, CloseShort, SetStopLoss, SetTakeProfit were declared twice - Compilation failed with 'already declared' errors Solution: - Rename MockTrader to MockPartialCloseTrader in partial_close_test.go - This avoids naming conflict while keeping test logic independent Test Results: - All partial close tests pass - All trader tests pass Related: PR #713 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
b907155f54
commit
6df2bf202c
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"nofx/market"
|
||||
"nofx/mcp"
|
||||
"nofx/pool"
|
||||
@@ -397,9 +398,12 @@ func buildUserPrompt(ctx *Context) string {
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("%d. %s %s | 入场价%.4f 当前价%.4f | 盈亏%+.2f%% | 盈亏金额%+.2f USDT | 最高收益率%.2f%% | 杠杆%dx | 保证金%.0f | 强平价%.4f%s\n\n",
|
||||
// 计算仓位价值(用于 partial_close 检查)
|
||||
positionValue := math.Abs(pos.Quantity) * pos.MarkPrice
|
||||
|
||||
sb.WriteString(fmt.Sprintf("%d. %s %s | 入场价%.4f 当前价%.4f | 数量%.4f | 仓位价值%.2f USDT | 盈亏%+.2f%% | 盈亏金额%+.2f USDT | 最高收益率%.2f%% | 杠杆%dx | 保证金%.0f | 强平价%.4f%s\n\n",
|
||||
i+1, pos.Symbol, strings.ToUpper(pos.Side),
|
||||
pos.EntryPrice, pos.MarkPrice, pos.UnrealizedPnLPct, pos.UnrealizedPnL, pos.PeakPnLPct,
|
||||
pos.EntryPrice, pos.MarkPrice, pos.Quantity, positionValue, pos.UnrealizedPnLPct, pos.UnrealizedPnL, pos.PeakPnLPct,
|
||||
pos.Leverage, pos.MarginUsed, pos.LiquidationPrice, holdingDuration))
|
||||
|
||||
// 使用FormatMarketData输出完整市场数据
|
||||
|
||||
Reference in New Issue
Block a user