From fe2db11b75eb1597fd2bbc0fa697409fd5315f90 Mon Sep 17 00:00:00 2001 From: WrBug Date: Thu, 1 Jan 2026 12:03:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=B6=E4=BB=96=E4=BB=A3=E7=A0=81=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CopyOrderTrackingRepository: 添加查询方法 - PolymarketClobService: 功能更新 - CopyOrderTrackingService: 逻辑优化 - OrderStatusUpdateService: 功能增强 - TelegramNotificationService: 通知优化 --- .../repository/CopyOrderTrackingRepository.kt | 6 + .../service/common/PolymarketClobService.kt | 18 ++- .../statistics/CopyOrderTrackingService.kt | 14 +- .../statistics/OrderStatusUpdateService.kt | 143 +++++++++++++++++- .../system/TelegramNotificationService.kt | 31 ++-- 5 files changed, 187 insertions(+), 25 deletions(-) diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/repository/CopyOrderTrackingRepository.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/repository/CopyOrderTrackingRepository.kt index f49051b..84d1bae 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/repository/CopyOrderTrackingRepository.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/repository/CopyOrderTrackingRepository.kt @@ -55,5 +55,11 @@ interface CopyOrderTrackingRepository : JpaRepository { * 查询未发送通知的买入订单(用于轮询更新) */ fun findByNotificationSentFalse(): List + + /** + * 查询指定时间之前创建的订单(用于检查30秒后未成交的订单) + */ + @Query("SELECT t FROM CopyOrderTracking t WHERE t.createdAt <= :beforeTime") + fun findByCreatedAtBefore(beforeTime: Long): List } diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/common/PolymarketClobService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/common/PolymarketClobService.kt index 6cb5b9c..30f1551 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/common/PolymarketClobService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/common/PolymarketClobService.kt @@ -300,13 +300,23 @@ class PolymarketClobService( ) val response = authenticatedClobApi.getOrder(orderId) - if (response.isSuccessful && response.body() != null) { - Result.success(response.body()!!) + if (response.isSuccessful) { + val body = response.body() + if (body != null) { + Result.success(body) + } else { + // 响应体为空,可能是订单不存在或已过期 + logger.warn("获取订单详情失败: 响应体为空, orderId=$orderId, code=${response.code()}") + Result.failure(Exception("订单不存在或已过期: orderId=$orderId")) + } } else { - Result.failure(Exception("获取订单详情失败: ${response.code()} ${response.message()}")) + // HTTP 状态码不是 2xx + val errorBody = response.errorBody()?.string()?.take(200) ?: "无错误详情" + logger.warn("获取订单详情失败: HTTP ${response.code()}, orderId=$orderId, errorBody=$errorBody") + Result.failure(Exception("获取订单详情失败: HTTP ${response.code()} ${response.message()}")) } } catch (e: Exception) { - logger.error("获取订单详情异常: ${e.message}", e) + logger.error("获取订单详情异常: orderId=$orderId, ${e.message}", e) Result.failure(e) } } 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 e044c75..ba1f088 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 @@ -1496,12 +1496,18 @@ open class CopyOrderTrackingService( return try { // 1. 查询订单详情 val orderResponse = clobApi.getOrder(orderId) - if (!orderResponse.isSuccessful || orderResponse.body() == null) { - logger.warn("查询订单详情失败: orderId=$orderId, code=${orderResponse.code()}") + if (!orderResponse.isSuccessful) { + val errorBody = orderResponse.errorBody()?.string()?.take(200) ?: "无错误详情" + logger.warn("查询订单详情失败: orderId=$orderId, code=${orderResponse.code()}, errorBody=$errorBody") + return fallbackPrice + } + + val order = orderResponse.body() + if (order == null) { + // 响应体为空,可能是订单不存在或已过期 + logger.warn("查询订单详情失败: 响应体为空, orderId=$orderId, code=${orderResponse.code()}") return fallbackPrice } - - val order = orderResponse.body()!! // 2. 如果订单未成交,使用下单价格 if (order.status != "FILLED" && order.sizeMatched.toSafeBigDecimal() <= BigDecimal.ZERO) { diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/OrderStatusUpdateService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/OrderStatusUpdateService.kt index 0f6281c..88d12ae 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/OrderStatusUpdateService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/statistics/OrderStatusUpdateService.kt @@ -56,10 +56,13 @@ class OrderStatusUpdateService( // 1. 清理已删除账户的订单 cleanupDeletedAccountOrders() - // 2. 更新卖出订单的实际成交价并发送通知(priceUpdated 共用字段) + // 2. 检查30秒前创建的订单,如果未成交则删除 + checkAndDeleteUnfilledOrders() + + // 3. 更新卖出订单的实际成交价并发送通知(priceUpdated 共用字段) updatePendingSellOrderPrices() - // 3. 更新买入订单的实际数据并发送通知 + // 4. 更新买入订单的实际数据并发送通知 updatePendingBuyOrders() } catch (e: Exception) { logger.error("订单状态更新异常: ${e.message}", e) @@ -130,6 +133,124 @@ class OrderStatusUpdateService( } } + /** + * 检查30秒前创建的订单,如果未成交则删除 + * 首次检测但加入缓存中30s后还没有成交,则删除 + */ + @Transactional + private suspend fun checkAndDeleteUnfilledOrders() { + try { + // 计算30秒前的时间戳 + val thirtySecondsAgo = System.currentTimeMillis() - 30000 + + // 查询30秒前创建的订单 + val ordersToCheck = copyOrderTrackingRepository.findByCreatedAtBefore(thirtySecondsAgo) + + if (ordersToCheck.isEmpty()) { + return + } + + logger.debug("检查 ${ordersToCheck.size} 个30秒前创建的订单是否成交") + + // 按账户分组,避免重复创建 API 客户端 + val ordersByAccount = ordersToCheck.groupBy { it.accountId } + + for ((accountId, orders) in ordersByAccount) { + try { + // 获取账户 + val account = accountRepository.findById(accountId).orElse(null) + if (account == null) { + logger.warn("账户不存在,跳过检查: accountId=$accountId") + continue + } + + // 检查账户是否配置了 API 凭证 + if (account.apiKey == null || account.apiSecret == null || account.apiPassphrase == null) { + logger.debug("账户未配置 API 凭证,跳过检查: accountId=${account.id}") + continue + } + + // 解密 API 凭证 + val apiSecret = try { + cryptoUtils.decrypt(account.apiSecret!!) + } catch (e: Exception) { + logger.warn("解密 API Secret 失败: accountId=${account.id}, error=${e.message}") + continue + } + + val apiPassphrase = try { + cryptoUtils.decrypt(account.apiPassphrase!!) + } catch (e: Exception) { + logger.warn("解密 API Passphrase 失败: accountId=${account.id}, error=${e.message}") + continue + } + + // 创建带认证的 CLOB API 客户端 + val clobApi = retrofitFactory.createClobApi( + account.apiKey!!, + apiSecret, + apiPassphrase, + account.walletAddress + ) + + // 检查每个订单 + for (order in orders) { + try { + // 查询订单详情 + val orderResponse = clobApi.getOrder(order.buyOrderId) + + if (!orderResponse.isSuccessful) { + // HTTP 错误,可能是订单不存在,删除 + logger.info("订单查询失败(HTTP错误),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}") + try { + copyOrderTrackingRepository.deleteById(order.id!!) + logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") + } catch (e: Exception) { + logger.error("删除本地订单失败: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, error=${e.message}", e) + } + continue + } + + val orderDetail = orderResponse.body() + if (orderDetail == null) { + // HTTP 200 但响应体为空,表示订单不存在,删除 + logger.info("订单不存在(响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}") + try { + copyOrderTrackingRepository.deleteById(order.id!!) + logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") + } catch (e: Exception) { + logger.error("删除本地订单失败: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, error=${e.message}", e) + } + continue + } + + // 检查订单是否成交 + // 如果订单状态不是 FILLED 且已成交数量为0,说明未成交,删除 + val sizeMatched = orderDetail.sizeMatched?.toSafeBigDecimal() ?: BigDecimal.ZERO + if (orderDetail.status != "FILLED" && sizeMatched <= BigDecimal.ZERO) { + logger.info("订单30秒后仍未成交,删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, status=${orderDetail.status}, sizeMatched=$sizeMatched") + try { + copyOrderTrackingRepository.deleteById(order.id!!) + logger.info("已删除未成交订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") + } catch (e: Exception) { + logger.error("删除未成交订单失败: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, error=${e.message}", e) + } + } else { + logger.debug("订单已成交或部分成交,保留: orderId=${order.buyOrderId}, status=${orderDetail.status}, sizeMatched=$sizeMatched") + } + } catch (e: Exception) { + logger.error("检查订单失败: orderId=${order.buyOrderId}, error=${e.message}", e) + } + } + } catch (e: Exception) { + logger.error("检查账户订单失败: accountId=$accountId, error=${e.message}", e) + } + } + } catch (e: Exception) { + logger.error("检查未成交订单异常: ${e.message}", e) + } + } + /** * 更新待更新的卖出订单价格 * 注意:priceUpdated 现在同时表示价格已更新和通知已发送(共用字段) @@ -456,12 +577,24 @@ class OrderStatusUpdateService( // 查询订单详情 val orderResponse = clobApi.getOrder(order.buyOrderId) - if (!orderResponse.isSuccessful || orderResponse.body() == null) { - logger.debug("查询订单详情失败,等待下次轮询: orderId=${order.buyOrderId}, code=${orderResponse.code()}") + if (!orderResponse.isSuccessful) { + val errorBody = orderResponse.errorBody()?.string()?.take(200) ?: "无错误详情" + logger.debug("查询订单详情失败,等待下次轮询: orderId=${order.buyOrderId}, code=${orderResponse.code()}, errorBody=$errorBody") continue } - val orderDetail = orderResponse.body()!! + val orderDetail = orderResponse.body() + if (orderDetail == null) { + // HTTP 200 但响应体为空,表示订单不存在(没有交易成功),删除本地订单 + logger.info("订单不存在(响应体为空),删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, code=${orderResponse.code()}") + try { + copyOrderTrackingRepository.deleteById(order.id!!) + logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}") + } catch (e: Exception) { + logger.error("删除本地订单失败: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, error=${e.message}", e) + } + continue + } // 获取实际价格和数量 val actualPrice = orderDetail.price?.toSafeBigDecimal() ?: order.price diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/system/TelegramNotificationService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/system/TelegramNotificationService.kt index 0406ccc..24860ae 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/system/TelegramNotificationService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/system/TelegramNotificationService.kt @@ -97,21 +97,28 @@ class TelegramNotificationService( if ((actualPrice == null || actualSize == null) && orderId != null && clobApi != null && apiKey != null && apiSecret != null && apiPassphrase != null && walletAddressForApi != null) { try { val orderResponse = clobApi.getOrder(orderId) - if (orderResponse.isSuccessful && orderResponse.body() != null) { - val order = orderResponse.body()!! - if (actualPrice == null) { - actualPrice = order.price - } - if (actualSize == null) { - actualSize = order.originalSize // 使用 originalSize 作为订单数量 - } - actualSide = order.side // 使用订单详情中的 side - if (actualOutcome == null) { - actualOutcome = order.outcome // 使用订单详情中的 outcome(市场方向) + if (orderResponse.isSuccessful) { + val order = orderResponse.body() + if (order != null) { + if (actualPrice == null) { + actualPrice = order.price + } + if (actualSize == null) { + actualSize = order.originalSize // 使用 originalSize 作为订单数量 + } + actualSide = order.side // 使用订单详情中的 side + if (actualOutcome == null) { + actualOutcome = order.outcome // 使用订单详情中的 outcome(市场方向) + } + } else { + logger.debug("查询订单详情失败: 响应体为空, orderId=$orderId") } + } else { + val errorBody = orderResponse.errorBody()?.string()?.take(200) ?: "无错误详情" + logger.debug("查询订单详情失败: orderId=$orderId, code=${orderResponse.code()}, errorBody=$errorBody") } } catch (e: Exception) { - logger.warn("查询订单详情失败: ${e.message}", e) + logger.warn("查询订单详情失败: orderId=$orderId, ${e.message}", e) } }