Wire watcher into CONFIG + load env in npm scripts
- npm start / watch:telegram: add --env-file=env so TELEGRAM_* and HTTPS_PROXY from nv reach process.env - watcher: import CONFIG.candleWindowMinutes from src/config.js so the per-window latch stays in sync with the bot - watcher: replace prevSide/cooldown latch with lastSentWindowId keyed on floor(ts/WINDOW_MS); one alert per window, NO_TRADE rows no longer reset state
This commit is contained in:
+2
-2
@@ -4,8 +4,8 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/index.js",
|
"start": "node --env-file=env src/index.js",
|
||||||
"watch:telegram": "node scripts/telegram-watcher.js"
|
"watch:telegram": "node --env-file=env scripts/telegram-watcher.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ethers": "^6.11.1",
|
"ethers": "^6.11.1",
|
||||||
|
|||||||
+14
-21
@@ -10,12 +10,14 @@ try {
|
|||||||
applyGlobalProxyFromEnv();
|
applyGlobalProxyFromEnv();
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
|
const { CONFIG } = await import(path.join(ROOT, "src/config.js"));
|
||||||
|
|
||||||
const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
|
const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
|
||||||
const CHAT_ID = process.env.TELEGRAM_CHAT_ID;
|
const CHAT_ID = process.env.TELEGRAM_CHAT_ID;
|
||||||
const CSV_PATH = process.env.SIGNALS_CSV || path.join(ROOT, "logs/signals.csv");
|
const CSV_PATH = process.env.SIGNALS_CSV || path.join(ROOT, "logs/signals.csv");
|
||||||
const COOLDOWN_MS = Number(process.env.TELEGRAM_COOLDOWN_MS) || 30_000;
|
|
||||||
const DRY_RUN = process.env.DRY_RUN === "true";
|
const DRY_RUN = process.env.DRY_RUN === "true";
|
||||||
const POLY_BASE = process.env.POLYMARKET_BASE_URL || "https://polymarket.com/zh/event";
|
const POLY_BASE = process.env.POLYMARKET_BASE_URL || "https://polymarket.com/zh/event";
|
||||||
|
const WINDOW_MS = CONFIG.candleWindowMinutes * 60_000;
|
||||||
|
|
||||||
if (!DRY_RUN && (!BOT_TOKEN || !CHAT_ID)) {
|
if (!DRY_RUN && (!BOT_TOKEN || !CHAT_ID)) {
|
||||||
console.error("[watcher] Missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID");
|
console.error("[watcher] Missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID");
|
||||||
@@ -23,8 +25,7 @@ if (!DRY_RUN && (!BOT_TOKEN || !CHAT_ID)) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let prevSide = null;
|
let lastSentWindowId = null;
|
||||||
let lastSentAt = 0;
|
|
||||||
let lastSentTs = null;
|
let lastSentTs = null;
|
||||||
|
|
||||||
async function send(text) {
|
async function send(text) {
|
||||||
@@ -94,7 +95,10 @@ function readLast() {
|
|||||||
if (lines.length < 2) return null;
|
if (lines.length < 2) return null;
|
||||||
const hdr = lines[0].split(",");
|
const hdr = lines[0].split(",");
|
||||||
const row = lines[lines.length - 1].split(",");
|
const row = lines[lines.length - 1].split(",");
|
||||||
return { hdr, row, rec: row[hdr.indexOf("recommendation")] || "" };
|
const ts = row[hdr.indexOf("timestamp")] || null;
|
||||||
|
const tsMs = ts ? Date.parse(ts) : NaN;
|
||||||
|
const windowId = Number.isFinite(tsMs) ? Math.floor(tsMs / WINDOW_MS) : null;
|
||||||
|
return { hdr, row, ts, windowId, rec: row[hdr.indexOf("recommendation")] || "" };
|
||||||
}
|
}
|
||||||
|
|
||||||
function watch() {
|
function watch() {
|
||||||
@@ -103,33 +107,22 @@ function watch() {
|
|||||||
setTimeout(watch, 1000);
|
setTimeout(watch, 1000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(`[watcher] watching ${CSV_PATH} (cooldown ${COOLDOWN_MS}ms${DRY_RUN ? ", DRY_RUN" : ""})`);
|
console.log(`[watcher] watching ${CSV_PATH} (window ${WINDOW_MS / 60_000}min${DRY_RUN ? ", DRY_RUN" : ""})`);
|
||||||
|
|
||||||
fs.watch(CSV_PATH, { persistent: true }, () => {
|
fs.watch(CSV_PATH, { persistent: true }, () => {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
const data = readLast();
|
const data = readLast();
|
||||||
if (!data) return;
|
if (!data || data.windowId === null || !data.rec.includes(":")) return;
|
||||||
const isEnter = data.rec.includes(":");
|
const [side, phase, strength] = data.rec.split(":");
|
||||||
const side = isEnter ? data.rec.split(":")[0] : null;
|
if (data.windowId !== lastSentWindowId && data.ts !== lastSentTs) {
|
||||||
if (!side) {
|
|
||||||
prevSide = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const [, phase, strength] = data.rec.split(":");
|
|
||||||
const ts = data.row[data.hdr.indexOf("timestamp")];
|
|
||||||
const flipped = side !== prevSide;
|
|
||||||
const cooled = Date.now() - lastSentAt > COOLDOWN_MS;
|
|
||||||
const newRow = ts !== lastSentTs;
|
|
||||||
if (flipped && cooled && newRow) {
|
|
||||||
const slug = getCurrentSlug();
|
const slug = getCurrentSlug();
|
||||||
console.log("[watcher] slug for push:", slug);
|
console.log("[watcher] slug for push:", slug);
|
||||||
const msg = buildMessage(data.hdr, data.row, side, phase, strength, slug);
|
const msg = buildMessage(data.hdr, data.row, side, phase, strength, slug);
|
||||||
await send(msg);
|
await send(msg);
|
||||||
lastSentAt = Date.now();
|
lastSentWindowId = data.windowId;
|
||||||
lastSentTs = ts;
|
lastSentTs = data.ts;
|
||||||
}
|
}
|
||||||
prevSide = side;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[watcher] error:", e.message);
|
console.error("[watcher] error:", e.message);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user