import { ClaudeBridge } from "../../channels/slack/bridge"; import { sendEmail } from "../../channels/email/send"; const TICKERS = [ { symbol: "^GSPC", label: "S&P 500" }, { symbol: "^IXIC", label: "Nasdaq" }, { symbol: "^KS11", label: "KOSPI" }, { symbol: "^TNX", label: "10Y Treasury Yield" }, { symbol: "^VIX", label: "VIX" }, { symbol: "CL=F", label: "Crude Oil (WTI)" }, { symbol: "KRW=X", label: "USD/KRW" }, { symbol: "BTC-USD", label: "Bitcoin" }, ]; async function fetchTicker(symbol: string): Promise<{ price: number; prev: number; changePct: number } | null> { try { const url = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(symbol)}?interval=1d&range=2d`; const res = await fetch(url, { headers: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" }, }); if (!res.ok) return null; const data = (await res.json()) as any; const meta = data?.chart?.result?.[0]?.meta; if (!meta) return null; const price: number = meta.regularMarketPrice; const prev: number = meta.chartPreviousClose ?? meta.previousClose; if (!price || !prev) return null; const changePct = ((price - prev) / prev) * 100; return { price, prev, changePct }; } catch { return null; } } async function fetchMarketData(): Promise { const rows = await Promise.all( TICKERS.map(async ({ symbol, label }) => { const data = await fetchTicker(symbol); if (!data) return `${label}: unavailable`; const { price, changePct } = data; const sign = changePct >= 0 ? "+" : ""; const priceStr = symbol === "KRW=X" ? price.toFixed(0) : symbol === "^VIX" || symbol === "^TNX" ? price.toFixed(2) : price.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); return `${label}: ${priceStr} (${sign}${changePct.toFixed(2)}%)`; }) ); return rows.join("\n"); } async function main() { console.log("[market-report] starting..."); const now = new Date(); const todayPST = now.toLocaleDateString("en-CA", { timeZone: "America/Los_Angeles" }); const hourPST = Number(now.toLocaleString("en-US", { hour: "numeric", hour12: false, timeZone: "America/Los_Angeles" })); const lockFile = new URL(".last-sent", import.meta.url).pathname; const fs = await import("fs"); if (fs.existsSync(lockFile) && fs.readFileSync(lockFile, "utf8").trim() === todayPST) { console.log(`[market-report] already sent today (${todayPST}), skipping`); process.exit(0); } if (hourPST < 5 || hourPST > 10) { console.log(`[market-report] outside send window (hour=${hourPST}), skipping stale catch-up`); process.exit(0); } const marketData = await fetchMarketData(); console.log("[market-report] market data fetched"); const dateStr = now.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric", timeZone: "America/Los_Angeles", }); const timeStr = now.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", timeZone: "America/Los_Angeles", timeZoneName: "short", }); const prompt = `You are Ace, managing Palace Fund LLC with Junwon. Write a daily pre-market report email in Markdown. Today is ${dateStr}, ${timeStr}. Palace Fund context: - California LLC, Buffett-style value investing - Two members: Junwon (Managing Member, 100% control) + Sungho Park ($300K capital, 50% carry after 5% hurdle) - Monitors: US equities, KOSPI, oil, FX (USD/KRW), macro Guidelines: - Direct, analytical. No fluff. No pleasantries beyond opening line. - Lead with what matters most for the fund today. - Flag any macro risks or sector moves relevant to value investing. - Note any significant overnight news that moved markets. - Keep it scannable — headers, bullets, bold for key numbers. - End with 1-2 sentence "Watch Today" — what to monitor. Sections: 1. ## Markets — use the data below, add brief context for each 2. ## Macro — key themes (rates, Fed, geopolitics, commodities) 3. ## Watch Today — 1-2 sentences on what to monitor --- MARKET DATA: ${marketData}`; console.log("[market-report] calling Claude..."); const bridge = new ClaudeBridge(); bridge.channel = "market-report"; bridge.model = "sonnet"; const reportMd = await bridge.send(prompt); bridge.kill(); const subject = `Palace Fund Market Report — ${dateStr}`; await sendEmail("ace@palace.fund", { to: "junwon@manglasabang.com", subject, markdown: reportMd, footer: `Palace Fund LLC · Daily Market Report · ${dateStr}`, }); fs.writeFileSync(lockFile, todayPST); console.log(`[market-report] sent: ${subject}`); } main().catch((err) => { console.error("[market-report] fatal:", err); process.exit(1); });