| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { httpGet } from '../utils/httpClient.js'; |
| import { config } from '../config.js'; |
| import { logger } from '../utils/logger.js'; |
|
|
| const FINNHUB_API_URL = 'https://finnhub.io/api/v1'; |
| const API_KEY = config.FINNHUB_API_KEY ?? ''; |
|
|
| |
| const MAX_CALLS_PER_MINUTE = 60; |
| let callTimestamps = []; |
|
|
| function canMakeCall() { |
| const now = Date.now(); |
| const oneMinuteAgo = now - 60_000; |
| callTimestamps = callTimestamps.filter((ts) => ts > oneMinuteAgo); |
| return callTimestamps.length < MAX_CALLS_PER_MINUTE; |
| } |
|
|
| function recordCall() { |
| callTimestamps.push(Date.now()); |
| } |
|
|
| function ensureApiKey() { |
| return !!API_KEY; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function normalizeItem(item) { |
| return { |
| id: item.id || item.url || null, |
| headline: item.headline || item.title || '', |
| summary: item.summary || '', |
| url: item.url || null, |
| source: item.source || null, |
| related: item.related || null, |
| datetime: item.datetime ? new Date(item.datetime * 1000) : null, |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export async function getCompanyNews(symbol, daysBack = 7) { |
| if (!ensureApiKey()) { |
| logger.debug('Finnhub API key not configured, returning empty array'); |
| return []; |
| } |
| if (!canMakeCall()) { |
| logger.warn('Finnhub rate limit exceeded (60 calls/min), returning empty array'); |
| return []; |
| } |
|
|
| const to = new Date(); |
| const from = new Date(Date.now() - daysBack * 24 * 60 * 60 * 1000); |
| const fromStr = from.toISOString().slice(0, 10); |
| const toStr = to.toISOString().slice(0, 10); |
|
|
| const url = `${FINNHUB_API_URL}/company-news?symbol=${encodeURIComponent(symbol)}&from=${fromStr}&to=${toStr}&token=${API_KEY}`; |
|
|
| try { |
| recordCall(); |
| const data = await httpGet(url, { timeout: 10_000, retries: 1 }); |
| if (!Array.isArray(data)) return []; |
| return data.map(normalizeItem); |
| } catch (err) { |
| logger.warn({ err: err.message, symbol }, 'Finnhub getCompanyNews failed'); |
| return []; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export async function getMarketNews(category = 'general', limit = 50) { |
| if (!ensureApiKey()) { |
| logger.debug('Finnhub API key not configured, returning empty array'); |
| return []; |
| } |
| if (!canMakeCall()) { |
| logger.warn('Finnhub rate limit exceeded (60 calls/min), returning empty array'); |
| return []; |
| } |
|
|
| const url = `${FINNHUB_API_URL}/news?category=${encodeURIComponent(category)}&token=${API_KEY}`; |
|
|
| try { |
| recordCall(); |
| const data = await httpGet(url, { timeout: 10_000, retries: 1 }); |
| if (!Array.isArray(data)) return []; |
| return data.slice(0, limit).map(normalizeItem); |
| } catch (err) { |
| logger.warn({ err: err.message, category }, 'Finnhub getMarketNews failed'); |
| return []; |
| } |
| } |
|
|