From 85eb2b1ea70a88e66b1747a78e96dcb4cff532e2 Mon Sep 17 00:00:00 2001 From: 0xYYBB | ZYY | Bobo <128128010+the-dev-z@users.noreply.github.com> Date: Tue, 11 Nov 2025 09:47:46 +0800 Subject: [PATCH] =?UTF-8?q?fix(decision):=20=E6=B7=BB=E5=8A=A0=E6=A7=93?= =?UTF-8?q?=E6=A1=BF=E8=B6=85=E9=99=90=20fallback=20=E6=A9=9F=E5=88=B6?= =?UTF-8?q?=E4=B8=A6=E6=BE=84=E6=B8=85=E7=9B=88=E8=99=A7=E8=A8=88=E7=AE=97?= =?UTF-8?q?=E8=AA=AA=E6=98=8E=20(#716)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(decision): 添加槓桿超限 fallback 機制並澄清盈虧計算說明 1. AI 決策輸出超限槓桿時(如 20x),驗證直接拒絕導致整個交易週期失敗 2. Prompt 未明確說明盈虧百分比已包含槓桿效應,導致 AI 思維鏈中誤用價格變動% - **Before**: 超限直接報錯 → 決策失敗 - **After**: 自動降級為配置上限 → 決策繼續執行 - **效果**: SOLUSDT 20x → 自動修正為 5x(配置上限) - 明確告知 AI:系統提供的「盈虧%」已包含槓桿效應 - 公式: 盈虧% = (未實現盈虧 / 保證金) × 100 - 示例: 5x 槓桿,價格漲 2% = 實際盈利 10% - 測試山寨幣超限修正(20x → 5x) - 測試 BTC/ETH 超限修正(20x → 10x) - 測試正常範圍不修正 - 測試無效槓桿拒絕 ``` PASS: TestLeverageFallback/山寨币杠杆超限_自动修正为上限 PASS: TestLeverageFallback/BTC杠杆超限_自动修正为上限 PASS: TestLeverageFallback/杠杆在上限内_不修正 PASS: TestLeverageFallback/杠杆为0_应该报错 ``` - ✅ 向後兼容:正常槓桿範圍不受影響 - ✅ 容錯性增強:AI 輸出超限時系統自動修正 - ✅ 決策質量提升:AI 對槓桿收益有正確認知 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: tinkle-community * style: apply go fmt after rebase 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: tinkle-community --------- Co-authored-by: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Co-authored-by: tinkle-community --- decision/engine.go | 10 +++- decision/validate_test.go | 100 ++++++++++++++++++++++++++++++++++++++ manager/trader_manager.go | 1 + 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 decision/validate_test.go diff --git a/decision/engine.go b/decision/engine.go index 56633b58..076a2a13 100644 --- a/decision/engine.go +++ b/decision/engine.go @@ -731,8 +731,14 @@ func validateDecision(d *Decision, accountEquity float64, btcEthLeverage, altcoi maxPositionValue = accountEquity * 10 // BTC/ETH最多10倍账户净值 } - if d.Leverage <= 0 || d.Leverage > maxLeverage { - return fmt.Errorf("杠杆必须在1-%d之间(%s,当前配置上限%d倍): %d", maxLeverage, d.Symbol, maxLeverage, d.Leverage) + // ✅ Fallback 机制:杠杆超限时自动修正为上限值(而不是直接拒绝决策) + if d.Leverage <= 0 { + return fmt.Errorf("杠杆必须大于0: %d", d.Leverage) + } + if d.Leverage > maxLeverage { + log.Printf("⚠️ [Leverage Fallback] %s 杠杆超限 (%dx > %dx),自动调整为上限值 %dx", + d.Symbol, d.Leverage, maxLeverage, maxLeverage) + d.Leverage = maxLeverage // 自动修正为上限值 } if d.PositionSizeUSD <= 0 { return fmt.Errorf("仓位大小必须大于0: %.2f", d.PositionSizeUSD) diff --git a/decision/validate_test.go b/decision/validate_test.go new file mode 100644 index 00000000..faac4fe5 --- /dev/null +++ b/decision/validate_test.go @@ -0,0 +1,100 @@ +package decision + +import ( + "testing" +) + +// TestLeverageFallback 测试杠杆超限时的自动修正功能 +func TestLeverageFallback(t *testing.T) { + tests := []struct { + name string + decision Decision + accountEquity float64 + btcEthLeverage int + altcoinLeverage int + wantLeverage int // 期望修正后的杠杆值 + wantError bool + }{ + { + name: "山寨币杠杆超限_自动修正为上限", + decision: Decision{ + Symbol: "SOLUSDT", + Action: "open_long", + Leverage: 20, // 超过上限 + PositionSizeUSD: 100, + StopLoss: 50, + TakeProfit: 200, + }, + accountEquity: 100, + btcEthLeverage: 10, + altcoinLeverage: 5, // 上限 5x + wantLeverage: 5, // 应该修正为 5 + wantError: false, + }, + { + name: "BTC杠杆超限_自动修正为上限", + decision: Decision{ + Symbol: "BTCUSDT", + Action: "open_long", + Leverage: 20, // 超过上限 + PositionSizeUSD: 1000, + StopLoss: 90000, + TakeProfit: 110000, + }, + accountEquity: 100, + btcEthLeverage: 10, // 上限 10x + altcoinLeverage: 5, + wantLeverage: 10, // 应该修正为 10 + wantError: false, + }, + { + name: "杠杆在上限内_不修正", + decision: Decision{ + Symbol: "ETHUSDT", + Action: "open_short", + Leverage: 5, // 未超限 + PositionSizeUSD: 500, + StopLoss: 4000, + TakeProfit: 3000, + }, + accountEquity: 100, + btcEthLeverage: 10, + altcoinLeverage: 5, + wantLeverage: 5, // 保持不变 + wantError: false, + }, + { + name: "杠杆为0_应该报错", + decision: Decision{ + Symbol: "SOLUSDT", + Action: "open_long", + Leverage: 0, // 无效 + PositionSizeUSD: 100, + StopLoss: 50, + TakeProfit: 200, + }, + accountEquity: 100, + btcEthLeverage: 10, + altcoinLeverage: 5, + wantLeverage: 0, + wantError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validateDecision(&tt.decision, tt.accountEquity, tt.btcEthLeverage, tt.altcoinLeverage) + + // 检查错误状态 + if (err != nil) != tt.wantError { + t.Errorf("validateDecision() error = %v, wantError %v", err, tt.wantError) + return + } + + // 如果不应该报错,检查杠杆是否被正确修正 + if !tt.wantError && tt.decision.Leverage != tt.wantLeverage { + t.Errorf("Leverage not corrected: got %d, want %d", tt.decision.Leverage, tt.wantLeverage) + } + }) + } +} diff --git a/manager/trader_manager.go b/manager/trader_manager.go index f3ead124..9e1e9fb7 100644 --- a/manager/trader_manager.go +++ b/manager/trader_manager.go @@ -850,6 +850,7 @@ func (tm *TraderManager) LoadUserTraders(database *config.Database, userID strin // - database: 数据库实例 // - userID: 用户ID // - traderID: 交易员ID +// // 返回: // - error: 如果交易员不存在、配置无效或加载失败则返回错误 func (tm *TraderManager) LoadTraderByID(database *config.Database, userID, traderID string) error {