Compare commits

...

4 Commits

Author SHA1 Message Date
WrBug 07b4d654b4 清理 MarketPollingService 调试日志
- 删除多余的 debug 日志输出
2026-01-19 12:50:34 +08:00
WrBug b65827038f 添加订单详情查询脚本
- 添加 get-order-detail.js 脚本,用于获取 Polymarket 订单详情
- 添加 package.json 配置文件
- 忽略 package-lock.json (已在 .gitignore 中通过 node_modules/ 规则处理)
2026-01-19 12:50:20 +08:00
WrBug d768da72c6 清理 MarketPollingService 中多余的 debug 日志 2026-01-19 12:48:18 +08:00
WrBug 7385efff1a 优化订单详情为null时的处理逻辑
- 当订单详情为null且已发送通知超过60秒时,将订单状态改为fully_matched而不是删除
- 避免已经通过订单详情更正并发送通知的订单被意外删除
- fully_matched状态的订单不会被包含在待查询列表中,自动跳过查询
2026-01-19 12:47:03 +08:00
4 changed files with 265 additions and 7 deletions
@@ -89,8 +89,6 @@ class MarketPollingService(
*/
private suspend fun checkAndUpdateMissingMarkets() {
try {
logger.debug("开始检查缺失的市场信息...")
// 1. 获取所有买入订单的市场ID(去重)
val allOrders = copyOrderTrackingRepository.findAll()
val marketIds = allOrders.map { it.marketId }.distinct()
@@ -99,9 +97,6 @@ class MarketPollingService(
logger.debug("没有找到任何订单,跳过市场信息检查")
return
}
logger.debug("找到 ${marketIds.size} 个不同的市场ID")
// 2. 检查哪些市场信息在数据库中缺失
val existingMarkets = marketService.marketRepository.findByMarketIdIn(marketIds)
val existingMarketIds = existingMarkets.map { it.marketId }.toSet()
@@ -113,7 +108,6 @@ class MarketPollingService(
}
if (validMissingMarketIds.isEmpty()) {
logger.debug("所有市场信息都已存在,无需更新")
return
}
@@ -269,6 +269,43 @@ class OrderStatusUpdateService(
orderNullDetectionTime.getOrPut(order.buyOrderId) { System.currentTimeMillis() }
val currentTime = System.currentTimeMillis()
// 检查订单是否已经通过订单详情更正过数据并发送过通知
if (order.notificationSent) {
// 检查是否超过重试时间窗口
if (currentTime - firstDetectionTime >= ORDER_NULL_RETRY_WINDOW_MS) {
// 超过60秒,将订单状态改为 fully_matched,不再查询
logger.info("订单已发送通知且详情为 null 超过60秒,标记为 fully_matched: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}")
try {
val updatedOrder = CopyOrderTracking(
id = order.id,
copyTradingId = order.copyTradingId,
accountId = order.accountId,
leaderId = order.leaderId,
marketId = order.marketId,
side = order.side,
outcomeIndex = order.outcomeIndex,
buyOrderId = order.buyOrderId,
leaderBuyTradeId = order.leaderBuyTradeId,
quantity = order.quantity,
price = order.price,
matchedQuantity = order.matchedQuantity,
remainingQuantity = order.remainingQuantity,
status = "fully_matched", // 标记为完全匹配
notificationSent = order.notificationSent,
source = order.source,
createdAt = order.createdAt,
updatedAt = System.currentTimeMillis()
)
copyOrderTrackingRepository.save(updatedOrder)
} catch (e: Exception) {
logger.error("更新订单状态失败: orderId=${order.buyOrderId}, error=${e.message}", e)
}
}
// 清除缓存,下次重新检测
orderNullDetectionTime.remove(order.buyOrderId)
continue
}
// 检查订单是否已部分卖出,如果已部分卖出则保留订单用于统计
val hasMatchedDetails =
sellMatchDetailRepository.findByTrackingId(order.id!!).isNotEmpty()
@@ -710,6 +747,43 @@ class OrderStatusUpdateService(
orderNullDetectionTime.getOrPut(order.buyOrderId) { System.currentTimeMillis() }
val currentTime = System.currentTimeMillis()
// 检查订单是否已经通过订单详情更正过数据并发送过通知
if (order.notificationSent) {
// 检查是否超过重试时间窗口
if (currentTime - firstDetectionTime >= ORDER_NULL_RETRY_WINDOW_MS) {
// 超过60秒,将订单状态改为 fully_matched,不再查询
logger.info("订单已发送通知且详情为 null 超过60秒,标记为 fully_matched: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}")
try {
val updatedOrder = CopyOrderTracking(
id = order.id,
copyTradingId = order.copyTradingId,
accountId = order.accountId,
leaderId = order.leaderId,
marketId = order.marketId,
side = order.side,
outcomeIndex = order.outcomeIndex,
buyOrderId = order.buyOrderId,
leaderBuyTradeId = order.leaderBuyTradeId,
quantity = order.quantity,
price = order.price,
matchedQuantity = order.matchedQuantity,
remainingQuantity = order.remainingQuantity,
status = "fully_matched", // 标记为完全匹配
notificationSent = order.notificationSent,
source = order.source,
createdAt = order.createdAt,
updatedAt = System.currentTimeMillis()
)
copyOrderTrackingRepository.save(updatedOrder)
} catch (e: Exception) {
logger.error("更新订单状态失败: orderId=${order.buyOrderId}, error=${e.message}", e)
}
}
// 清除缓存,下次重新检测
orderNullDetectionTime.remove(order.buyOrderId)
continue
}
// 检查订单是否已部分卖出,如果已部分卖出则保留订单用于统计
val hasMatchedDetails = sellMatchDetailRepository.findByTrackingId(order.id!!).isNotEmpty()
if (hasMatchedDetails || order.matchedQuantity > BigDecimal.ZERO) {
@@ -746,7 +820,7 @@ class OrderStatusUpdateService(
}
// 超过重试窗口,删除本地订单
logger.warn("订单详情为 null 超过重试窗口,删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, 已等待=$((currentTime - firstDetectionTime) / 1000)s")
logger.warn("订单详情为 null 超过重试窗口,删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}, 已等待=$((currentTime - firstDetectionTime) / 1000}s")
try {
copyOrderTrackingRepository.deleteById(order.id!!)
logger.info("已删除本地订单: orderId=${order.buyOrderId}, copyOrderTrackingId=${order.id}")
+177
View File
@@ -0,0 +1,177 @@
#!/usr/bin/env node
/**
* 获取订单详情脚本
*
* 使用方法:
* node scripts/get-order-detail.js <private_key> <order_id>
*
* 参数说明:
* private_key: 钱包私钥(用于签名)
* order_id: 订单 ID
*
* 示例:
* node scripts/get-order-detail.js "0x..." "0x123..."
*/
import { Wallet } from '@ethersproject/wallet';
import { ClobClient } from '@polymarket/clob-client';
// Polymarket CLOB 主机地址
const HOST = 'https://clob.polymarket.com';
const CHAIN_ID = 137; // Polygon 主网
async function getOrderDetail(privateKey, orderId) {
try {
console.log('正在初始化钱包...');
const wallet = new Wallet(privateKey);
console.log(`钱包地址: ${wallet.address}`);
console.log('\n正在初始化 ClobClient...');
const clobClient = new ClobClient(
HOST,
CHAIN_ID,
wallet
);
console.log('\n正在获取或创建 API Key...');
try {
// 尝试 derive API key(如果已存在)
const creds = await clobClient.deriveApiKey();
console.log(`✅ API Key 已获取`);
console.log(` API Key: ${creds.key.substring(0, 10)}...`);
console.log(` Passphrase: ${creds.passphrase.substring(0, 10)}...`);
// 使用 creds 初始化一个新的 ClobClient 实例用于 L2 认证
const authenticatedClient = new ClobClient(
HOST,
CHAIN_ID,
wallet,
creds
);
console.log(`\n正在获取订单详情...`);
console.log(` 订单 ID: ${orderId}`);
const orderDetail = await authenticatedClient.getOrder(orderId);
console.log('\n================ 订单详情 ================');
console.log(`订单 ID: ${orderDetail.id}`);
console.log(`状态: ${orderDetail.status}`);
console.log(`所有者: ${orderDetail.owner}`);
console.log(`Maker 地址: ${orderDetail.maker_address}`);
console.log(`市场 ID: ${orderDetail.market}`);
console.log(`资产 ID: ${orderDetail.asset_id}`);
console.log(`方向: ${orderDetail.side}`);
console.log(`原始数量: ${orderDetail.original_size}`);
console.log(`已匹配数量: ${orderDetail.size_matched}`);
console.log(`价格: ${orderDetail.price}`);
console.log(`结果: ${orderDetail.outcome}`);
console.log(`创建时间: ${new Date(orderDetail.created_at * 1000).toISOString()}`);
console.log(`过期时间: ${orderDetail.expiration}`);
console.log(`订单类型: ${orderDetail.order_type}`);
if (orderDetail.associate_trades && orderDetail.associate_trades.length > 0) {
console.log(`关联交易数量: ${orderDetail.associate_trades.length}`);
console.log(`关联交易 IDs: ${orderDetail.associate_trades.join(', ')}`);
}
console.log('=========================================\n');
} catch (apiKeyError) {
if (apiKeyError.message && apiKeyError.message.includes('API key does not exist')) {
console.log('⚠️ API Key 不存在,正在创建新的 API Key...');
// 创建新的 API key
const creds = await clobClient.createApiKey();
console.log(`✅ API Key 已创建`);
console.log(` API Key: ${creds.key.substring(0, 10)}...`);
console.log(` Passphrase: ${creds.passphrase.substring(0, 10)}...`);
// 使用 creds 初始化一个新的 ClobClient 实例用于 L2 认证
const authenticatedClient = new ClobClient(
HOST,
CHAIN_ID,
wallet,
creds
);
console.log(`\n正在获取订单详情...`);
console.log(` 订单 ID: ${orderId}`);
const orderDetail = await authenticatedClient.getOrder(orderId);
console.log('\n================ 订单详情 ================');
console.log(`订单 ID: ${orderDetail.id}`);
console.log(`状态: ${orderDetail.status}`);
console.log(`所有者: ${orderDetail.owner}`);
console.log(`Maker 地址: ${orderDetail.maker_address}`);
console.log(`市场 ID: ${orderDetail.market}`);
console.log(`资产 ID: ${orderDetail.asset_id}`);
console.log(`方向: ${orderDetail.side}`);
console.log(`原始数量: ${orderDetail.original_size}`);
console.log(`已匹配数量: ${orderDetail.size_matched}`);
console.log(`价格: ${orderDetail.price}`);
console.log(`结果: ${orderDetail.outcome}`);
console.log(`创建时间: ${new Date(orderDetail.created_at * 1000).toISOString()}`);
console.log(`过期时间: ${orderDetail.expiration}`);
console.log(`订单类型: ${orderDetail.order_type}`);
if (orderDetail.associate_trades && orderDetail.associate_trades.length > 0) {
console.log(`关联交易数量: ${orderDetail.associate_trades.length}`);
console.log(`关联交易 IDs: ${orderDetail.associate_trades.join(', ')}`);
}
console.log('=========================================\n');
} else {
throw apiKeyError;
}
}
} catch (error) {
console.error('\n❌ 获取订单详情失败:');
console.error(error.message);
if (error.response) {
console.error(`\nHTTP 状态码: ${error.response.status}`);
console.error(`响应数据:`, error.response.data);
}
process.exit(1);
}
}
// 检查命令行参数
const args = process.argv.slice(2);
if (args.length < 2) {
console.error('错误: 缺少必要参数');
console.error('\n使用方法:');
console.error(' node scripts/get-order-detail.js <private_key> <order_id>');
console.error('\n参数说明:');
console.error(' private_key: 钱包私钥(用于签名)');
console.error(' order_id: 订单 ID');
console.error('\n示例:');
console.error(' node scripts/get-order-detail.js "0x123..." "0x456..."');
process.exit(1);
}
const [privateKey, orderId] = args;
// 验证私钥格式
if (!privateKey.startsWith('0x')) {
console.error('错误: 私钥格式不正确,必须以 0x 开头');
process.exit(1);
}
if (privateKey.length !== 66) {
console.error('错误: 私钥长度不正确,应为 66 个字符(包括 0x 前缀)');
process.exit(1);
}
// 验证订单 ID
if (!orderId.startsWith('0x')) {
console.error('错误: 订单 ID 格式不正确,必须以 0x 开头');
process.exit(1);
}
// 执行主函数
getOrderDetail(privateKey, orderId);
+13
View File
@@ -0,0 +1,13 @@
{
"name": "polyhermes-scripts",
"version": "1.0.0",
"description": "Utility scripts for Polyhermes",
"type": "module",
"scripts": {
"get-order-detail": "node get-order-detail.js"
},
"dependencies": {
"@ethersproject/wallet": "^5.7.0",
"@polymarket/clob-client": "^5.2.1"
}
}