polycopy 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +60 -127
- package/dist/index.js +292 -150
- package/dist/{init-Bs-N_AZi.js → process-lock-BbFF27h8.js} +183 -220
- package/package.json +4 -5
- package/dist/config.js +0 -13
- package/dist/paths-CEjGES8j.js +0 -20
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
const
|
|
2
|
+
const processLock = require("./process-lock-BbFF27h8.js");
|
|
3
3
|
const grammy = require("grammy");
|
|
4
|
+
const log4js = require("log4js");
|
|
5
|
+
const path = require("path");
|
|
4
6
|
const wallet = require("@ethersproject/wallet");
|
|
5
7
|
const clobClient = require("@polymarket/clob-client");
|
|
6
8
|
const builderSigningSdk = require("@polymarket/builder-signing-sdk");
|
|
@@ -11,7 +13,6 @@ const builderRelayerClient = require("@polymarket/builder-relayer-client");
|
|
|
11
13
|
const setPromiseInterval = require("set-promise-interval");
|
|
12
14
|
const child_process = require("child_process");
|
|
13
15
|
const fs = require("fs");
|
|
14
|
-
const paths = require("./paths-CEjGES8j.js");
|
|
15
16
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
16
17
|
function _interopNamespace(e) {
|
|
17
18
|
if (e && e.__esModule) return e;
|
|
@@ -30,8 +31,114 @@ function _interopNamespace(e) {
|
|
|
30
31
|
n.default = e;
|
|
31
32
|
return Object.freeze(n);
|
|
32
33
|
}
|
|
34
|
+
const log4js__default = /* @__PURE__ */ _interopDefault(log4js);
|
|
35
|
+
const path__namespace = /* @__PURE__ */ _interopNamespace(path);
|
|
33
36
|
const setPromiseInterval__default = /* @__PURE__ */ _interopDefault(setPromiseInterval);
|
|
34
37
|
const fs__namespace = /* @__PURE__ */ _interopNamespace(fs);
|
|
38
|
+
const LOG_FILE = path__namespace.join(processLock.LOGS_DIR, "polycopy.log");
|
|
39
|
+
path__namespace.join(processLock.LOGS_DIR, "polycopy");
|
|
40
|
+
log4js__default.default.configure({
|
|
41
|
+
appenders: {
|
|
42
|
+
file: {
|
|
43
|
+
type: "dateFile",
|
|
44
|
+
filename: LOG_FILE,
|
|
45
|
+
pattern: "yyyy-MM-dd",
|
|
46
|
+
alwaysIncludePattern: true,
|
|
47
|
+
compress: false,
|
|
48
|
+
keepFileExt: true,
|
|
49
|
+
maxLogSize: 2 * 1024 * 1024,
|
|
50
|
+
backups: 10,
|
|
51
|
+
layout: { type: "pattern", pattern: "[%d{yyyy-MM-dd hh:mm:ss}] %m" }
|
|
52
|
+
},
|
|
53
|
+
stdout: {
|
|
54
|
+
type: "stdout",
|
|
55
|
+
layout: { type: "pattern", pattern: "[%d{yyyy-MM-dd hh:mm:ss}] %m" }
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
categories: {
|
|
59
|
+
default: { appenders: ["stdout", "file"], level: "info" }
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const log4jsLogger = log4js__default.default.getLogger("polycopy");
|
|
63
|
+
const formatMessage = (message, args) => args.length > 0 ? `${message} ${args.join(" ")}` : message;
|
|
64
|
+
class Logger {
|
|
65
|
+
initialized = false;
|
|
66
|
+
/**
|
|
67
|
+
* 初始化日志模块(输出日志路径信息)
|
|
68
|
+
*/
|
|
69
|
+
init() {
|
|
70
|
+
if (this.initialized) return;
|
|
71
|
+
this.initialized = true;
|
|
72
|
+
console.log(`
|
|
73
|
+
📁 日志目录: ${processLock.LOGS_DIR}`);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 输出信息日志(前后自动添加空行)
|
|
77
|
+
*/
|
|
78
|
+
info(message, ...args) {
|
|
79
|
+
log4jsLogger.info(formatMessage(message, args));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 输出成功日志(前后自动添加空行)
|
|
83
|
+
*/
|
|
84
|
+
success(message, ...args) {
|
|
85
|
+
log4jsLogger.info(`✅ ${formatMessage(message, args)}`);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 输出警告日志(前后自动添加空行)
|
|
89
|
+
*/
|
|
90
|
+
warning(message, ...args) {
|
|
91
|
+
log4jsLogger.warn(`⚠️ ${formatMessage(message, args)}`);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 输出错误日志(前后自动添加空行)
|
|
95
|
+
*/
|
|
96
|
+
error(message, ...args) {
|
|
97
|
+
log4jsLogger.error(`❌ ${formatMessage(message, args)}`);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 输出章节标题(前后自动添加空行)
|
|
101
|
+
*/
|
|
102
|
+
section(title) {
|
|
103
|
+
log4jsLogger.info(`=== ${title} ===`);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 输出列表项(不添加空行,用于连续输出)
|
|
107
|
+
*/
|
|
108
|
+
item(message, indent = 0) {
|
|
109
|
+
const indentStr = " ".repeat(indent);
|
|
110
|
+
const formatted = `${indentStr}${message}`;
|
|
111
|
+
log4jsLogger.info(formatted);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 输出普通文本(不添加空行,用于连续输出)
|
|
115
|
+
*/
|
|
116
|
+
text(message, ...args) {
|
|
117
|
+
log4jsLogger.info(formatMessage(message, args));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 输出空行
|
|
121
|
+
*/
|
|
122
|
+
blank() {
|
|
123
|
+
console.log("");
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 输出带前缀的信息(不添加空行)
|
|
127
|
+
* 用于在同一组日志中输出多行信息
|
|
128
|
+
*/
|
|
129
|
+
line(prefix, message, ...args) {
|
|
130
|
+
const formatted = args.length > 0 ? ` ${prefix} ${message} ${args.join(" ")}` : ` ${prefix} ${message}`;
|
|
131
|
+
log4jsLogger.info(formatted);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 刷新日志输出(用于进程退出前)
|
|
135
|
+
*/
|
|
136
|
+
async flush(timeoutMs = 1e3) {
|
|
137
|
+
await log4js__default.default.shutdown();
|
|
138
|
+
await new Promise((resolve) => setTimeout(resolve, timeoutMs));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const logger = new Logger();
|
|
35
142
|
function parseSignalMetadata(messageText) {
|
|
36
143
|
const match = messageText.match(/🤖\s*({.*})/s);
|
|
37
144
|
if (!match) {
|
|
@@ -41,7 +148,7 @@ function parseSignalMetadata(messageText) {
|
|
|
41
148
|
const jsonStr = match[1];
|
|
42
149
|
const metadata = JSON.parse(jsonStr);
|
|
43
150
|
if (!metadata.eventId || !metadata.assetId || !metadata.outcome || !metadata.endTime) {
|
|
44
|
-
|
|
151
|
+
logger.error("解析失败: 缺少必需字段", {
|
|
45
152
|
hasEventId: !!metadata.eventId,
|
|
46
153
|
hasAssetId: !!metadata.assetId,
|
|
47
154
|
hasOutcome: !!metadata.outcome,
|
|
@@ -50,14 +157,14 @@ function parseSignalMetadata(messageText) {
|
|
|
50
157
|
return null;
|
|
51
158
|
}
|
|
52
159
|
if (metadata.outcome !== "Up" && metadata.outcome !== "Down") {
|
|
53
|
-
|
|
160
|
+
logger.error("解析失败: outcome 值无效", {
|
|
54
161
|
outcome: metadata.outcome,
|
|
55
162
|
expected: "Up 或 Down"
|
|
56
163
|
});
|
|
57
164
|
return null;
|
|
58
165
|
}
|
|
59
166
|
if (typeof metadata.endTime !== "number" || metadata.endTime <= 0) {
|
|
60
|
-
|
|
167
|
+
logger.error("解析失败: endTime 值无效", {
|
|
61
168
|
endTime: metadata.endTime,
|
|
62
169
|
expected: "毫秒级时间戳"
|
|
63
170
|
});
|
|
@@ -65,9 +172,9 @@ function parseSignalMetadata(messageText) {
|
|
|
65
172
|
}
|
|
66
173
|
return metadata;
|
|
67
174
|
} catch (error) {
|
|
68
|
-
|
|
175
|
+
logger.error("解析信号元数据失败:", error);
|
|
69
176
|
if (error instanceof Error) {
|
|
70
|
-
|
|
177
|
+
logger.line("", `错误详情: ${error.message}`);
|
|
71
178
|
}
|
|
72
179
|
return null;
|
|
73
180
|
}
|
|
@@ -97,12 +204,12 @@ class TelegramListener {
|
|
|
97
204
|
await this.handleMyChatMemberUpdate(ctx);
|
|
98
205
|
});
|
|
99
206
|
this.bot.catch((err) => {
|
|
100
|
-
|
|
207
|
+
logger.error("Telegram Bot 错误:", err);
|
|
101
208
|
if (err.error) {
|
|
102
|
-
|
|
209
|
+
logger.line("", `错误详情: ${err.error}`);
|
|
103
210
|
}
|
|
104
211
|
if (err.ctx) {
|
|
105
|
-
|
|
212
|
+
logger.line("", `上下文信息:`, {
|
|
106
213
|
chatId: err.ctx.chat?.id,
|
|
107
214
|
messageId: err.ctx.message?.message_id
|
|
108
215
|
});
|
|
@@ -169,28 +276,28 @@ class TelegramListener {
|
|
|
169
276
|
reason = "需要管理员权限才能监听";
|
|
170
277
|
}
|
|
171
278
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
279
|
+
logger.info(`📢 频道状态更新: ${chatTitle}`);
|
|
280
|
+
logger.line("", action);
|
|
281
|
+
logger.line(
|
|
175
282
|
"",
|
|
176
283
|
`当前状态: ${canListen ? "✅ 可监听预测消息" : "❌ 无法监听预测消息"}`
|
|
177
284
|
);
|
|
178
285
|
if (reason) {
|
|
179
|
-
|
|
286
|
+
logger.line("", `原因: ${reason}`);
|
|
180
287
|
}
|
|
181
288
|
}
|
|
182
289
|
if (isAdmin && !this.targetChatIds.has(chatId)) {
|
|
183
290
|
this.targetChatIds.add(chatId);
|
|
184
291
|
await this.saveChatIds();
|
|
185
|
-
|
|
292
|
+
logger.line("📡", "已自动开始监听此频道");
|
|
186
293
|
}
|
|
187
294
|
if ((isRemoved || !isAdmin) && this.targetChatIds.has(chatId)) {
|
|
188
295
|
this.targetChatIds.delete(chatId);
|
|
189
296
|
await this.saveChatIds();
|
|
190
297
|
if (isRemoved) {
|
|
191
|
-
|
|
298
|
+
logger.line("⏹️", "已停止监听此频道(Bot 已被移除)");
|
|
192
299
|
} else {
|
|
193
|
-
|
|
300
|
+
logger.line("⏹️", "已停止监听此频道(Bot 不再是管理员)");
|
|
194
301
|
}
|
|
195
302
|
}
|
|
196
303
|
}
|
|
@@ -205,7 +312,7 @@ class TelegramListener {
|
|
|
205
312
|
const chatIds = Array.from(this.targetChatIds);
|
|
206
313
|
await this.onChatIdsChangedCallback(chatIds);
|
|
207
314
|
} catch (error) {
|
|
208
|
-
|
|
315
|
+
logger.error("保存频道 ID 列表时出错:", error);
|
|
209
316
|
}
|
|
210
317
|
}
|
|
211
318
|
/**
|
|
@@ -234,9 +341,9 @@ class TelegramListener {
|
|
|
234
341
|
}
|
|
235
342
|
if (isChannel && !this.targetChatIds.has(chatId)) {
|
|
236
343
|
this.targetChatIds.add(chatId);
|
|
237
|
-
|
|
344
|
+
logger.info(`➕ 自动添加频道到监听列表: ${chatTitle} (ID: ${chatId})`);
|
|
238
345
|
await this.saveChatIds();
|
|
239
|
-
|
|
346
|
+
logger.line("💾", "已保存到配置文件");
|
|
240
347
|
}
|
|
241
348
|
if (!isChannel) {
|
|
242
349
|
return;
|
|
@@ -250,13 +357,13 @@ class TelegramListener {
|
|
|
250
357
|
const emojiIndex = messageText.indexOf("🤖");
|
|
251
358
|
const jsonStart = emojiIndex + 1;
|
|
252
359
|
const jsonEnd = Math.min(jsonStart + 200, messageText.length);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
360
|
+
logger.warning(`解析预测消息失败:`);
|
|
361
|
+
logger.line("", `频道 ID: ${chatId}`);
|
|
362
|
+
logger.line(
|
|
256
363
|
"",
|
|
257
364
|
`消息片段: ${messageText.substring(emojiIndex, jsonEnd)}${messageText.length > jsonEnd ? "..." : ""}`
|
|
258
365
|
);
|
|
259
|
-
|
|
366
|
+
logger.line("", "提示: 请检查消息格式是否正确");
|
|
260
367
|
}
|
|
261
368
|
return;
|
|
262
369
|
}
|
|
@@ -271,7 +378,7 @@ class TelegramListener {
|
|
|
271
378
|
try {
|
|
272
379
|
await this.onSignalCallback(parsedSignal);
|
|
273
380
|
} catch (error) {
|
|
274
|
-
|
|
381
|
+
logger.error("处理信号时出错:", error);
|
|
275
382
|
}
|
|
276
383
|
}
|
|
277
384
|
}
|
|
@@ -299,15 +406,15 @@ class TelegramListener {
|
|
|
299
406
|
*/
|
|
300
407
|
async start(onReady) {
|
|
301
408
|
try {
|
|
302
|
-
|
|
409
|
+
logger.info("正在启动 Bot...");
|
|
303
410
|
const botInfo = await this.bot.api.getMe();
|
|
304
411
|
const botId = botInfo.id;
|
|
305
|
-
|
|
412
|
+
logger.info(`Bot 信息: @${botInfo.username} (ID: ${botId})`);
|
|
306
413
|
await this.checkAndDisplayChannels(botId);
|
|
307
414
|
if (onReady) {
|
|
308
415
|
await onReady();
|
|
309
416
|
}
|
|
310
|
-
|
|
417
|
+
logger.info("开始监听预测消息...");
|
|
311
418
|
await this.bot.start({
|
|
312
419
|
drop_pending_updates: true,
|
|
313
420
|
allowed_updates: [
|
|
@@ -317,7 +424,7 @@ class TelegramListener {
|
|
|
317
424
|
]
|
|
318
425
|
});
|
|
319
426
|
} catch (error) {
|
|
320
|
-
|
|
427
|
+
logger.error("启动 Bot 失败:", error);
|
|
321
428
|
throw error;
|
|
322
429
|
}
|
|
323
430
|
}
|
|
@@ -328,14 +435,14 @@ class TelegramListener {
|
|
|
328
435
|
async checkAndDisplayChannels(botId) {
|
|
329
436
|
const chatIds = Array.from(this.targetChatIds);
|
|
330
437
|
if (chatIds.length === 0) {
|
|
331
|
-
|
|
332
|
-
|
|
438
|
+
logger.info(`📋 当前没有保存的频道配置`);
|
|
439
|
+
logger.line(
|
|
333
440
|
"",
|
|
334
441
|
"提示: 当 Bot 被添加为频道管理员时,会自动添加到监听列表"
|
|
335
442
|
);
|
|
336
443
|
return;
|
|
337
444
|
}
|
|
338
|
-
|
|
445
|
+
logger.info(`📋 检查 Bot 所在的频道:`);
|
|
339
446
|
const validChannels = [];
|
|
340
447
|
const invalidChannels = [];
|
|
341
448
|
for (const chatId of chatIds) {
|
|
@@ -367,43 +474,43 @@ class TelegramListener {
|
|
|
367
474
|
const adminChannels = validChannels.filter((c) => c.isAdmin);
|
|
368
475
|
const nonAdminChannels = validChannels.filter((c) => !c.isAdmin);
|
|
369
476
|
if (adminChannels.length > 0) {
|
|
370
|
-
|
|
477
|
+
logger.info(`📡 可监听频道 (${adminChannels.length} 个):`);
|
|
371
478
|
adminChannels.forEach((channel, index) => {
|
|
372
|
-
|
|
479
|
+
logger.item(
|
|
373
480
|
`${index + 1}. ${channel.title} (ID: ${channel.id}) 👑 管理员`,
|
|
374
481
|
1
|
|
375
482
|
);
|
|
376
483
|
});
|
|
377
484
|
}
|
|
378
485
|
if (nonAdminChannels.length > 0) {
|
|
379
|
-
|
|
486
|
+
logger.warning(`无法监听频道 (${nonAdminChannels.length} 个):`);
|
|
380
487
|
nonAdminChannels.forEach((channel, index) => {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
488
|
+
logger.item(`${index + 1}. ${channel.title}`, 1);
|
|
489
|
+
logger.item(`ID: ${channel.id}`, 2);
|
|
490
|
+
logger.item(`状态: 🚫 非管理员(无法接收预测消息)`, 2);
|
|
491
|
+
logger.item(`原因: 需要管理员权限才能监听`, 2);
|
|
385
492
|
});
|
|
386
493
|
nonAdminChannels.forEach((channel) => {
|
|
387
494
|
this.targetChatIds.delete(channel.id);
|
|
388
495
|
});
|
|
389
496
|
if (nonAdminChannels.length > 0) {
|
|
390
497
|
await this.saveChatIds();
|
|
391
|
-
|
|
498
|
+
logger.line("💾", "已清理无法监听的频道并保存配置");
|
|
392
499
|
}
|
|
393
500
|
}
|
|
394
501
|
}
|
|
395
502
|
if (invalidChannels.length > 0) {
|
|
396
|
-
|
|
503
|
+
logger.error(`无效频道 (${invalidChannels.length} 个):`);
|
|
397
504
|
invalidChannels.forEach((channel, index) => {
|
|
398
|
-
|
|
399
|
-
|
|
505
|
+
logger.item(`${index + 1}. ID: ${channel.id}`, 1);
|
|
506
|
+
logger.item(`原因: ${channel.reason}`, 2);
|
|
400
507
|
});
|
|
401
508
|
invalidChannels.forEach((channel) => {
|
|
402
509
|
this.targetChatIds.delete(channel.id);
|
|
403
510
|
});
|
|
404
511
|
if (invalidChannels.length > 0) {
|
|
405
512
|
await this.saveChatIds();
|
|
406
|
-
|
|
513
|
+
logger.line("💾", "已清理无效频道并保存配置");
|
|
407
514
|
}
|
|
408
515
|
}
|
|
409
516
|
}
|
|
@@ -439,13 +546,13 @@ class TelegramService {
|
|
|
439
546
|
this.bot = new grammy.Bot(botToken);
|
|
440
547
|
this.listener = new TelegramListener(this.bot, targetChatIds);
|
|
441
548
|
if (!adminChatId) {
|
|
442
|
-
|
|
549
|
+
logger.warning("未配置管理员 Chat ID,通知功能已禁用");
|
|
443
550
|
this.notifyEnabled = false;
|
|
444
551
|
return;
|
|
445
552
|
}
|
|
446
553
|
this.adminChatId = adminChatId;
|
|
447
554
|
this.notifyEnabled = true;
|
|
448
|
-
|
|
555
|
+
logger.info("管理员通知已启用");
|
|
449
556
|
}
|
|
450
557
|
/**
|
|
451
558
|
* 设置频道 ID 列表变更回调
|
|
@@ -511,7 +618,7 @@ ${details}` : message;
|
|
|
511
618
|
});
|
|
512
619
|
} catch (error) {
|
|
513
620
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
514
|
-
|
|
621
|
+
logger.error(`发送通知失败: ${errorMsg}`);
|
|
515
622
|
}
|
|
516
623
|
}
|
|
517
624
|
}
|
|
@@ -528,11 +635,11 @@ class ClobClientWrapper {
|
|
|
528
635
|
return;
|
|
529
636
|
}
|
|
530
637
|
const wallet$1 = new wallet.Wallet(config.privateKey);
|
|
531
|
-
|
|
638
|
+
logger.info(`钱包地址: ${wallet$1.address}`);
|
|
532
639
|
const tempClient = new clobClient.ClobClient(CLOB_HOST, clobClient.Chain.POLYGON, wallet$1);
|
|
533
640
|
this.patchClient(tempClient);
|
|
534
641
|
const creds = await tempClient.deriveApiKey();
|
|
535
|
-
|
|
642
|
+
logger.success("API 凭证已获取");
|
|
536
643
|
let builderConfig;
|
|
537
644
|
if (config.builderCreds) {
|
|
538
645
|
builderConfig = new builderSigningSdk.BuilderConfig({
|
|
@@ -556,7 +663,7 @@ class ClobClientWrapper {
|
|
|
556
663
|
);
|
|
557
664
|
this.patchClient(this.client);
|
|
558
665
|
this.initialized = true;
|
|
559
|
-
|
|
666
|
+
logger.success("ClobClient 初始化完成");
|
|
560
667
|
}
|
|
561
668
|
/**
|
|
562
669
|
* 魔法操作:clob-client 请求出错不抛异常,只返回 { error: ... }
|
|
@@ -632,13 +739,13 @@ class ClobClientWrapper {
|
|
|
632
739
|
clobClient.OrderType.GTC
|
|
633
740
|
);
|
|
634
741
|
if (response.success) {
|
|
635
|
-
|
|
742
|
+
logger.success(`买单已提交: ${response.orderID}`);
|
|
636
743
|
return {
|
|
637
744
|
success: true,
|
|
638
745
|
orderId: response.orderID
|
|
639
746
|
};
|
|
640
747
|
} else {
|
|
641
|
-
|
|
748
|
+
logger.error(`买单提交失败: ${response.errorMsg}`);
|
|
642
749
|
return {
|
|
643
750
|
success: false,
|
|
644
751
|
orderId: "",
|
|
@@ -647,7 +754,7 @@ class ClobClientWrapper {
|
|
|
647
754
|
}
|
|
648
755
|
} catch (error) {
|
|
649
756
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
650
|
-
|
|
757
|
+
logger.error(`买单提交失败: ${errorMsg}`);
|
|
651
758
|
return {
|
|
652
759
|
success: false,
|
|
653
760
|
orderId: "",
|
|
@@ -709,11 +816,11 @@ class ClobClientWrapper {
|
|
|
709
816
|
const client = this.ensureInitialized();
|
|
710
817
|
try {
|
|
711
818
|
await client.cancelOrder({ orderID: orderId });
|
|
712
|
-
|
|
819
|
+
logger.info(`订单已取消: ${orderId}`);
|
|
713
820
|
return true;
|
|
714
821
|
} catch (error) {
|
|
715
822
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
716
|
-
|
|
823
|
+
logger.error(`取消订单失败: ${errorMsg}`);
|
|
717
824
|
return false;
|
|
718
825
|
}
|
|
719
826
|
}
|
|
@@ -759,7 +866,7 @@ class RelayClientWrapper {
|
|
|
759
866
|
builderRelayerClient.RelayerTxType.SAFE
|
|
760
867
|
);
|
|
761
868
|
this.initialized = true;
|
|
762
|
-
|
|
869
|
+
logger.success("RelayClient 初始化完成");
|
|
763
870
|
}
|
|
764
871
|
/**
|
|
765
872
|
* 检查是否已初始化
|
|
@@ -838,7 +945,7 @@ class AssetFilter {
|
|
|
838
945
|
this.traded.set(endTime, /* @__PURE__ */ new Set());
|
|
839
946
|
}
|
|
840
947
|
this.traded.get(endTime).add(assetId);
|
|
841
|
-
|
|
948
|
+
logger.info(`已标记 assetId: ${assetId.slice(0, 10)}...`);
|
|
842
949
|
}
|
|
843
950
|
/**
|
|
844
951
|
* 清理过期记录
|
|
@@ -852,7 +959,7 @@ class AssetFilter {
|
|
|
852
959
|
}
|
|
853
960
|
}
|
|
854
961
|
if (cleanedCount > 0) {
|
|
855
|
-
|
|
962
|
+
logger.info(`已清理 ${cleanedCount} 个过期周期的交易记录`);
|
|
856
963
|
}
|
|
857
964
|
}
|
|
858
965
|
}
|
|
@@ -872,12 +979,12 @@ class BalanceCache {
|
|
|
872
979
|
}
|
|
873
980
|
if (this.cache.has(endTime)) {
|
|
874
981
|
const cached = this.cache.get(endTime);
|
|
875
|
-
|
|
982
|
+
logger.info(`使用缓存余额: ${cached} USDC`);
|
|
876
983
|
return cached;
|
|
877
984
|
}
|
|
878
985
|
const balance = await fetchFn();
|
|
879
986
|
this.cache.set(endTime, balance);
|
|
880
|
-
|
|
987
|
+
logger.info(`获取当前余额: ${balance} USDC`);
|
|
881
988
|
return balance;
|
|
882
989
|
}
|
|
883
990
|
/**
|
|
@@ -886,7 +993,7 @@ class BalanceCache {
|
|
|
886
993
|
invalidate() {
|
|
887
994
|
if (this.cache.size > 0) {
|
|
888
995
|
this.cache.clear();
|
|
889
|
-
|
|
996
|
+
logger.info("余额缓存已清除");
|
|
890
997
|
}
|
|
891
998
|
}
|
|
892
999
|
/**
|
|
@@ -901,7 +1008,7 @@ class BalanceCache {
|
|
|
901
1008
|
}
|
|
902
1009
|
}
|
|
903
1010
|
if (cleanedCount > 0) {
|
|
904
|
-
|
|
1011
|
+
logger.info(`已清理 ${cleanedCount} 个过期周期的余额缓存`);
|
|
905
1012
|
}
|
|
906
1013
|
}
|
|
907
1014
|
}
|
|
@@ -918,13 +1025,13 @@ class OrderWatcher {
|
|
|
918
1025
|
async watch(orderId, endTime, getOrderFn) {
|
|
919
1026
|
const timeoutAt = endTime + TIMEOUT_BUFFER;
|
|
920
1027
|
const shortId = orderId.substring(0, 10) + "...";
|
|
921
|
-
|
|
1028
|
+
logger.info(
|
|
922
1029
|
`开始监控订单 ${shortId},超时: ${new Date(timeoutAt).toLocaleString()}`
|
|
923
1030
|
);
|
|
924
1031
|
while (true) {
|
|
925
1032
|
const now = Date.now();
|
|
926
1033
|
if (now >= timeoutAt) {
|
|
927
|
-
|
|
1034
|
+
logger.warning(`订单超时: ${shortId}`);
|
|
928
1035
|
return { status: "TIMEOUT" };
|
|
929
1036
|
}
|
|
930
1037
|
try {
|
|
@@ -932,7 +1039,7 @@ class OrderWatcher {
|
|
|
932
1039
|
const status = order.status.toUpperCase();
|
|
933
1040
|
if (TERMINAL_STATUSES.includes(status)) {
|
|
934
1041
|
const filledSize = parseFloat(order.size_matched || "0");
|
|
935
|
-
|
|
1042
|
+
logger.info(`订单 ${shortId} 终态: ${status},成交: ${filledSize}`);
|
|
936
1043
|
return {
|
|
937
1044
|
status,
|
|
938
1045
|
filledSize,
|
|
@@ -941,7 +1048,7 @@ class OrderWatcher {
|
|
|
941
1048
|
}
|
|
942
1049
|
} catch (error) {
|
|
943
1050
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
944
|
-
|
|
1051
|
+
logger.warning(`查询订单失败: ${errorMsg},继续重试...`);
|
|
945
1052
|
}
|
|
946
1053
|
await this.delay(POLL_INTERVAL);
|
|
947
1054
|
}
|
|
@@ -986,7 +1093,7 @@ class Redeemer {
|
|
|
986
1093
|
this.funderAddress = funderAddress;
|
|
987
1094
|
this.onRedeemSuccess = onRedeemSuccess || null;
|
|
988
1095
|
this.state = "running";
|
|
989
|
-
|
|
1096
|
+
logger.success("自动 redeem 已启动");
|
|
990
1097
|
this.startInterval();
|
|
991
1098
|
}
|
|
992
1099
|
/**
|
|
@@ -1034,7 +1141,7 @@ class Redeemer {
|
|
|
1034
1141
|
this.delayTimeoutId = void 0;
|
|
1035
1142
|
}
|
|
1036
1143
|
this.state = "stopped";
|
|
1037
|
-
|
|
1144
|
+
logger.info("自动 redeem 已停止");
|
|
1038
1145
|
}
|
|
1039
1146
|
/**
|
|
1040
1147
|
* 获取可 Redeem 的仓位
|
|
@@ -1085,7 +1192,7 @@ class Redeemer {
|
|
|
1085
1192
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1086
1193
|
if (errorMsg.includes("429") || errorMsg.includes("Too Many Requests")) {
|
|
1087
1194
|
const resetSeconds = this.parseResetSeconds(errorMsg);
|
|
1088
|
-
|
|
1195
|
+
logger.warning(`获取仓位限流,${resetSeconds} 秒后重试`);
|
|
1089
1196
|
telegramService.warning(
|
|
1090
1197
|
"API 限流",
|
|
1091
1198
|
`获取仓位限流,${resetSeconds} 秒后重试`
|
|
@@ -1093,7 +1200,7 @@ class Redeemer {
|
|
|
1093
1200
|
this.pauseAndRestart((resetSeconds + 60) * 1e3);
|
|
1094
1201
|
return;
|
|
1095
1202
|
}
|
|
1096
|
-
|
|
1203
|
+
logger.error(`获取仓位失败: ${errorMsg}`);
|
|
1097
1204
|
return;
|
|
1098
1205
|
}
|
|
1099
1206
|
const pendingRedeemablePositions = positions.filter((position) => {
|
|
@@ -1121,7 +1228,7 @@ class Redeemer {
|
|
|
1121
1228
|
if (record.success) {
|
|
1122
1229
|
record.matchedCount++;
|
|
1123
1230
|
if (record.matchedCount >= MAX_MATCH_TIMES) {
|
|
1124
|
-
|
|
1231
|
+
logger.error(
|
|
1125
1232
|
`Redeem 失败: 执行成功但仍存在 ${record.matchedCount} 次 (conditionId: ${position.conditionId.slice(0, 10)}...)`
|
|
1126
1233
|
);
|
|
1127
1234
|
telegramService.error(
|
|
@@ -1135,18 +1242,18 @@ conditionId: ${position.conditionId}
|
|
|
1135
1242
|
try {
|
|
1136
1243
|
const txHash = await this.redeemFn?.(position.conditionId);
|
|
1137
1244
|
record.success = true;
|
|
1138
|
-
|
|
1245
|
+
logger.info(`Redeem 已执行: ${txHash}`);
|
|
1139
1246
|
} catch (error) {
|
|
1140
1247
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1141
1248
|
if (errorMsg.includes("429") || errorMsg.includes("Too Many Requests")) {
|
|
1142
1249
|
const resetSeconds = this.parseResetSeconds(errorMsg);
|
|
1143
|
-
|
|
1250
|
+
logger.warning(`Redeem 限流,${resetSeconds} 秒后重试`);
|
|
1144
1251
|
telegramService.warning("Redeem 限流", `${resetSeconds} 秒后重试`);
|
|
1145
1252
|
this.pauseAndRestart((resetSeconds + 60) * 1e3);
|
|
1146
1253
|
return;
|
|
1147
1254
|
}
|
|
1148
1255
|
record.failedCount++;
|
|
1149
|
-
|
|
1256
|
+
logger.error(
|
|
1150
1257
|
`Redeem 异常 (第 ${record.failedCount} 次): ${errorMsg}`
|
|
1151
1258
|
);
|
|
1152
1259
|
if (record.failedCount >= MAX_MATCH_TIMES) {
|
|
@@ -1177,7 +1284,7 @@ conditionId: ${position.conditionId}
|
|
|
1177
1284
|
telegramService.success(successTitle, successLines.join("\n"));
|
|
1178
1285
|
this.onRedeemSuccess?.();
|
|
1179
1286
|
}
|
|
1180
|
-
|
|
1287
|
+
logger.info(`redeem 状态: ${this.records.size} 个待确认`);
|
|
1181
1288
|
}
|
|
1182
1289
|
/**
|
|
1183
1290
|
* 统一格式化仓位信息
|
|
@@ -1192,15 +1299,12 @@ conditionId: ${position.conditionId}
|
|
|
1192
1299
|
* 统一输出 Redeem 仓位列表
|
|
1193
1300
|
*/
|
|
1194
1301
|
logPositions(title, items) {
|
|
1195
|
-
|
|
1302
|
+
logger.info(title);
|
|
1196
1303
|
const lines = [];
|
|
1197
1304
|
for (const item of items) {
|
|
1198
|
-
const line = this.formatPositionLine(
|
|
1199
|
-
item.position,
|
|
1200
|
-
item.matchedCount
|
|
1201
|
-
);
|
|
1305
|
+
const line = this.formatPositionLine(item.position, item.matchedCount);
|
|
1202
1306
|
lines.push(line);
|
|
1203
|
-
|
|
1307
|
+
logger.line("", `- ${line}`);
|
|
1204
1308
|
}
|
|
1205
1309
|
return lines;
|
|
1206
1310
|
}
|
|
@@ -1240,7 +1344,7 @@ class Trader {
|
|
|
1240
1344
|
}
|
|
1241
1345
|
this.config = config;
|
|
1242
1346
|
if (!config.polymarket?.privateKey) {
|
|
1243
|
-
|
|
1347
|
+
logger.warning("未配置 PolyMarket 私钥,交易功能已禁用");
|
|
1244
1348
|
return;
|
|
1245
1349
|
}
|
|
1246
1350
|
await this.client.init({
|
|
@@ -1254,10 +1358,10 @@ class Trader {
|
|
|
1254
1358
|
builderCreds: config.polymarket.builderCreds
|
|
1255
1359
|
});
|
|
1256
1360
|
} else {
|
|
1257
|
-
|
|
1361
|
+
logger.warning("未配置 builderCreds,自动 Redeem 功能已禁用");
|
|
1258
1362
|
}
|
|
1259
1363
|
this.initialized = true;
|
|
1260
|
-
|
|
1364
|
+
logger.success("交易模块初始化完成");
|
|
1261
1365
|
}
|
|
1262
1366
|
/**
|
|
1263
1367
|
* 启动 Redeemer(在 Telegram listener 就绪后调用)
|
|
@@ -1283,19 +1387,19 @@ class Trader {
|
|
|
1283
1387
|
*/
|
|
1284
1388
|
async executeSignal(signal) {
|
|
1285
1389
|
if (!this.initialized || !this.config) {
|
|
1286
|
-
|
|
1390
|
+
logger.warning("交易模块未初始化,跳过信号");
|
|
1287
1391
|
return;
|
|
1288
1392
|
}
|
|
1289
1393
|
const { metadata } = signal;
|
|
1290
1394
|
const { assetId, endTime, outcome } = metadata;
|
|
1291
|
-
|
|
1395
|
+
logger.section(`处理信号: ${outcome} (${metadata.eventId})`);
|
|
1292
1396
|
if (!this.assetFilter.canTrade(assetId, endTime)) {
|
|
1293
|
-
|
|
1397
|
+
logger.warning(`assetId 已交易,跳过: ${assetId.slice(0, 10)}...`);
|
|
1294
1398
|
return;
|
|
1295
1399
|
}
|
|
1296
1400
|
const tradingConfig = this.config.trading;
|
|
1297
1401
|
const amountMode = tradingConfig?.amountMode || "fixed";
|
|
1298
|
-
const amountValue = tradingConfig?.
|
|
1402
|
+
const amountValue = amountMode === "percentage" ? tradingConfig?.amountPercentageValue ?? 0.1 : tradingConfig?.amountFixedValue ?? 5;
|
|
1299
1403
|
const buyPrice = tradingConfig?.buyPrice || 0.98;
|
|
1300
1404
|
const sellPrice = tradingConfig?.sellPrice || 1;
|
|
1301
1405
|
let balance;
|
|
@@ -1306,12 +1410,12 @@ class Trader {
|
|
|
1306
1410
|
);
|
|
1307
1411
|
} catch (error) {
|
|
1308
1412
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1309
|
-
|
|
1413
|
+
logger.error(`获取余额失败: ${errorMsg}`);
|
|
1310
1414
|
return;
|
|
1311
1415
|
}
|
|
1312
1416
|
if (amountMode === "fixed" && balance < amountValue) {
|
|
1313
1417
|
const msg = `余额不足: ${balance} USDC < ${amountValue} USDC`;
|
|
1314
|
-
|
|
1418
|
+
logger.warning(msg);
|
|
1315
1419
|
telegramService.warning(
|
|
1316
1420
|
"余额不足",
|
|
1317
1421
|
`当前余额: ${balance} USDC
|
|
@@ -1320,18 +1424,18 @@ class Trader {
|
|
|
1320
1424
|
return;
|
|
1321
1425
|
}
|
|
1322
1426
|
if (amountMode === "percentage" && balance <= 0) {
|
|
1323
|
-
|
|
1427
|
+
logger.warning(`余额为零: ${balance} USDC`);
|
|
1324
1428
|
telegramService.warning("余额为零", `当前余额: ${balance} USDC`);
|
|
1325
1429
|
return;
|
|
1326
1430
|
}
|
|
1327
1431
|
const orderAmount = amountMode === "fixed" ? amountValue : balance * amountValue;
|
|
1328
1432
|
const size = orderAmount / buyPrice;
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1433
|
+
logger.info(`下单金额: ${orderAmount.toFixed(2)} USDC`);
|
|
1434
|
+
logger.info(`下单价格: ${buyPrice}`);
|
|
1435
|
+
logger.info(`下单数量: ${size.toFixed(4)}`);
|
|
1332
1436
|
const buyResult = await this.client.createBuyOrder(assetId, buyPrice, size);
|
|
1333
1437
|
if (!buyResult.success) {
|
|
1334
|
-
|
|
1438
|
+
logger.error(`买单失败: ${buyResult.errorMsg}`);
|
|
1335
1439
|
telegramService.error(
|
|
1336
1440
|
"买单失败",
|
|
1337
1441
|
`事件: ${metadata.eventId}
|
|
@@ -1341,7 +1445,7 @@ class Trader {
|
|
|
1341
1445
|
}
|
|
1342
1446
|
this.assetFilter.markTraded(assetId, endTime);
|
|
1343
1447
|
if (sellPrice === 1) {
|
|
1344
|
-
|
|
1448
|
+
logger.info("sellPrice = 1,成交后将自动 redeem");
|
|
1345
1449
|
return;
|
|
1346
1450
|
}
|
|
1347
1451
|
this.watchAndHandle(
|
|
@@ -1371,7 +1475,7 @@ class Trader {
|
|
|
1371
1475
|
);
|
|
1372
1476
|
} catch (error) {
|
|
1373
1477
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1374
|
-
|
|
1478
|
+
logger.error(`订单监控异常: ${errorMsg}`);
|
|
1375
1479
|
telegramService.error(
|
|
1376
1480
|
"订单监控异常",
|
|
1377
1481
|
`事件: ${eventId}
|
|
@@ -1388,24 +1492,22 @@ class Trader {
|
|
|
1388
1492
|
case "MATCHED":
|
|
1389
1493
|
case "CONFIRMED":
|
|
1390
1494
|
const filledSize = result.filledSize || 0;
|
|
1391
|
-
|
|
1495
|
+
logger.success(`买单成交: ${filledSize.toFixed(4)} 份`);
|
|
1392
1496
|
if (sellPrice < 1) {
|
|
1393
1497
|
this.waitAndSell(assetId, filledSize, sellPrice, endTime);
|
|
1394
1498
|
} else {
|
|
1395
|
-
|
|
1499
|
+
logger.info("sellPrice = 1,等待 Redeem");
|
|
1396
1500
|
}
|
|
1397
1501
|
break;
|
|
1398
1502
|
case "CANCELLED":
|
|
1399
|
-
|
|
1503
|
+
logger.info("订单已取消(市场可能已结束)");
|
|
1400
1504
|
break;
|
|
1401
1505
|
case "FAILED":
|
|
1402
|
-
|
|
1506
|
+
logger.error("订单执行失败");
|
|
1403
1507
|
telegramService.error("订单执行失败", `订单 ID: ${orderId}`);
|
|
1404
1508
|
break;
|
|
1405
1509
|
case "TIMEOUT":
|
|
1406
|
-
|
|
1407
|
-
telegramService.warning("订单超时", `订单 ID: ${orderId}
|
|
1408
|
-
已尝试取消`);
|
|
1510
|
+
logger.warning("订单超时,尝试取消");
|
|
1409
1511
|
await this.client.cancelOrder(orderId);
|
|
1410
1512
|
break;
|
|
1411
1513
|
}
|
|
@@ -1416,18 +1518,18 @@ class Trader {
|
|
|
1416
1518
|
async waitAndSell(assetId, expectedSize, sellPrice, endTime) {
|
|
1417
1519
|
const shortId = assetId.substring(0, 10) + "...";
|
|
1418
1520
|
const threshold = expectedSize * POSITION_MATCH_THRESHOLD;
|
|
1419
|
-
|
|
1521
|
+
logger.info(`等待仓位同步: ${shortId},预期 >= ${threshold.toFixed(4)}`);
|
|
1420
1522
|
while (true) {
|
|
1421
1523
|
const now = Date.now();
|
|
1422
1524
|
if (now >= endTime) {
|
|
1423
|
-
|
|
1525
|
+
logger.warning(`仓位同步超时: ${shortId},等待 Redeem`);
|
|
1424
1526
|
this.notifyRedeemFallback(assetId, expectedSize);
|
|
1425
1527
|
return;
|
|
1426
1528
|
}
|
|
1427
1529
|
try {
|
|
1428
1530
|
const positionSize = await this.client.getPositionSize(assetId);
|
|
1429
1531
|
if (positionSize >= threshold) {
|
|
1430
|
-
|
|
1532
|
+
logger.info(
|
|
1431
1533
|
`仓位已同步: ${shortId},数量: ${positionSize.toFixed(4)}`
|
|
1432
1534
|
);
|
|
1433
1535
|
await this.executeSellOrder(assetId, sellPrice, positionSize);
|
|
@@ -1435,7 +1537,7 @@ class Trader {
|
|
|
1435
1537
|
}
|
|
1436
1538
|
} catch (error) {
|
|
1437
1539
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1438
|
-
|
|
1540
|
+
logger.warning(`获取仓位失败: ${shortId},${errorMsg}`);
|
|
1439
1541
|
}
|
|
1440
1542
|
await this.delay(POSITION_POLL_INTERVAL);
|
|
1441
1543
|
}
|
|
@@ -1446,7 +1548,7 @@ class Trader {
|
|
|
1446
1548
|
notifyRedeemFallback(assetId, expectedSize) {
|
|
1447
1549
|
if (!this.relayClient.isInitialized()) {
|
|
1448
1550
|
const msg = "仓位同步超时且 Redeem 不可用";
|
|
1449
|
-
|
|
1551
|
+
logger.warning(msg);
|
|
1450
1552
|
telegramService.warning(
|
|
1451
1553
|
msg,
|
|
1452
1554
|
`Asset: ${assetId.substring(0, 20)}...
|
|
@@ -1465,12 +1567,12 @@ class Trader {
|
|
|
1465
1567
|
* 执行卖单
|
|
1466
1568
|
*/
|
|
1467
1569
|
async executeSellOrder(assetId, price, size) {
|
|
1468
|
-
|
|
1570
|
+
logger.info(`创建卖单: ${size.toFixed(4)} 份 @ ${price}`);
|
|
1469
1571
|
const sellResult = await this.client.createSellOrder(assetId, price, size);
|
|
1470
1572
|
if (sellResult.success) {
|
|
1471
|
-
|
|
1573
|
+
logger.success(`卖单已提交: ${sellResult.orderId}`);
|
|
1472
1574
|
} else {
|
|
1473
|
-
|
|
1575
|
+
logger.error(`卖单提交失败: ${sellResult.errorMsg}`);
|
|
1474
1576
|
telegramService.error("卖单提交失败", `错误: ${sellResult.errorMsg}`);
|
|
1475
1577
|
}
|
|
1476
1578
|
}
|
|
@@ -1488,13 +1590,13 @@ class Trader {
|
|
|
1488
1590
|
*/
|
|
1489
1591
|
async shutdown() {
|
|
1490
1592
|
this.redeemer.stop();
|
|
1491
|
-
|
|
1593
|
+
logger.info("交易模块已停止");
|
|
1492
1594
|
}
|
|
1493
1595
|
}
|
|
1494
1596
|
const trader = new Trader();
|
|
1495
1597
|
function enterDaemonMode() {
|
|
1496
|
-
if (!fs__namespace.existsSync(
|
|
1497
|
-
fs__namespace.mkdirSync(
|
|
1598
|
+
if (!fs__namespace.existsSync(processLock.PID_DIR)) {
|
|
1599
|
+
fs__namespace.mkdirSync(processLock.PID_DIR, { recursive: true });
|
|
1498
1600
|
}
|
|
1499
1601
|
const child = child_process.spawn(process.execPath, [__filename], {
|
|
1500
1602
|
detached: true,
|
|
@@ -1506,46 +1608,77 @@ function enterDaemonMode() {
|
|
|
1506
1608
|
}
|
|
1507
1609
|
});
|
|
1508
1610
|
if (child.pid) {
|
|
1509
|
-
fs__namespace.writeFileSync(
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1611
|
+
fs__namespace.writeFileSync(processLock.PID_FILE, child.pid.toString());
|
|
1612
|
+
logger.blank();
|
|
1613
|
+
logger.success(`已进入后台模式 (PID: ${child.pid})`);
|
|
1614
|
+
logger.line("", `日志目录: ${processLock.LOGS_DIR}`);
|
|
1615
|
+
logger.line("", `使用 'polycopy stop' 停止`);
|
|
1616
|
+
logger.line("", `使用 'polycopy log' 查看日志`);
|
|
1515
1617
|
child.unref();
|
|
1516
1618
|
process.exit(0);
|
|
1517
1619
|
} else {
|
|
1518
|
-
|
|
1620
|
+
logger.error("进入后台模式失败");
|
|
1519
1621
|
process.exit(1);
|
|
1520
1622
|
}
|
|
1521
1623
|
}
|
|
1522
1624
|
async function main() {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1625
|
+
logger.init();
|
|
1626
|
+
if (process.env.POLYCOPY_DAEMON_CHILD !== "1") {
|
|
1627
|
+
const runningPid = processLock.resolveRunningPid();
|
|
1628
|
+
if (runningPid) {
|
|
1629
|
+
console.log(`⚠️ 程序正在运行中 (PID: ${runningPid})`);
|
|
1630
|
+
console.log(` 请先停止程序后再启动`);
|
|
1631
|
+
process.exit(1);
|
|
1632
|
+
}
|
|
1633
|
+
processLock.savePid(process.pid);
|
|
1634
|
+
}
|
|
1635
|
+
const exitOnFatal = async () => {
|
|
1636
|
+
await logger.flush();
|
|
1637
|
+
if (process.env.POLYCOPY_DAEMON_CHILD !== "1") {
|
|
1638
|
+
processLock.removePid();
|
|
1639
|
+
}
|
|
1640
|
+
process.exit(1);
|
|
1641
|
+
};
|
|
1642
|
+
process.on("uncaughtException", async (error) => {
|
|
1643
|
+
const message = error instanceof Error ? error.stack || error.message : String(error);
|
|
1644
|
+
logger.error(`捕获未处理异常:
|
|
1645
|
+
${message}`);
|
|
1646
|
+
await exitOnFatal();
|
|
1647
|
+
});
|
|
1648
|
+
process.on("unhandledRejection", async (reason) => {
|
|
1649
|
+
const message = reason instanceof Error ? reason.stack || reason.message : String(reason);
|
|
1650
|
+
logger.error(`捕获未处理 Promise 拒绝:
|
|
1651
|
+
${message}`);
|
|
1652
|
+
await exitOnFatal();
|
|
1653
|
+
});
|
|
1654
|
+
process.on("beforeExit", async () => {
|
|
1655
|
+
await logger.flush();
|
|
1656
|
+
});
|
|
1657
|
+
logger.section("PolyMarket 跟单机器人");
|
|
1658
|
+
const config = await processLock.ensureConfig();
|
|
1526
1659
|
telegramService.init(
|
|
1527
1660
|
config.telegram.botToken,
|
|
1528
1661
|
config.telegram.adminChatId,
|
|
1529
1662
|
config.telegram.targetChatIds || []
|
|
1530
1663
|
);
|
|
1531
|
-
const autoTradeEnabled =
|
|
1664
|
+
const autoTradeEnabled = processLock.canAutoTrade(config);
|
|
1532
1665
|
if (!autoTradeEnabled) {
|
|
1533
|
-
|
|
1666
|
+
logger.warning("自动跟单功能未启用");
|
|
1534
1667
|
if (!config.polymarket?.privateKey) {
|
|
1535
|
-
|
|
1668
|
+
logger.line("", "- PolyMarket 配置不完整(缺少私钥)");
|
|
1536
1669
|
}
|
|
1537
|
-
if (!config.trading?.amountMode || !config.trading?.
|
|
1538
|
-
|
|
1670
|
+
if (!config.trading?.amountMode || !config.trading?.buyPrice || !config.trading?.sellPrice || (config.trading.amountMode === "fixed" ? !config.trading.amountFixedValue : !config.trading.amountPercentageValue)) {
|
|
1671
|
+
logger.line("", "- 交易配置不完整");
|
|
1539
1672
|
}
|
|
1540
|
-
|
|
1673
|
+
logger.line("", "当前不会跟单任何预测消息");
|
|
1541
1674
|
} else {
|
|
1542
1675
|
try {
|
|
1543
1676
|
await trader.init(config);
|
|
1544
1677
|
if (config.trading?.sellPrice === 1 && !config.polymarket?.builderCreds) {
|
|
1545
|
-
|
|
1678
|
+
logger.error(
|
|
1546
1679
|
"sellPrice = 1 但未配置 builderCreds,Redeem 功能无法使用,程序已退出"
|
|
1547
1680
|
);
|
|
1548
|
-
|
|
1681
|
+
logger.line(
|
|
1549
1682
|
"",
|
|
1550
1683
|
"请配置 Builder API 凭证,或将卖价设置为 < 1 使用限价卖出模式"
|
|
1551
1684
|
);
|
|
@@ -1553,29 +1686,29 @@ async function main() {
|
|
|
1553
1686
|
}
|
|
1554
1687
|
} catch (error) {
|
|
1555
1688
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1556
|
-
|
|
1557
|
-
|
|
1689
|
+
logger.error(`交易模块初始化失败: ${errorMsg}`);
|
|
1690
|
+
logger.line("", "当前不会跟单任何预测消息");
|
|
1558
1691
|
}
|
|
1559
1692
|
}
|
|
1560
1693
|
telegramService.onChatIdsChanged((chatIds) => {
|
|
1561
|
-
const telegram =
|
|
1694
|
+
const telegram = processLock.configLocal.getItem("telegram");
|
|
1562
1695
|
if (!telegram) {
|
|
1563
|
-
|
|
1696
|
+
logger.error("警告: 无法保存频道 ID,telegram 配置不存在");
|
|
1564
1697
|
return;
|
|
1565
1698
|
}
|
|
1566
1699
|
telegram.targetChatIds = chatIds.length > 0 ? chatIds : void 0;
|
|
1567
|
-
|
|
1568
|
-
|
|
1700
|
+
processLock.configLocal.setItem("telegram", telegram);
|
|
1701
|
+
logger.line("💾", `已保存频道 ID 列表: ${chatIds.length} 个频道`);
|
|
1569
1702
|
});
|
|
1570
1703
|
telegramService.onSignal(async (signal) => {
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1704
|
+
logger.info(`📨 收到预测信号:`);
|
|
1705
|
+
logger.line("", `事件: ${signal.metadata.eventId}`);
|
|
1706
|
+
logger.line(
|
|
1574
1707
|
"",
|
|
1575
1708
|
`方向: ${signal.metadata.outcome === "Up" ? "上涨 📈" : "下跌 📉"}`
|
|
1576
1709
|
);
|
|
1577
|
-
|
|
1578
|
-
|
|
1710
|
+
logger.line("", `Asset: ${signal.metadata.assetId}`);
|
|
1711
|
+
logger.line(
|
|
1579
1712
|
"",
|
|
1580
1713
|
`结束时间: ${new Date(signal.metadata.endTime).toLocaleString()}`
|
|
1581
1714
|
);
|
|
@@ -1584,7 +1717,7 @@ async function main() {
|
|
|
1584
1717
|
await trader.executeSignal(signal);
|
|
1585
1718
|
} catch (error) {
|
|
1586
1719
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1587
|
-
|
|
1720
|
+
logger.error(`交易执行失败: ${errorMsg}`);
|
|
1588
1721
|
}
|
|
1589
1722
|
}
|
|
1590
1723
|
});
|
|
@@ -1598,10 +1731,13 @@ async function main() {
|
|
|
1598
1731
|
isShuttingDown = true;
|
|
1599
1732
|
process.removeAllListeners("SIGINT");
|
|
1600
1733
|
process.removeAllListeners("SIGTERM");
|
|
1601
|
-
|
|
1602
|
-
|
|
1734
|
+
logger.blank();
|
|
1735
|
+
logger.info("正在退出...");
|
|
1603
1736
|
await Promise.all([trader.shutdown(), telegramService.stop()]);
|
|
1604
|
-
await
|
|
1737
|
+
await logger.flush();
|
|
1738
|
+
if (process.env.POLYCOPY_DAEMON_CHILD !== "1") {
|
|
1739
|
+
processLock.removePid();
|
|
1740
|
+
}
|
|
1605
1741
|
process.exit(0);
|
|
1606
1742
|
};
|
|
1607
1743
|
process.prependListener("SIGINT", () => {
|
|
@@ -1616,7 +1752,13 @@ async function main() {
|
|
|
1616
1752
|
}
|
|
1617
1753
|
});
|
|
1618
1754
|
}
|
|
1619
|
-
main().catch((error) => {
|
|
1620
|
-
|
|
1755
|
+
main().catch(async (error) => {
|
|
1756
|
+
const message = error instanceof Error ? error.stack || error.message : String(error);
|
|
1757
|
+
logger.error(`程序运行出错:
|
|
1758
|
+
${message}`);
|
|
1759
|
+
await logger.flush();
|
|
1760
|
+
if (process.env.POLYCOPY_DAEMON_CHILD !== "1") {
|
|
1761
|
+
processLock.removePid();
|
|
1762
|
+
}
|
|
1621
1763
|
process.exit(1);
|
|
1622
1764
|
});
|