Loading quotes...

Market News

Watchlist news, government/macro events, and top market-moving stories โ€” with live prices for affected tickers or sectors

Loading insights...

Loading trending tickers...

+ q.c.toFixed(2) : '--'; const isMajor = popularTickers.includes(e.symbol); return ` ${e.symbol} ${isMajor ? 'MAJOR' : ''} ${e.date} ${hourLabel(e.hour)} ${e.quarter ? 'Q'+e.quarter : ''} ${e.year || ''} ${e.epsEstimate != null ? e.epsEstimate.toFixed(2) : '--'} ${price} `; }).join(''); } function renderEarningsNews() { const feed = document.getElementById('earningsNewsFeed'); if (earningsNews.length === 0) { feed.innerHTML = `
No recent news found for companies with upcoming earnings.
`; return; } feed.innerHTML = earningsNews.map(item => renderNewsRow(item, 'major')).join(''); } /* ---------- BETA (BIOTECH) TAB ---------- */ const biotechTickers = ['MRNA','BNTX','VRTX','REGN','GILD','BIIB','AMGN','ILMN','IONS','SRPT','BMRN','ALNY','NBIX','EXEL','RARE','FOLD','ARWR','CRSP','NTLA','BEAM','VKTX','SAGE','PTCT','ACAD','HALO','RGNX']; const catalystKeywordRe = /phase (1|2|3|i|ii|iii)\b|topline|data readout|clinical trial|study results|\bfda\b|pdufa|breakthrough therapy|orphan drug|biologics license|new drug application|\bnda\b|\bbla\b|adcom|advisory committee|trial results|primary endpoint/i; async function fetchBiotech(forceRefresh) { const body = document.getElementById('biotechTableBody'); const newsFeed = document.getElementById('biotechNewsFeed'); body.innerHTML = `Loading biotech quotes...`; newsFeed.innerHTML = `
Loading...
`; try { const results = await Promise.all(biotechTickers.map(t => fetchQuote(t).then(q => [t, q]))); results.forEach(([t, q]) => { if (q) quotes[t] = q; }); renderBiotechTable(); // Scan news for a subset (avoid hammering rate limit) for catalyst language const scanList = biotechTickers.slice(0, 14); const fromDate = getDateDaysAgo(7); const toDate = getDateDaysAgo(0); const promises = scanList.map(ticker => fetch(`https://finnhub.io/api/v1/company-news?symbol=${ticker}&from=${fromDate}&to=${toDate}&token=${API_KEY}`) .then(res => res.ok ? res.json() : []) .then(arr => arr.map(item => ({ ...item, biotechTicker: ticker }))) .catch(() => []) ); const arrays = await Promise.all(promises); const combined = arrays.flat(); const flagged = combined.filter(item => catalystKeywordRe.test((item.headline||'') + ' ' + (item.summary||''))); biotechNews = flagged.slice(0, 25).map((item, index) => ({ id: `b-${index}`, headline: item.headline || 'No headline', summary: item.summary || 'Click to read more details...', source: item.source || 'Financial News', time: formatTime(item.datetime), url: item.url || '#', related: item.biotechTicker, isWatchlist: false, isCatalyst: true, chipTickers: [{ ticker: item.biotechTicker, isSectorTop: false }] })); renderBiotechNews(); } catch (e) { console.error('fetchBiotech error', e); body.innerHTML = `Unable to load biotech data right now.`; newsFeed.innerHTML = `
Unable to load news.
`; } } function renderBiotechTable() { const body = document.getElementById('biotechTableBody'); body.innerHTML = biotechTickers.map(t => { const q = quotes[t]; const price = q ? ' /* ---------- WATCHLIST ---------- */ function addToWatchlist() { const input = document.getElementById('watchlistTickerInput'); const ticker = input.value.trim().toUpperCase(); if (!ticker) return; if (watchlist.includes(ticker)) { input.value=''; return; } if (watchlist.length >= 10) { alert('Maximum 10 tickers.'); return; } watchlist.push(ticker); input.value = ''; renderWatchlistTable(); fetchNews(); refreshScreener(); } function removeFromWatchlist(ticker) { watchlist = watchlist.filter(t => t !== ticker); renderWatchlistTable(); fetchNews(); refreshScreener(); } function renderWatchlistTable() { const body = document.getElementById('watchlistTableBody'); if (watchlist.length === 0) { body.innerHTML = `No tickers in your watchlist yet. Add one above.`; return; } body.innerHTML = watchlist.map(t => { const q = quotes[t]; const price = q ? '$' + q.c.toFixed(2) : '--'; const change = q ? q.d.toFixed(2) : '--'; const pct = q ? q.dp.toFixed(2) + '%' : '--'; const dir = q && q.d >= 0 ? 'up' : (q ? 'down' : ''); return ` ${t} ${price} ${change} ${pct} `; }).join(''); } function renderWatchlistNews() { const feed = document.getElementById('watchlistNewsFeed'); const items = allNews.filter(n => n.isWatchlist); if (items.length === 0) { feed.innerHTML = `
No watchlist-specific news loaded yet. Add tickers above, news will populate shortly.
`; return; } feed.innerHTML = items.map(renderNewsRow).join(''); } /* ---------- PORTFOLIO ---------- */ function addToPortfolio() { const ticker = document.getElementById('portTickerInput').value.trim().toUpperCase(); const shares = parseFloat(document.getElementById('portSharesInput').value); const cost = parseFloat(document.getElementById('portCostInput').value); if (!ticker || !shares || shares <= 0) { alert('Enter a ticker and share count.'); return; } portfolio = portfolio.filter(p => p.ticker !== ticker); portfolio.push({ ticker, shares, cost: isNaN(cost) ? null : cost }); document.getElementById('portTickerInput').value = ''; document.getElementById('portSharesInput').value = ''; document.getElementById('portCostInput').value = ''; renderPortfolioTable(); fetchNews(); refreshScreener(); } function removeFromPortfolio(ticker) { portfolio = portfolio.filter(p => p.ticker !== ticker); renderPortfolioTable(); fetchNews(); refreshScreener(); } function renderPortfolioTable() { const body = document.getElementById('portfolioTableBody'); if (portfolio.length === 0) { body.innerHTML = `No holdings added yet. Add one above.`; return; } body.innerHTML = portfolio.map(p => { const q = quotes[p.ticker]; const price = q ? q.c : null; const mktValue = price ? (price * p.shares) : null; let gl = '--', glClass = ''; if (price && p.cost) { const glVal = (price - p.cost) * p.shares; gl = (glVal >= 0 ? '+' : '') + '$' + glVal.toFixed(2); glClass = glVal >= 0 ? 'up' : 'down'; } return ` ${p.ticker} ${p.shares} ${p.cost ? '$' + p.cost.toFixed(2) : '--'} ${price ? '$' + price.toFixed(2) : '--'} ${mktValue ? '$' + mktValue.toFixed(2) : '--'} ${gl} `; }).join(''); } function renderPortfolioNews() { const feed = document.getElementById('portfolioNewsFeed'); const tickers = portfolio.map(p => p.ticker); const items = allNews.filter(n => n.isWatchlist && tickers.includes(n.related)); if (items.length === 0) { feed.innerHTML = `
No portfolio-specific news loaded yet.
`; return; } feed.innerHTML = items.map(renderNewsRow).join(''); } /* ---------- CHARTS ---------- */ function renderCharts() { const container = document.getElementById('chartsContainer'); const tickers = [...new Set([...watchlist, ...portfolio.map(p => p.ticker)])]; if (tickers.length === 0) { container.innerHTML = `
Add tickers to your watchlist or portfolio to see their charts here.
`; return; } container.innerHTML = tickers.map(t => { const q = quotes[t]; let rangeHtml = '
Loading quote data...
'; let headerPrice = ''; if (q) { const low = q.l, high = q.h, cur = q.c, open = q.o; const range = (high - low) || 1; const curPct = ((cur - low) / range) * 100; const openPct = ((open - low) / range) * 100; const dir = q.d >= 0 ? 'up' : 'down'; headerPrice = `$${cur.toFixed(2)} (${q.dp.toFixed(2)}%)`; rangeHtml = `
Low $${low.toFixed(2)}Open $${open.toFixed(2)}High $${high.toFixed(2)}
`; } return `
${t} ${headerPrice}
${rangeHtml}
`; }).join(''); } /* ---------- NEWS FETCH ---------- */ function getDateDaysAgo(days) { const d = new Date(); d.setDate(d.getDate() - days); return d.toISOString().split('T')[0]; } function formatTime(timestamp) { if (!timestamp) return 'Recently'; const date = new Date(timestamp * 1000); const now = new Date(); const diffHours = Math.floor((now - date) / (1000*60*60)); const diffDays = Math.floor(diffHours/24); if (diffHours < 1) return 'Less than 1 hour ago'; if (diffHours < 24) return `${diffHours} hour${diffHours>1?'s':''} ago`; if (diffDays === 1) return '1 day ago'; if (diffDays < 7) return `${diffDays} days ago`; return date.toLocaleDateString(); } async function fetchNews() { try { const fromDate = getDateDaysAgo(5); const toDate = getDateDaysAgo(0); const generalPromise = fetch(`https://finnhub.io/api/v1/news?category=general&token=${API_KEY}`).then(r => r.ok ? r.json() : []); const trackedTickers = [...new Set([...watchlist, ...portfolio.map(p => p.ticker)])]; const trackedPromises = trackedTickers.map(ticker => fetch(`https://finnhub.io/api/v1/company-news?symbol=${ticker}&from=${fromDate}&to=${toDate}&token=${API_KEY}`) .then(r => r.ok ? r.json() : []) .then(data => data.map(item => ({ ...item, watchlistTicker: ticker }))) .catch(() => []) ); const [generalData, ...trackedArrays] = await Promise.all([generalPromise, ...trackedPromises]); const trackedData = trackedArrays.flat(); if ((!generalData || generalData.length === 0) && trackedData.length === 0) return; const watchlistNews = trackedData.slice(0, 30).map((item, index) => ({ id: `w-${index}`, headline: item.headline || 'No headline', summary: item.summary || 'Click to read more details...', source: item.source || 'Financial News', time: formatTime(item.datetime), url: item.url || '#', related: item.watchlistTicker, isWatchlist: true, isHighImpact: /earnings|merger|acquisition|beats|misses|billion|record|surge|plunge|guidance/i.test(item.headline + item.summary), chipTickers: [{ ticker: item.watchlistTicker, isSectorTop: false }] })); const generalNews = (generalData || []).slice(0, 40).map((item, index) => { const fullText = (item.headline||'') + ' ' + (item.summary||''); const detectedTickers = detectTickersInText(fullText); let chipTickers = []; if (detectedTickers.length > 0) { chipTickers = detectedTickers.map(t => ({ ticker: t, isSectorTop: false })); } else { const sector = detectSector(fullText); if (sector) { chipTickers = sectorTickers[sector].map(t => ({ ticker: t, isSectorTop: true, sectorName: sector })); } } return { id: `g-${index}`, headline: item.headline || 'No headline', summary: item.summary || 'Click to read more details...', source: item.source || 'Financial News', time: formatTime(item.datetime), url: item.url || '#', related: item.related || '', isWatchlist: false, isMacro: isMacroNews(fullText), isHighImpact: /earnings|merger|acquisition|beats|misses|billion|record|surge|plunge|fed|rate|guidance/i.test(fullText), inNasdaq: matchesIndex(fullText, nasdaqTickers, nasdaqNames), inSp500: matchesIndex(fullText, sp500Tickers, sp500Names), chipTickers }; }); allNews = [...watchlistNews, ...generalNews]; allNews.sort((a,b) => { if (a.isWatchlist && !b.isWatchlist) return -1; if (!a.isWatchlist && b.isWatchlist) return 1; if (a.isMacro && !b.isMacro) return -1; if (!a.isMacro && b.isMacro) return 1; if (a.isHighImpact && !b.isHighImpact) return -1; if (!a.isHighImpact && b.isHighImpact) return 1; return 0; }); // Fetch quotes for any chip tickers we don't have cached yet const chipTickerSet = new Set(); allNews.forEach(n => (n.chipTickers||[]).forEach(c => chipTickerSet.add(c.ticker))); const needed = [...chipTickerSet].filter(t => !quotes[t]); if (needed.length) { const results = await Promise.all(needed.map(t => fetchQuote(t).then(q => [t, q]))); results.forEach(([t, q]) => { if (q) quotes[t] = q; }); } updateInsights(); await renderTrendingBox(); filterNews(); if (currentView === 'watchlist') renderWatchlistNews(); if (currentView === 'portfolio') renderPortfolioNews(); } catch (e) { console.error('fetchNews error', e); } } function updateInsights() { const box = document.getElementById('insightsBox'); const total = allNews.length; const sources = [...new Set(allNews.map(n => n.source))].length; const wl = allNews.filter(n => n.isWatchlist).length; const macro = allNews.filter(n => n.isMacro).length; const hi = allNews.filter(n => n.isHighImpact).length; box.innerHTML = `

Market Snapshot

`; } /* ---------- ANALYSIS ---------- */ function generateAnalysis(headline, summary) { const isPositive = /beat|exceed|surge|jump|gain|rise|growth|profit|success|increase|up|high|strong|positive|rally/i.test(headline+summary); const isNegative = /miss|drop|fall|decline|loss|cut|reduce|lower|weak|concern|down|plunge|crash|negative/i.test(headline+summary); const isEarnings = /earnings|revenue|profit|quarter|q[1-4]|fiscal|results/i.test(headline+summary); const isMerger = /merger|acquisition|acquire|deal|purchase|buy|takeover/i.test(headline+summary); let companyImpact, priceImpact, considerations; if (isEarnings && isPositive) { companyImpact = 'This earnings beat indicates the company exceeded analyst expectations, suggesting stronger-than-anticipated operational performance.'; priceImpact = 'Positive earnings historically correlate with upward price momentum:'; considerations = ['Average 2-5% price increase in the 5 days following positive earnings beats', 'Institutional buying often increases after positive news', 'Options markets typically price in 1-3% moves on earnings', 'Watch for "buy the rumor, sell the news" if expectations were already high']; } else if (isEarnings && isNegative) { companyImpact = 'This earnings miss suggests the company underperformed relative to analyst expectations.'; priceImpact = 'Negative earnings historically correlate with downward price pressure:'; considerations = ['Average 3-7% price decrease in the 5 days following earnings misses', 'Increased institutional selling pressure managing risk', 'Potential analyst downgrades and revised targets', 'Monitor key technical support levels']; } else if (isMerger) { companyImpact = 'M&A activity signals strategic expansion or consolidation, which can increase market share or create synergies, but carries integration risk.'; priceImpact = 'M&A announcements show varied price impacts:'; considerations = ['Acquirers often see 1-3% initial decline (premium concern)', 'Targets typically rise toward acquisition price', 'Success depends on integration execution', 'Monitor deal terms, financing, and regulatory approval timeline']; } else { companyImpact = 'This is a material market development. Impact depends on scale, sector context, and competitive positioning.'; priceImpact = 'Market reaction depends on multiple factors:'; considerations = ['Broader market sentiment and sector performance', 'Details in subsequent company communications', 'Analyst interpretations and revised forecasts', 'Overall market volatility and macro conditions']; } return { companyImpact, priceImpact, considerations }; } function toggleAnalysis(id) { const el = document.getElementById('analysis-' + id); if (!el) return; document.querySelectorAll('.analysis.open').forEach(a => { if (a.id !== 'analysis-'+id) a.classList.remove('open'); }); el.classList.toggle('open'); } function renderChipRow(item) { const chips = item.chipTickers || []; if (chips.length === 0) return ''; const label = chips[0].isSectorTop ? `Top ${capitalize(chips[0].sectorName)} stocks:` : 'Related:'; return `
${label}${chips.map(c => { const q = quotes[c.ticker]; const dir = q ? (q.d >= 0 ? 'up' : 'down') : ''; const priceStr = q ? `$${q.c.toFixed(2)} ${q.dp >= 0 ? '+' : ''}${q.dp.toFixed(2)}%` : '--'; return `${c.ticker}${priceStr}`; }).join('')}
`; } function renderNewsRow(item) { const a = generateAnalysis(item.headline, item.summary); return `
${item.source}
${item.isWatchlist ? `โญ ${item.related}` : ''} ${item.isHighImpact ? 'HIGH IMPACT' : ''} ${item.isMacro ? 'MACRO/GOV' : ''} ${item.inNasdaq ? 'NASDAQ' : ''} ${item.inSp500 ? 'S&P 500' : ''} ${item.headline}
${item.source} ยท ${item.time}
${item.summary}
${renderChipRow(item)}

1. Company Impact

${a.companyImpact}

2. Historical Price Impact Patterns

${a.priceImpact}

    ${a.considerations.map(c => `
  • ${c}
  • `).join('')}

3. Objective Considerations

Monitor: trading volume, relative strength vs peers, options flow, analyst rating changes.

Compare: current metrics to 5-year averages and sector benchmarks.

Risk: consider position sizing, stop-loss levels, time horizon.

Educational purpose only โ€” based on historical statistical patterns, not investment advice. Do your own research.
Read full article โ†’
`; } function renderNews(items) { const feed = document.getElementById('newsFeed'); if (items.length === 0) { feed.innerHTML = `
No news items found.
`; return; } feed.innerHTML = items.map(renderNewsRow).join(''); } function setIndexScope(scope) { indexScope = scope; document.querySelectorAll('#indexToggle button').forEach((btn,i) => { const scopes = ['all','nasdaq','sp500']; btn.classList.toggle('active', scopes[i] === scope); }); filterNews(); } function filterNews() { const search = document.getElementById('searchInput').value.toLowerCase(); const sourceFilter = document.getElementById('sourceFilter').value; const filtered = allNews.filter(item => { const matchSource = sourceFilter === 'all' || item.source.toLowerCase().includes(sourceFilter.toLowerCase()); const matchSearch = !search || item.headline.toLowerCase().includes(search) || item.summary.toLowerCase().includes(search) || item.source.toLowerCase().includes(search) || (item.related && item.related.toLowerCase().includes(search)); let matchScope = true; if (indexScope !== 'all' && !item.isWatchlist && !item.isMacro) { matchScope = indexScope === 'nasdaq' ? !!item.inNasdaq : !!item.inSp500; } return matchSource && matchSearch && matchScope; }); renderNews(filtered); } /* ---------- INITIAL DATA ---------- */ const initialNews = [ { id:0, headline:'Tech Giants Report Strong Q4 Earnings, Market Rallies', summary:'Major technology companies exceeded Wall Street expectations with robust quarterly results.', source:'Bloomberg', time:'3 hours ago', url:'#', related:'AAPL, MSFT, GOOGL', isHighImpact:true, inNasdaq:true, inSp500:true, chipTickers:[{ticker:'AAPL',isSectorTop:false},{ticker:'MSFT',isSectorTop:false},{ticker:'GOOGL',isSectorTop:false}] }, { id:1, headline:'Federal Reserve Signals Cautious Approach to Rate Changes', summary:'Central bank officials indicate a measured stance on monetary policy adjustments.', source:'Reuters', time:'5 hours ago', url:'#', related:'SPY, DXY', isHighImpact:true, isMacro:true, chipTickers:[{ticker:'SPY',isSectorTop:false}] }, { id:2, headline:'Healthcare Sector Sees Major M&A Activity Surge', summary:'Pharma and biotech companies announce multiple M&A deals totaling billions.', source:'CNBC', time:'7 hours ago', url:'#', related:'JNJ, PFE, ABBV', isHighImpact:true, inSp500:true, chipTickers:[{ticker:'JNJ',isSectorTop:false},{ticker:'PFE',isSectorTop:false},{ticker:'ABBV',isSectorTop:false}] }, { id:3, headline:'Energy Prices Fluctuate on Global Supply Concerns', summary:'Oil and gas markets see volatility amid geopolitical tensions.', source:'Wall Street Journal', time:'9 hours ago', url:'#', related:'XOM, CVX', isHighImpact:true, isMacro:true, inSp500:true, chipTickers:[{ticker:'XOM',isSectorTop:false},{ticker:'CVX',isSectorTop:false}] }, { id:4, headline:'Consumer Spending Shows Resilience Despite Uncertainty', summary:'Retail sales data exceeds expectations, indicating strong consumer demand.', source:'Financial Times', time:'11 hours ago', url:'#', related:'WMT, TGT, AMZN', inNasdaq:true, inSp500:true, chipTickers:[{ticker:'WMT',isSectorTop:false},{ticker:'AMZN',isSectorTop:false}] }, { id:5, headline:'Semiconductor Stocks Rally on AI Chip Demand Forecast', summary:'Chipmakers see gains as analysts project continued strong AI processor demand.', source:'MarketWatch', time:'1 day ago', url:'#', related:'NVDA, AMD, INTC', isHighImpact:true, inNasdaq:true, inSp500:true, chipTickers:[{ticker:'NVDA',isSectorTop:false},{ticker:'AMD',isSectorTop:false}] }, { id:6, headline:'Banking Sector Profits Beat Estimates on Trading Revenue', summary:'Major banks report better-than-expected profits driven by trading divisions.', source:'Bloomberg', time:'1 day ago', url:'#', related:'JPM, BAC, GS', isHighImpact:true, inSp500:true, chipTickers:[{ticker:'JPM',isSectorTop:false},{ticker:'BAC',isSectorTop:false},{ticker:'GS',isSectorTop:false}] }, { id:7, headline:'Renewable Energy Investments Hit Record Levels', summary:'Global investment in renewables reaches all-time high.', source:'Reuters', time:'2 days ago', url:'#', related:'TSLA, ENPH, NEE', inNasdaq:true, inSp500:true, chipTickers:[{ticker:'TSLA',isSectorTop:false}] } ]; window.onload = () => { allNews = initialNews; updateInsights(); filterNews(); refreshScreener(); renderTrendingBox(); fetchNews(); setInterval(refreshScreener, 60000); }; + q.c.toFixed(2) : '--'; const change = q ? q.d.toFixed(2) : '--'; const pct = q ? q.dp.toFixed(2) + '%' : '--'; const dir = q && q.d >= 0 ? 'up' : (q ? 'down' : ''); const inWatch = watchlist.includes(t); return ` ${t} ${price} ${change} ${pct} ${inWatch ? 'WATCHING' : ``} `; }).join(''); } function renderBiotechNews() { const feed = document.getElementById('biotechNewsFeed'); if (biotechNews.length === 0) { feed.innerHTML = `
No recent trial/regulatory language detected in the scanned tickers this week.
`; return; } feed.innerHTML = biotechNews.map(item => renderNewsRow(item, 'catalyst')).join(''); } /* ---------- WATCHLIST ---------- */ function addToWatchlist() { const input = document.getElementById('watchlistTickerInput'); const ticker = input.value.trim().toUpperCase(); if (!ticker) return; if (watchlist.includes(ticker)) { input.value=''; return; } if (watchlist.length >= 10) { alert('Maximum 10 tickers.'); return; } watchlist.push(ticker); input.value = ''; renderWatchlistTable(); fetchNews(); refreshScreener(); } function removeFromWatchlist(ticker) { watchlist = watchlist.filter(t => t !== ticker); renderWatchlistTable(); fetchNews(); refreshScreener(); } function renderWatchlistTable() { const body = document.getElementById('watchlistTableBody'); if (watchlist.length === 0) { body.innerHTML = `No tickers in your watchlist yet. Add one above.`; return; } body.innerHTML = watchlist.map(t => { const q = quotes[t]; const price = q ? '$' + q.c.toFixed(2) : '--'; const change = q ? q.d.toFixed(2) : '--'; const pct = q ? q.dp.toFixed(2) + '%' : '--'; const dir = q && q.d >= 0 ? 'up' : (q ? 'down' : ''); return ` ${t} ${price} ${change} ${pct} `; }).join(''); } function renderWatchlistNews() { const feed = document.getElementById('watchlistNewsFeed'); const items = allNews.filter(n => n.isWatchlist); if (items.length === 0) { feed.innerHTML = `
No watchlist-specific news loaded yet. Add tickers above, news will populate shortly.
`; return; } feed.innerHTML = items.map(renderNewsRow).join(''); } /* ---------- PORTFOLIO ---------- */ function addToPortfolio() { const ticker = document.getElementById('portTickerInput').value.trim().toUpperCase(); const shares = parseFloat(document.getElementById('portSharesInput').value); const cost = parseFloat(document.getElementById('portCostInput').value); if (!ticker || !shares || shares <= 0) { alert('Enter a ticker and share count.'); return; } portfolio = portfolio.filter(p => p.ticker !== ticker); portfolio.push({ ticker, shares, cost: isNaN(cost) ? null : cost }); document.getElementById('portTickerInput').value = ''; document.getElementById('portSharesInput').value = ''; document.getElementById('portCostInput').value = ''; renderPortfolioTable(); fetchNews(); refreshScreener(); } function removeFromPortfolio(ticker) { portfolio = portfolio.filter(p => p.ticker !== ticker); renderPortfolioTable(); fetchNews(); refreshScreener(); } function renderPortfolioTable() { const body = document.getElementById('portfolioTableBody'); if (portfolio.length === 0) { body.innerHTML = `No holdings added yet. Add one above.`; return; } body.innerHTML = portfolio.map(p => { const q = quotes[p.ticker]; const price = q ? q.c : null; const mktValue = price ? (price * p.shares) : null; let gl = '--', glClass = ''; if (price && p.cost) { const glVal = (price - p.cost) * p.shares; gl = (glVal >= 0 ? '+' : '') + '$' + glVal.toFixed(2); glClass = glVal >= 0 ? 'up' : 'down'; } return ` ${p.ticker} ${p.shares} ${p.cost ? '$' + p.cost.toFixed(2) : '--'} ${price ? '$' + price.toFixed(2) : '--'} ${mktValue ? '$' + mktValue.toFixed(2) : '--'} ${gl} `; }).join(''); } function renderPortfolioNews() { const feed = document.getElementById('portfolioNewsFeed'); const tickers = portfolio.map(p => p.ticker); const items = allNews.filter(n => n.isWatchlist && tickers.includes(n.related)); if (items.length === 0) { feed.innerHTML = `
No portfolio-specific news loaded yet.
`; return; } feed.innerHTML = items.map(renderNewsRow).join(''); } /* ---------- CHARTS ---------- */ function renderCharts() { const container = document.getElementById('chartsContainer'); const tickers = [...new Set([...watchlist, ...portfolio.map(p => p.ticker)])]; if (tickers.length === 0) { container.innerHTML = `
Add tickers to your watchlist or portfolio to see their charts here.
`; return; } container.innerHTML = tickers.map(t => { const q = quotes[t]; let rangeHtml = '
Loading quote data...
'; let headerPrice = ''; if (q) { const low = q.l, high = q.h, cur = q.c, open = q.o; const range = (high - low) || 1; const curPct = ((cur - low) / range) * 100; const openPct = ((open - low) / range) * 100; const dir = q.d >= 0 ? 'up' : 'down'; headerPrice = `$${cur.toFixed(2)} (${q.dp.toFixed(2)}%)`; rangeHtml = `
Low $${low.toFixed(2)}Open $${open.toFixed(2)}High $${high.toFixed(2)}
`; } return `
${t} ${headerPrice}
${rangeHtml}
`; }).join(''); } /* ---------- NEWS FETCH ---------- */ function getDateDaysAgo(days) { const d = new Date(); d.setDate(d.getDate() - days); return d.toISOString().split('T')[0]; } function formatTime(timestamp) { if (!timestamp) return 'Recently'; const date = new Date(timestamp * 1000); const now = new Date(); const diffHours = Math.floor((now - date) / (1000*60*60)); const diffDays = Math.floor(diffHours/24); if (diffHours < 1) return 'Less than 1 hour ago'; if (diffHours < 24) return `${diffHours} hour${diffHours>1?'s':''} ago`; if (diffDays === 1) return '1 day ago'; if (diffDays < 7) return `${diffDays} days ago`; return date.toLocaleDateString(); } async function fetchNews() { try { const fromDate = getDateDaysAgo(5); const toDate = getDateDaysAgo(0); const generalPromise = fetch(`https://finnhub.io/api/v1/news?category=general&token=${API_KEY}`).then(r => r.ok ? r.json() : []); const trackedTickers = [...new Set([...watchlist, ...portfolio.map(p => p.ticker)])]; const trackedPromises = trackedTickers.map(ticker => fetch(`https://finnhub.io/api/v1/company-news?symbol=${ticker}&from=${fromDate}&to=${toDate}&token=${API_KEY}`) .then(r => r.ok ? r.json() : []) .then(data => data.map(item => ({ ...item, watchlistTicker: ticker }))) .catch(() => []) ); const [generalData, ...trackedArrays] = await Promise.all([generalPromise, ...trackedPromises]); const trackedData = trackedArrays.flat(); if ((!generalData || generalData.length === 0) && trackedData.length === 0) return; const watchlistNews = trackedData.slice(0, 30).map((item, index) => ({ id: `w-${index}`, headline: item.headline || 'No headline', summary: item.summary || 'Click to read more details...', source: item.source || 'Financial News', time: formatTime(item.datetime), url: item.url || '#', related: item.watchlistTicker, isWatchlist: true, isHighImpact: /earnings|merger|acquisition|beats|misses|billion|record|surge|plunge|guidance/i.test(item.headline + item.summary), chipTickers: [{ ticker: item.watchlistTicker, isSectorTop: false }] })); const generalNews = (generalData || []).slice(0, 40).map((item, index) => { const fullText = (item.headline||'') + ' ' + (item.summary||''); const detectedTickers = detectTickersInText(fullText); let chipTickers = []; if (detectedTickers.length > 0) { chipTickers = detectedTickers.map(t => ({ ticker: t, isSectorTop: false })); } else { const sector = detectSector(fullText); if (sector) { chipTickers = sectorTickers[sector].map(t => ({ ticker: t, isSectorTop: true, sectorName: sector })); } } return { id: `g-${index}`, headline: item.headline || 'No headline', summary: item.summary || 'Click to read more details...', source: item.source || 'Financial News', time: formatTime(item.datetime), url: item.url || '#', related: item.related || '', isWatchlist: false, isMacro: isMacroNews(fullText), isHighImpact: /earnings|merger|acquisition|beats|misses|billion|record|surge|plunge|fed|rate|guidance/i.test(fullText), inNasdaq: matchesIndex(fullText, nasdaqTickers, nasdaqNames), inSp500: matchesIndex(fullText, sp500Tickers, sp500Names), chipTickers }; }); allNews = [...watchlistNews, ...generalNews]; allNews.sort((a,b) => { if (a.isWatchlist && !b.isWatchlist) return -1; if (!a.isWatchlist && b.isWatchlist) return 1; if (a.isMacro && !b.isMacro) return -1; if (!a.isMacro && b.isMacro) return 1; if (a.isHighImpact && !b.isHighImpact) return -1; if (!a.isHighImpact && b.isHighImpact) return 1; return 0; }); // Fetch quotes for any chip tickers we don't have cached yet const chipTickerSet = new Set(); allNews.forEach(n => (n.chipTickers||[]).forEach(c => chipTickerSet.add(c.ticker))); const needed = [...chipTickerSet].filter(t => !quotes[t]); if (needed.length) { const results = await Promise.all(needed.map(t => fetchQuote(t).then(q => [t, q]))); results.forEach(([t, q]) => { if (q) quotes[t] = q; }); } updateInsights(); await renderTrendingBox(); filterNews(); if (currentView === 'watchlist') renderWatchlistNews(); if (currentView === 'portfolio') renderPortfolioNews(); } catch (e) { console.error('fetchNews error', e); } } function updateInsights() { const box = document.getElementById('insightsBox'); const total = allNews.length; const sources = [...new Set(allNews.map(n => n.source))].length; const wl = allNews.filter(n => n.isWatchlist).length; const macro = allNews.filter(n => n.isMacro).length; const hi = allNews.filter(n => n.isHighImpact).length; box.innerHTML = `

Market Snapshot

`; } /* ---------- ANALYSIS ---------- */ function generateAnalysis(headline, summary) { const isPositive = /beat|exceed|surge|jump|gain|rise|growth|profit|success|increase|up|high|strong|positive|rally/i.test(headline+summary); const isNegative = /miss|drop|fall|decline|loss|cut|reduce|lower|weak|concern|down|plunge|crash|negative/i.test(headline+summary); const isEarnings = /earnings|revenue|profit|quarter|q[1-4]|fiscal|results/i.test(headline+summary); const isMerger = /merger|acquisition|acquire|deal|purchase|buy|takeover/i.test(headline+summary); let companyImpact, priceImpact, considerations; if (isEarnings && isPositive) { companyImpact = 'This earnings beat indicates the company exceeded analyst expectations, suggesting stronger-than-anticipated operational performance.'; priceImpact = 'Positive earnings historically correlate with upward price momentum:'; considerations = ['Average 2-5% price increase in the 5 days following positive earnings beats', 'Institutional buying often increases after positive news', 'Options markets typically price in 1-3% moves on earnings', 'Watch for "buy the rumor, sell the news" if expectations were already high']; } else if (isEarnings && isNegative) { companyImpact = 'This earnings miss suggests the company underperformed relative to analyst expectations.'; priceImpact = 'Negative earnings historically correlate with downward price pressure:'; considerations = ['Average 3-7% price decrease in the 5 days following earnings misses', 'Increased institutional selling pressure managing risk', 'Potential analyst downgrades and revised targets', 'Monitor key technical support levels']; } else if (isMerger) { companyImpact = 'M&A activity signals strategic expansion or consolidation, which can increase market share or create synergies, but carries integration risk.'; priceImpact = 'M&A announcements show varied price impacts:'; considerations = ['Acquirers often see 1-3% initial decline (premium concern)', 'Targets typically rise toward acquisition price', 'Success depends on integration execution', 'Monitor deal terms, financing, and regulatory approval timeline']; } else { companyImpact = 'This is a material market development. Impact depends on scale, sector context, and competitive positioning.'; priceImpact = 'Market reaction depends on multiple factors:'; considerations = ['Broader market sentiment and sector performance', 'Details in subsequent company communications', 'Analyst interpretations and revised forecasts', 'Overall market volatility and macro conditions']; } return { companyImpact, priceImpact, considerations }; } function toggleAnalysis(id) { const el = document.getElementById('analysis-' + id); if (!el) return; document.querySelectorAll('.analysis.open').forEach(a => { if (a.id !== 'analysis-'+id) a.classList.remove('open'); }); el.classList.toggle('open'); } function renderChipRow(item) { const chips = item.chipTickers || []; if (chips.length === 0) return ''; const label = chips[0].isSectorTop ? `Top ${capitalize(chips[0].sectorName)} stocks:` : 'Related:'; return `
${label}${chips.map(c => { const q = quotes[c.ticker]; const dir = q ? (q.d >= 0 ? 'up' : 'down') : ''; const priceStr = q ? `$${q.c.toFixed(2)} ${q.dp >= 0 ? '+' : ''}${q.dp.toFixed(2)}%` : '--'; return `${c.ticker}${priceStr}`; }).join('')}
`; } function renderNewsRow(item) { const a = generateAnalysis(item.headline, item.summary); return `
${item.source}
${item.isWatchlist ? `โญ ${item.related}` : ''} ${item.isHighImpact ? 'HIGH IMPACT' : ''} ${item.isMacro ? 'MACRO/GOV' : ''} ${item.inNasdaq ? 'NASDAQ' : ''} ${item.inSp500 ? 'S&P 500' : ''} ${item.headline}
${item.source} ยท ${item.time}
${item.summary}
${renderChipRow(item)}

1. Company Impact

${a.companyImpact}

2. Historical Price Impact Patterns

${a.priceImpact}

    ${a.considerations.map(c => `
  • ${c}
  • `).join('')}

3. Objective Considerations

Monitor: trading volume, relative strength vs peers, options flow, analyst rating changes.

Compare: current metrics to 5-year averages and sector benchmarks.

Risk: consider position sizing, stop-loss levels, time horizon.

Educational purpose only โ€” based on historical statistical patterns, not investment advice. Do your own research.
Read full article โ†’
`; } function renderNews(items) { const feed = document.getElementById('newsFeed'); if (items.length === 0) { feed.innerHTML = `
No news items found.
`; return; } feed.innerHTML = items.map(renderNewsRow).join(''); } function setIndexScope(scope) { indexScope = scope; document.querySelectorAll('#indexToggle button').forEach((btn,i) => { const scopes = ['all','nasdaq','sp500']; btn.classList.toggle('active', scopes[i] === scope); }); filterNews(); } function filterNews() { const search = document.getElementById('searchInput').value.toLowerCase(); const sourceFilter = document.getElementById('sourceFilter').value; const filtered = allNews.filter(item => { const matchSource = sourceFilter === 'all' || item.source.toLowerCase().includes(sourceFilter.toLowerCase()); const matchSearch = !search || item.headline.toLowerCase().includes(search) || item.summary.toLowerCase().includes(search) || item.source.toLowerCase().includes(search) || (item.related && item.related.toLowerCase().includes(search)); let matchScope = true; if (indexScope !== 'all' && !item.isWatchlist && !item.isMacro) { matchScope = indexScope === 'nasdaq' ? !!item.inNasdaq : !!item.inSp500; } return matchSource && matchSearch && matchScope; }); renderNews(filtered); } /* ---------- INITIAL DATA ---------- */ const initialNews = [ { id:0, headline:'Tech Giants Report Strong Q4 Earnings, Market Rallies', summary:'Major technology companies exceeded Wall Street expectations with robust quarterly results.', source:'Bloomberg', time:'3 hours ago', url:'#', related:'AAPL, MSFT, GOOGL', isHighImpact:true, inNasdaq:true, inSp500:true, chipTickers:[{ticker:'AAPL',isSectorTop:false},{ticker:'MSFT',isSectorTop:false},{ticker:'GOOGL',isSectorTop:false}] }, { id:1, headline:'Federal Reserve Signals Cautious Approach to Rate Changes', summary:'Central bank officials indicate a measured stance on monetary policy adjustments.', source:'Reuters', time:'5 hours ago', url:'#', related:'SPY, DXY', isHighImpact:true, isMacro:true, chipTickers:[{ticker:'SPY',isSectorTop:false}] }, { id:2, headline:'Healthcare Sector Sees Major M&A Activity Surge', summary:'Pharma and biotech companies announce multiple M&A deals totaling billions.', source:'CNBC', time:'7 hours ago', url:'#', related:'JNJ, PFE, ABBV', isHighImpact:true, inSp500:true, chipTickers:[{ticker:'JNJ',isSectorTop:false},{ticker:'PFE',isSectorTop:false},{ticker:'ABBV',isSectorTop:false}] }, { id:3, headline:'Energy Prices Fluctuate on Global Supply Concerns', summary:'Oil and gas markets see volatility amid geopolitical tensions.', source:'Wall Street Journal', time:'9 hours ago', url:'#', related:'XOM, CVX', isHighImpact:true, isMacro:true, inSp500:true, chipTickers:[{ticker:'XOM',isSectorTop:false},{ticker:'CVX',isSectorTop:false}] }, { id:4, headline:'Consumer Spending Shows Resilience Despite Uncertainty', summary:'Retail sales data exceeds expectations, indicating strong consumer demand.', source:'Financial Times', time:'11 hours ago', url:'#', related:'WMT, TGT, AMZN', inNasdaq:true, inSp500:true, chipTickers:[{ticker:'WMT',isSectorTop:false},{ticker:'AMZN',isSectorTop:false}] }, { id:5, headline:'Semiconductor Stocks Rally on AI Chip Demand Forecast', summary:'Chipmakers see gains as analysts project continued strong AI processor demand.', source:'MarketWatch', time:'1 day ago', url:'#', related:'NVDA, AMD, INTC', isHighImpact:true, inNasdaq:true, inSp500:true, chipTickers:[{ticker:'NVDA',isSectorTop:false},{ticker:'AMD',isSectorTop:false}] }, { id:6, headline:'Banking Sector Profits Beat Estimates on Trading Revenue', summary:'Major banks report better-than-expected profits driven by trading divisions.', source:'Bloomberg', time:'1 day ago', url:'#', related:'JPM, BAC, GS', isHighImpact:true, inSp500:true, chipTickers:[{ticker:'JPM',isSectorTop:false},{ticker:'BAC',isSectorTop:false},{ticker:'GS',isSectorTop:false}] }, { id:7, headline:'Renewable Energy Investments Hit Record Levels', summary:'Global investment in renewables reaches all-time high.', source:'Reuters', time:'2 days ago', url:'#', related:'TSLA, ENPH, NEE', inNasdaq:true, inSp500:true, chipTickers:[{ticker:'TSLA',isSectorTop:false}] } ]; window.onload = () => { allNews = initialNews; updateInsights(); filterNews(); refreshScreener(); renderTrendingBox(); fetchNews(); setInterval(refreshScreener, 60000); };