From 185cade11d0904ad2b41b4e8c31ac758a528bea0 Mon Sep 17 00:00:00 2001 From: WrBug Date: Sun, 4 Jan 2026 12:46:35 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=8C=89=E6=AF=94?= =?UTF-8?q?=E4=BE=8B=E8=B7=9F=E5=8D=95=E9=87=91=E9=A2=9D=E7=B2=BE=E5=BA=A6?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=8C=E5=AF=B9=E9=87=91=E9=A2=9D=E8=BF=9B?= =?UTF-8?q?=E8=A1=8C=E5=90=91=E4=B8=8A=E5=8F=96=E6=95=B4=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复按比例计算的订单金额因精度问题导致低于最小限制的问题 - 对按比例计算的金额进行向上取整到2位小数(USDC精度) - 如果向上取整后的金额仍低于最小限制,调整 buyQuantity 以满足最小限制 - 优化 BigDecimal.div 扩展函数,支持精度和舍入模式参数(带默认值) 修复的文件: - MathExt.kt: 添加 div 扩展函数的重载版本,支持精度和舍入模式参数 - CopyOrderTrackingService.kt: 修复订单金额精度处理逻辑 问题: 订单金额 0.000999999860888892000000 因精度问题低于最小限制 1.00000000, 导致订单被错误跳过。 解决方案: 1. 对按比例计算的金额向上取整到2位小数 2. 如果向上取整后仍低于最小限制,调整数量以满足最小限制 3. 确保调整后的订单金额 >= minOrderSize --- .../statistics/CopyOrderTrackingService.kt | 45 ++++++++++++++++--- .../com/wrbug/polymarketbot/util/MathExt.kt | 20 +++++---- 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/CopyOrderTrackingService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/CopyOrderTrackingService.kt index 73a6b21..913ad9f 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/CopyOrderTrackingService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/CopyOrderTrackingService.kt @@ -376,15 +376,46 @@ open class CopyOrderTrackingService( // 验证订单数量限制(仅比例模式) var finalBuyQuantity = buyQuantity if (copyTrading.copyMode == "RATIO") { - val orderAmount = buyQuantity.multi(trade.price.toSafeBigDecimal()) - if (orderAmount.lt(copyTrading.minOrderSize)) { - logger.warn("订单金额低于最小限制,跳过: copyTradingId=${copyTrading.id}, amount=$orderAmount, min=${copyTrading.minOrderSize}") - continue + val tradePrice = trade.price.toSafeBigDecimal() + val rawOrderAmount = buyQuantity.multi(tradePrice) + + // 对按比例计算的金额进行向上取整处理(确保满足最小限制) + // 向上取整到 2 位小数(USDC 精度) + val roundedOrderAmount = rawOrderAmount.setScale(2, java.math.RoundingMode.CEILING) + + // 如果原始金额或向上取整后的金额小于最小限制,调整 buyQuantity 以满足最小限制 + // 这样可以避免精度问题导致订单被错误地跳过 + if (roundedOrderAmount.lt(copyTrading.minOrderSize)) { + logger.debug("订单金额(向上取整后)低于最小限制,调整数量以满足最小限制: copyTradingId=${copyTrading.id}, rawAmount=$rawOrderAmount, roundedAmount=$roundedOrderAmount, min=${copyTrading.minOrderSize}") + // 计算满足最小限制所需的数量(向上取整) + val minQuantity = copyTrading.minOrderSize.div(tradePrice, 8, java.math.RoundingMode.CEILING) + if (minQuantity.lte(BigDecimal.ZERO)) { + logger.warn("计算出的最小数量为0或负数,跳过: copyTradingId=${copyTrading.id}") + continue + } + // 使用调整后的数量 + finalBuyQuantity = minQuantity + logger.debug("已调整数量以满足最小限制: copyTradingId=${copyTrading.id}, originalQuantity=$buyQuantity, adjustedQuantity=$finalBuyQuantity, adjustedAmount=${finalBuyQuantity.multi(tradePrice)}") + } else if (rawOrderAmount.lt(copyTrading.minOrderSize)) { + // 原始金额小于最小限制,但向上取整后满足,调整数量以满足最小限制 + logger.debug("订单金额(精度处理后)低于最小限制,调整数量以满足最小限制: copyTradingId=${copyTrading.id}, rawAmount=$rawOrderAmount, min=${copyTrading.minOrderSize}") + // 计算满足最小限制所需的数量(向上取整) + val minQuantity = copyTrading.minOrderSize.div(tradePrice, 8, java.math.RoundingMode.CEILING) + if (minQuantity.lte(BigDecimal.ZERO)) { + logger.warn("计算出的最小数量为0或负数,跳过: copyTradingId=${copyTrading.id}") + continue + } + // 使用调整后的数量 + finalBuyQuantity = minQuantity + logger.debug("已调整数量以满足最小限制: copyTradingId=${copyTrading.id}, originalQuantity=$buyQuantity, adjustedQuantity=$finalBuyQuantity, adjustedAmount=${finalBuyQuantity.multi(tradePrice)}") } - if (orderAmount.gt(copyTrading.maxOrderSize)) { - logger.warn("订单金额超过最大限制,调整数量: copyTradingId=${copyTrading.id}, amount=$orderAmount, max=${copyTrading.maxOrderSize}") + + // 检查最大限制(使用调整后的数量) + val finalOrderAmount = finalBuyQuantity.multi(tradePrice) + if (finalOrderAmount.gt(copyTrading.maxOrderSize)) { + logger.warn("订单金额超过最大限制,调整数量: copyTradingId=${copyTrading.id}, amount=$finalOrderAmount, max=${copyTrading.maxOrderSize}") // 调整数量到最大值 - val adjustedQuantity = copyTrading.maxOrderSize.div(trade.price.toSafeBigDecimal()) + val adjustedQuantity = copyTrading.maxOrderSize.div(tradePrice, 8, java.math.RoundingMode.DOWN) if (adjustedQuantity.lte(BigDecimal.ZERO)) { logger.warn("调整后的数量为0或负数,跳过: copyTradingId=${copyTrading.id}") continue diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/util/MathExt.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/util/MathExt.kt index 826412b..7102604 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/util/MathExt.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/util/MathExt.kt @@ -26,21 +26,23 @@ fun BigDecimal.multi(value: Any): BigDecimal { return BigDecimal.ZERO } + /** - * BigDecimal除法扩展函数 + * BigDecimal除法扩展函数(带精度和舍入模式) * 安全地将BigDecimal与任意数值类型相除 * @param value 除数,支持BigDecimal、BigInteger类型或可转换为BigDecimal的字符串 - * @return 除法结果,精度为18位小数,使用四舍五入模式,如果转换失败返回IllegalBigDecimal + * @param scale 精度(小数位数) + * @param roundingMode 舍入模式 + * @return 除法结果,如果转换失败返回IllegalBigDecimal */ -fun BigDecimal.div(value: Any): BigDecimal { +fun BigDecimal.div(value: Any, scale: Int = 18, roundingMode: RoundingMode = RoundingMode.HALF_UP): BigDecimal { kotlin.runCatching { - if (value is BigDecimal) { - return divide(value, 18, RoundingMode.HALF_UP).stripTrailingZeros() + val divisor = when (value) { + is BigDecimal -> value + is BigInteger -> value.toBigDecimal() + else -> BigDecimal(value.toString()) } - if (value is BigInteger) { - return divide(value.toSafeBigDecimal(), 18, RoundingMode.HALF_UP).stripTrailingZeros() - } - return divide(BigDecimal(value.toString()), 18, RoundingMode.HALF_UP).stripTrailingZeros() + return divide(divisor, scale, roundingMode) } return IllegalBigDecimal }