mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-04 11:30:58 +08:00
Merge pull request #462 from zhouyongyou/fix/quantity-zero-min-notional
fix(trader+decision): prevent quantity=0 error with minimum notional validation
This commit is contained in:
@@ -309,9 +309,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. 开仓金额: 建议 **≥12 USDT** (交易所最小名义价值 10 USDT + 安全边际)\n\n")
|
||||
|
||||
// 3. 输出格式 - 动态生成
|
||||
sb.WriteString("#输出格式\n\n")
|
||||
@@ -670,6 +672,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 = 12.0 // 10 + 20% 安全边际
|
||||
const minPositionSizeBTCETH = 60.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 {
|
||||
|
||||
@@ -279,6 +279,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).
|
||||
@@ -322,6 +333,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).
|
||||
@@ -748,6 +770,32 @@ func (t *FuturesTrader) SetTakeProfit(symbol string, positionSide string, quanti
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMinNotional 获取最小名义价值(Binance要求)
|
||||
func (t *FuturesTrader) GetMinNotional(symbol string) float64 {
|
||||
// 使用保守的默认值 10 USDT,确保订单能够通过交易所验证
|
||||
return 10.0
|
||||
}
|
||||
|
||||
// CheckMinNotional 检查订单是否满足最小名义价值要求
|
||||
func (t *FuturesTrader) CheckMinNotional(symbol string, quantity float64) error {
|
||||
price, err := t.GetMarketPrice(symbol)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取市价失败: %w", err)
|
||||
}
|
||||
|
||||
notionalValue := quantity * price
|
||||
minNotional := t.GetMinNotional(symbol)
|
||||
|
||||
if notionalValue < minNotional {
|
||||
return fmt.Errorf(
|
||||
"订单金额 %.2f USDT 低于最小要求 %.2f USDT (数量: %.4f, 价格: %.4f)",
|
||||
notionalValue, minNotional, quantity, price,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSymbolPrecision 获取交易对的数量精度
|
||||
func (t *FuturesTrader) GetSymbolPrecision(symbol string) (int, error) {
|
||||
exchangeInfo, err := t.client.NewExchangeInfoService().Do(context.Background())
|
||||
|
||||
Reference in New Issue
Block a user