import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(__dirname, ".."); try { const { applyGlobalProxyFromEnv } = await import(path.join(ROOT, "src/net/proxy.js")); applyGlobalProxyFromEnv(); } catch {} const { CONFIG } = await import(path.join(ROOT, "src/config.js")); const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN; const CHAT_ID = process.env.TELEGRAM_CHAT_ID; const CSV_PATH = process.env.SIGNALS_CSV || path.join(ROOT, "logs/signals.csv"); const DRY_RUN = process.env.DRY_RUN === "true"; 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)) { console.error("[watcher] Missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID"); console.error("[watcher] Or set DRY_RUN=true to log without sending."); process.exit(1); } let lastSentWindowId = null; let lastSentTs = null; async function send(text) { if (DRY_RUN) { console.log("[watcher] DRY_RUN:", text.replace(/\n/g, " | ")); return; } try { const r = await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ chat_id: CHAT_ID, text, parse_mode: "HTML" }) }); if (!r.ok) console.error("[watcher] Telegram error:", await r.text()); else console.log(`[watcher] sent ${new Date().toISOString()}`); } catch (e) { console.error("[watcher] send failed:", e.message); } } function fmtTime(min) { const m = Math.floor(min); const s = Math.floor((min - m) * 60); return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`; } function buildMessage(hdr, row, side, phase, strength, slug) { const v = (k) => row[hdr.indexOf(k)]; const isUp = side === "UP"; const edge = Number(isUp ? v("edge_up") : v("edge_down")); const model = Number(isUp ? v("model_up") : v("model_down")); const mktYes = Number(isUp ? v("mkt_up") : v("mkt_down")); const mktNo = Number(isUp ? v("mkt_down") : v("mkt_up")); const mktSum = mktYes + mktNo; const mkt = mktSum > 0 ? mktYes / mktSum : mktYes; const lines = [ `🚨 ENTER ${side} — ${phase} (${strength})`, `Edge: ${edge >= 0 ? "+" : ""}${(edge * 100).toFixed(1)}%`, `Model ${(model * 100).toFixed(1)}% vs Market ${(mkt * 100).toFixed(1)}%`, `Time left: ${fmtTime(Number(v("time_left_min")))}`, `Regime: ${v("regime")}`, v("timestamp") ]; if (slug) lines.push(`🔗 Open on Polymarket`); return lines.join("\n"); } function getCurrentSlug() { if (!fs.existsSync("./logs")) return null; const files = fs.readdirSync("./logs") .filter((f) => f.startsWith("polymarket_market_") && f.endsWith(".json")) .map((f) => ({ f, mtime: fs.statSync(path.join("./logs", f)).mtimeMs })) .sort((a, b) => b.mtime - a.mtime); if (!files.length) return null; try { const m = JSON.parse(fs.readFileSync(path.join("./logs", files[0].f), "utf8")); return m.slug || null; } catch { return null; } } function readLast() { if (!fs.existsSync(CSV_PATH)) return null; const content = fs.readFileSync(CSV_PATH, "utf8"); const lines = content.trim().split("\n"); if (lines.length < 2) return null; const hdr = lines[0].split(","); const row = lines[lines.length - 1].split(","); 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() { if (!fs.existsSync(CSV_PATH)) { console.log(`[watcher] waiting for ${CSV_PATH} ...`); setTimeout(watch, 1000); return; } console.log(`[watcher] watching ${CSV_PATH} (window ${WINDOW_MS / 60_000}min${DRY_RUN ? ", DRY_RUN" : ""})`); fs.watch(CSV_PATH, { persistent: true }, () => { setTimeout(async () => { try { const data = readLast(); if (!data || data.windowId === null || !data.rec.includes(":")) return; const [side, phase, strength] = data.rec.split(":"); if (data.windowId !== lastSentWindowId && data.ts !== lastSentTs) { const slug = getCurrentSlug(); console.log("[watcher] slug for push:", slug); const msg = buildMessage(data.hdr, data.row, side, phase, strength, slug); await send(msg); lastSentWindowId = data.windowId; lastSentTs = data.ts; } } catch (e) { console.error("[watcher] error:", e.message); } }, 300); }); } watch();