From 4e6b8685311ee85d4233b3168db1a28f4f2f91f4 Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Tue, 4 Nov 2025 18:44:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(margin):=20correct=20position=20sizing=20fo?= =?UTF-8?q?rmula=20to=20prevent=20insufficient=20margin=20errors=20##=20Pr?= =?UTF-8?q?oblem=20AI=20was=20calculating=20position=5Fsize=5Fusd=20incorr?= =?UTF-8?q?ectly,=20treating=20it=20as=20margin=20requirement=20instead=20?= =?UTF-8?q?of=20notional=20value,=20causing=20code=3D-2019=20errors=20(ins?= =?UTF-8?q?ufficient=20margin).=20##=20Solution=20###=201.=20Updated=20AI?= =?UTF-8?q?=20prompts=20with=20correct=20formula=20-=20**prompts/adaptive.?= =?UTF-8?q?txt**:=20Added=20clear=20position=20sizing=20calculation=20step?= =?UTF-8?q?s=20-=20**prompts/nof1.txt**:=20Added=20English=20version=20wit?= =?UTF-8?q?h=20example=20-=20**prompts/default.txt**:=20Added=20Chinese=20?= =?UTF-8?q?version=20with=20example=20**Correct=20formula:**=201.=20Availa?= =?UTF-8?q?ble=20Margin=20=3D=20Available=20Cash=20=C3=97=200.95=20=C3=97?= =?UTF-8?q?=20Allocation=20%=20(reserve=205%=20for=20fees)=202.=20Notional?= =?UTF-8?q?=20Value=20=3D=20Available=20Margin=20=C3=97=20Leverage=203.=20?= =?UTF-8?q?position=5Fsize=5Fusd=20=3D=20Notional=20Value=20(this=20is=20t?= =?UTF-8?q?he=20value=20for=20JSON)=20**Example:**=20$500=20cash,=205x=20l?= =?UTF-8?q?everage=20=E2=86=92=20position=5Fsize=5Fusd=20=3D=20$2,375=20(n?= =?UTF-8?q?ot=20$500)=20###=202.=20Added=20code-level=20validation=20-=20*?= =?UTF-8?q?*trader/auto=5Ftrader.go**:=20Added=20margin=20checks=20in=20ex?= =?UTF-8?q?ecuteOpenLong/ShortWithRecord=20-=20Validates=20required=20marg?= =?UTF-8?q?in=20+=20fees=20=E2=89=A4=20available=20balance=20before=20open?= =?UTF-8?q?ing=20position=20-=20Returns=20clear=20error=20message=20if=20i?= =?UTF-8?q?nsufficient=20##=20Impact=20-=20Prevents=20code=3D-2019=20error?= =?UTF-8?q?s=20-=20AI=20now=20understands=20the=20difference=20between=20n?= =?UTF-8?q?otional=20value=20and=20margin=20requirement=20-=20Double=20val?= =?UTF-8?q?idation:=20AI=20prompt=20+=20code=20check=20##=20Testing=20-=20?= =?UTF-8?q?=E2=9C=85=20Compiles=20successfully=20-=20=E2=9A=A0=EF=B8=8F=20?= =?UTF-8?q?Requires=20live=20trading=20environment=20testing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prompts/adaptive.txt | 31 +++++++++++++++---------------- prompts/default.txt | 15 +++++++++++++++ prompts/nof1.txt | 15 ++++++++++++--- trader/auto_trader.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 19 deletions(-) diff --git a/prompts/adaptive.txt b/prompts/adaptive.txt index d5778caa..e02c59f8 100644 --- a/prompts/adaptive.txt +++ b/prompts/adaptive.txt @@ -330,26 +330,25 @@ ## 仓位计算公式 -``` -仓位大小(USD) = 可用资金 × 风险预算 / 止损距离百分比 -仓位数量(Coins) = 仓位大小(USD) / 当前价格 -``` +**重要**:position_size_usd 是**名义价值**(包含杠杆),非保证金需求。 -**示例**: -``` -账户净值:10,000 USDT -风险预算:2%(信心度 90-95) -止损距离:2%(50,000 → 49,000) +**计算步骤**: +1. **可用保证金** = Available Cash × 0.95 × Allocation %(预留5%给手续费) +2. **名义价值** = 可用保证金 × Leverage +3. **position_size_usd** = 名义价值(这是 JSON 中应填写的值) +4. **Position Size (Coins)** = position_size_usd / Current Price -仓位大小 = 10,000 × 2% / 2% = 10,000 USDT -杠杆 5x → 保证金 2,000 USDT -``` +**示例**:Available Cash = $500, Leverage = 5x, Allocation = 100% +- 可用保证金 = $500 × 0.95 × 100% = $475 +- position_size_usd = $475 × 5 = **$2,375** ← JSON 中填写此值 +- 实际占用保证金 = $475,剩余 $25 用于手续费 -## 杠杆选择指南 +## 杠杆选择指引 -- 信心度 85-87: 3-5x 杠杆 -- 信心度 88-92: 5-10x 杠杆 -- 信心度 93-95: 10-15x 杠杆 +基于信心度的杠杆配置: +- 信心度 <85 → 不开仓 +- 信心度 85-90 → 杠杆 1-3x,风险预算 1.5% +- 信心度 90-95 → 杠杆 3-8x,风险预算 2% - 信心度 >95: 最高 20x 杠杆(谨慎) ## 风险控制原则 diff --git a/prompts/default.txt b/prompts/default.txt index 310978ac..3094e473 100644 --- a/prompts/default.txt +++ b/prompts/default.txt @@ -106,6 +106,21 @@ 3. 寻找新机会: 有强信号吗?多空机会? 4. 输出决策: 思维链分析 + JSON +# 仓位大小计算 + +**重要**:`position_size_usd` 是**名义价值**(包含杠杆),非保证金需求。 + +**计算步骤**: +1. **可用保证金** = Available Cash × 0.95 × 配置比例(预留5%手续费) +2. **名义价值** = 可用保证金 × Leverage +3. **position_size_usd** = 名义价值(JSON中填写此值) +4. **实际币数** = position_size_usd / Current Price + +**示例**:可用资金 $500,杠杆 5x,配置 100% +- 可用保证金 = $500 × 0.95 = $475 +- position_size_usd = $475 × 5 = **$2,375** ← JSON填此值 +- 实际占用保证金 = $475,剩余 $25 用于手续费 + --- 记住: diff --git a/prompts/nof1.txt b/prompts/nof1.txt index 012daa62..e57efbec 100644 --- a/prompts/nof1.txt +++ b/prompts/nof1.txt @@ -45,10 +45,19 @@ You have exactly FOUR possible actions per decision cycle: # POSITION SIZING FRAMEWORK -Calculate position size using this formula: +**IMPORTANT**: `position_size_usd` is the **notional value** (includes leverage), NOT margin requirement. -Position Size (USD) = Available Cash × Leverage × Allocation % -Position Size (Coins) = Position Size (USD) / Current Price +## Calculation Steps: + +1. **Available Margin** = Available Cash × 0.95 × Allocation % (reserve 5% for fees) +2. **Notional Value** = Available Margin × Leverage +3. **position_size_usd** = Notional Value (this is the value for JSON) +4. **Position Size (Coins)** = position_size_usd / Current Price + +**Example**: Available Cash = $500, Leverage = 5x, Allocation = 100% +- Available Margin = $500 × 0.95 × 100% = $475 +- position_size_usd = $475 × 5 = **$2,375** ← Fill this value in JSON +- Actual margin used = $475, remaining $25 for fees ## Sizing Considerations diff --git a/trader/auto_trader.go b/trader/auto_trader.go index 1e93ab5c..a598bde8 100644 --- a/trader/auto_trader.go +++ b/trader/auto_trader.go @@ -626,6 +626,27 @@ func (at *AutoTrader) executeOpenLongWithRecord(decision *decision.Decision, act actionRecord.Quantity = quantity actionRecord.Price = marketData.CurrentPrice + // ⚠️ 保证金验证:防止保证金不足错误(code=-2019) + requiredMargin := decision.PositionSizeUSD / float64(decision.Leverage) + + balance, err := at.trader.GetBalance() + if err != nil { + return fmt.Errorf("获取账户余额失败: %w", err) + } + availableBalance := 0.0 + if avail, ok := balance["availableBalance"].(float64); ok { + availableBalance = avail + } + + // 手续费估算(Taker费率 0.04%) + estimatedFee := decision.PositionSizeUSD * 0.0004 + totalRequired := requiredMargin + estimatedFee + + if totalRequired > availableBalance { + return fmt.Errorf("❌ 保证金不足: 需要 %.2f USDT(保证金 %.2f + 手续费 %.2f),可用 %.2f USDT", + totalRequired, requiredMargin, estimatedFee, availableBalance) + } + // 设置仓位模式 if err := at.trader.SetMarginMode(decision.Symbol, at.config.IsCrossMargin); err != nil { log.Printf(" ⚠️ 设置仓位模式失败: %v", err) @@ -685,6 +706,27 @@ func (at *AutoTrader) executeOpenShortWithRecord(decision *decision.Decision, ac actionRecord.Quantity = quantity actionRecord.Price = marketData.CurrentPrice + // ⚠️ 保证金验证:防止保证金不足错误(code=-2019) + requiredMargin := decision.PositionSizeUSD / float64(decision.Leverage) + + balance, err := at.trader.GetBalance() + if err != nil { + return fmt.Errorf("获取账户余额失败: %w", err) + } + availableBalance := 0.0 + if avail, ok := balance["availableBalance"].(float64); ok { + availableBalance = avail + } + + // 手续费估算(Taker费率 0.04%) + estimatedFee := decision.PositionSizeUSD * 0.0004 + totalRequired := requiredMargin + estimatedFee + + if totalRequired > availableBalance { + return fmt.Errorf("❌ 保证金不足: 需要 %.2f USDT(保证金 %.2f + 手续费 %.2f),可用 %.2f USDT", + totalRequired, requiredMargin, estimatedFee, availableBalance) + } + // 设置仓位模式 if err := at.trader.SetMarginMode(decision.Symbol, at.config.IsCrossMargin); err != nil { log.Printf(" ⚠️ 设置仓位模式失败: %v", err)