mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
fix(trader+decision): prevent quantity=0 error with min notional checks
User encountered API error when opening BTC position: - Account equity: 9.20 USDT - AI suggested: ~7.36 USDT position - Error: `code=-4003, msg=Quantity less than or equal to zero.` ``` quantity = 7.36 / 101808.2 ≈ 0.00007228 BTC formatted (%.3f) → "0.000" ❌ Rounded down to 0! ``` BTCUSDT precision is 3 decimals (stepSize=0.001), causing small quantities to round to 0. - ✅ CloseLong() and CloseShort() have CheckMinNotional() - ❌ OpenLong() and OpenShort() **missing** CheckMinNotional() - AI could suggest position_size_usd < minimum notional value - No validation prevented tiny positions that would fail --- **OpenLong() and OpenShort()** - Added two checks: ```go // ✅ Check if formatted quantity became 0 (rounding issue) quantityFloat, _ := strconv.ParseFloat(quantityStr, 64) if quantityFloat <= 0 { return error("Quantity too small, formatted to 0...") } // ✅ Check minimum notional value (Binance requires ≥10 USDT) if err := t.CheckMinNotional(symbol, quantityFloat); err != nil { return err } ``` **Impact**: Prevents API errors by catching invalid quantities before submission. --- Added minimum position size validation: ```go const minPositionSizeGeneral = 15.0 // Altcoins const minPositionSizeBTCETH = 100.0 // BTC/ETH (high price + precision limits) if symbol == BTC/ETH && position_size_usd < 100 { return error("BTC/ETH requires ≥100 USDT to avoid rounding to 0") } if position_size_usd < 15 { return error("Position size must be ≥15 USDT (min notional requirement)") } ``` **Impact**: Rejects invalid decisions before execution, saving API calls. --- Updated hard constraints in AI prompt: ``` 6. 最小开仓金额: **BTC/ETH ≥100 USDT | 山寨币 ≥15 USDT** (⚠️ 低于此金额会因精度问题导致开仓失败) ``` **Impact**: AI proactively avoids suggesting too-small positions. --- - ❌ User equity 9.20 USDT → suggested 7.36 USDT BTC position → **FAIL** - ❌ No validation, error only at API level - ✅ AI validation rejects position_size_usd < 100 for BTC - ✅ Binance trader checks quantity != 0 before submission - ✅ Clear error: "BTC/ETH requires ≥100 USDT..." | Symbol | position_size_usd | Price | quantity | Formatted | Result | |--------|-------------------|-------|----------|-----------|--------| | BTCUSDT | 7.36 | 101808.2 | 0.00007228 | "0.000" | ❌ Rejected (validation) | | BTCUSDT | 150 | 101808.2 | 0.00147 | "0.001" | ✅ Pass | | ADAUSDT | 15 | 1.2 | 12.5 | "12.500" | ✅ Pass | --- **Immediate**: - ✅ Prevents quantity=0 API errors - ✅ Clear error messages guide users - ✅ Saves wasted API calls **Long-term**: - ✅ AI learns minimum position sizes - ✅ Better user experience for small accounts - ✅ Prevents confusion from cryptic API errors --- - Diagnostic report: /tmp/quantity_zero_diagnosis.md - Binance min notional: 10 USDT (hardcoded in GetMinNotional())
This commit is contained in:
+21
-3
@@ -264,9 +264,11 @@ func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage in
|
||||
sb.WriteString("# 硬约束(风险控制)\n\n")
|
||||
sb.WriteString("1. 风险回报比: 必须 ≥ 1:3(冒1%风险,赚3%+收益)\n")
|
||||
sb.WriteString("2. 最多持仓: 3个币种(质量>数量)\n")
|
||||
sb.WriteString(fmt.Sprintf("3. 单币仓位: 山寨%.0f-%.0f U(%dx杠杆) | BTC/ETH %.0f-%.0f U(%dx杠杆)\n",
|
||||
accountEquity*0.8, accountEquity*1.5, altcoinLeverage, accountEquity*5, accountEquity*10, btcEthLeverage))
|
||||
sb.WriteString("4. 保证金: 总使用率 ≤ 90%\n\n")
|
||||
sb.WriteString(fmt.Sprintf("3. 单币仓位: 山寨%.0f-%.0f U | BTC/ETH %.0f-%.0f U\n",
|
||||
accountEquity*0.8, accountEquity*1.5, accountEquity*5, accountEquity*10))
|
||||
sb.WriteString(fmt.Sprintf("4. 杠杆限制: **山寨币最大%dx杠杆** | **BTC/ETH最大%dx杠杆** (⚠️ 严格执行,不可超过)\n", altcoinLeverage, btcEthLeverage))
|
||||
sb.WriteString("5. 保证金: 总使用率 ≤ 90%\n")
|
||||
sb.WriteString("6. 最小开仓金额: **BTC/ETH ≥100 USDT | 山寨币 ≥15 USDT** (⚠️ 低于此金额会因精度问题导致开仓失败)\n\n")
|
||||
|
||||
// 3. 输出格式 - 动态生成
|
||||
sb.WriteString("#输出格式\n\n")
|
||||
@@ -532,6 +534,22 @@ func validateDecision(d *Decision, accountEquity float64, btcEthLeverage, altcoi
|
||||
if d.PositionSizeUSD <= 0 {
|
||||
return fmt.Errorf("仓位大小必须大于0: %.2f", d.PositionSizeUSD)
|
||||
}
|
||||
|
||||
// ✅ 验证最小开仓金额(防止数量格式化为 0 的错误)
|
||||
// Binance 最小名义价值 10 USDT + 安全边际
|
||||
const minPositionSizeGeneral = 15.0
|
||||
const minPositionSizeBTCETH = 100.0 // BTC/ETH 因价格高和精度限制需要更大金额
|
||||
|
||||
if d.Symbol == "BTCUSDT" || d.Symbol == "ETHUSDT" {
|
||||
if d.PositionSizeUSD < minPositionSizeBTCETH {
|
||||
return fmt.Errorf("%s 开仓金额过小(%.2f USDT),必须≥%.2f USDT(因价格高且精度限制,避免数量四舍五入为0)", d.Symbol, d.PositionSizeUSD, minPositionSizeBTCETH)
|
||||
}
|
||||
} else {
|
||||
if d.PositionSizeUSD < minPositionSizeGeneral {
|
||||
return fmt.Errorf("开仓金额过小(%.2f USDT),必须≥%.2f USDT(Binance 最小名义价值要求)", d.PositionSizeUSD, minPositionSizeGeneral)
|
||||
}
|
||||
}
|
||||
|
||||
// 验证仓位价值上限(加1%容差以避免浮点数精度问题)
|
||||
tolerance := maxPositionValue * 0.01 // 1%容差
|
||||
if d.PositionSizeUSD > maxPositionValue+tolerance {
|
||||
|
||||
@@ -237,6 +237,17 @@ func (t *FuturesTrader) OpenLong(symbol string, quantity float64, leverage int)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ✅ 检查格式化后的数量是否为 0(防止四舍五入导致的错误)
|
||||
quantityFloat, parseErr := strconv.ParseFloat(quantityStr, 64)
|
||||
if parseErr != nil || quantityFloat <= 0 {
|
||||
return nil, fmt.Errorf("开倉數量過小,格式化後為 0 (原始: %.8f → 格式化: %s)。建議增加開倉金額或選擇價格更低的幣種", quantity, quantityStr)
|
||||
}
|
||||
|
||||
// ✅ 检查最小名义价值(Binance 要求至少 10 USDT)
|
||||
if err := t.CheckMinNotional(symbol, quantityFloat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建市价买入订单
|
||||
order, err := t.client.NewCreateOrderService().
|
||||
Symbol(symbol).
|
||||
@@ -280,6 +291,17 @@ func (t *FuturesTrader) OpenShort(symbol string, quantity float64, leverage int)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ✅ 检查格式化后的数量是否为 0(防止四舍五入导致的错误)
|
||||
quantityFloat, parseErr := strconv.ParseFloat(quantityStr, 64)
|
||||
if parseErr != nil || quantityFloat <= 0 {
|
||||
return nil, fmt.Errorf("开倉數量過小,格式化後為 0 (原始: %.8f → 格式化: %s)。建議增加開倉金額或選擇價格更低的幣種", quantity, quantityStr)
|
||||
}
|
||||
|
||||
// ✅ 检查最小名义价值(Binance 要求至少 10 USDT)
|
||||
if err := t.CheckMinNotional(symbol, quantityFloat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建市价卖出订单
|
||||
order, err := t.client.NewCreateOrderService().
|
||||
Symbol(symbol).
|
||||
|
||||
Reference in New Issue
Block a user