// Alpha Vantage API key (stored here; secure on backend for production)
/**
* Alpha Vantage API key (stored here; secure on backend for production).
* @constant {string}
*/
const apiKey = "3NZ62AYDCROUWX3Q";
// Mapping ticker symbols to logo URLs via Clearbit Logo API
/**
* Mapping ticker symbols to logo URLs via Clearbit Logo API.
* @constant {Object.<string, string>}
*/
const logos = {
AAPL: "https://logo.clearbit.com/apple.com",
MSFT: "https://logo.clearbit.com/microsoft.com",
AMZN: "https://logo.clearbit.com/amazon.com",
GOOGL: "https://logo.clearbit.com/google.com",
TSLA: "https://logo.clearbit.com/tesla.com",
META: "https://logo.clearbit.com/meta.com",
NVDA: "https://logo.clearbit.com/nvidia.com",
JPM: "https://logo.clearbit.com/jpmorganchase.com",
JNJ: "https://logo.clearbit.com/jnj.com",
V: "https://logo.clearbit.com/visa.com",
PG: "https://logo.clearbit.com/pg.com",
UNH: "https://logo.clearbit.com/unitedhealthgroup.com",
HD: "https://logo.clearbit.com/homedepot.com",
VZ: "https://logo.clearbit.com/verizon.com",
DIS: "https://logo.clearbit.com/disney.com",
MA: "https://logo.clearbit.com/mastercard.com",
NFLX: "https://logo.clearbit.com/netflix.com",
KO: "https://logo.clearbit.com/coca-cola.com",
PFE: "https://logo.clearbit.com/pfizer.com",
MRK: "https://logo.clearbit.com/merck.com",
INTC: "https://logo.clearbit.com/intel.com",
CSCO: "https://logo.clearbit.com/cisco.com",
T: "https://logo.clearbit.com/att.com",
XOM: "https://logo.clearbit.com/exxonmobil.com",
BA: "https://logo.clearbit.com/boeing.com",
CRM: "https://logo.clearbit.com/salesforce.com",
ORCL: "https://logo.clearbit.com/oracle.com",
IBM: "https://logo.clearbit.com/ibm.com",
MCD: "https://logo.clearbit.com/mcdonalds.com",
WMT: "https://logo.clearbit.com/walmart.com",
CVX: "https://logo.clearbit.com/chevron.com",
ABT: "https://logo.clearbit.com/abbott.com",
COST: "https://logo.clearbit.com/costco.com",
NKE: "https://logo.clearbit.com/nike.com",
DOW: "https://logo.clearbit.com/dow.com",
QCOM: "https://logo.clearbit.com/qualcomm.com",
ADP: "https://logo.clearbit.com/adp.com",
MDT: "https://logo.clearbit.com/medtronic.com",
SBUX: "https://logo.clearbit.com/starbucks.com",
GS: "https://logo.clearbit.com/goldmansachs.com",
AMGN: "https://logo.clearbit.com/amgen.com",
LLY: "https://logo.clearbit.com/lilly.com",
HON: "https://logo.clearbit.com/honeywell.com",
CAT: "https://logo.clearbit.com/caterpillar.com",
GE: "https://logo.clearbit.com/ge.com",
UPS: "https://logo.clearbit.com/ups.com",
RTX: "https://logo.clearbit.com/raytheon.com",
BLK: "https://logo.clearbit.com/blackrock.com",
SPY: "https://logo.clearbit.com/ssga.com",
VOO: "https://logo.clearbit.com/vanguard.com"
};
/**
* DOM element for the stock selection dropdown.
* @constant {HTMLElement}
*/
const stockSelect = document.getElementById("stock-select");
/**
* DOM element for the time range selection dropdown.
* @constant {HTMLElement}
*/
const timeRangeSelect = document.getElementById("time-range-select");
/**
* DOM element for the chart load button.
* @constant {HTMLElement}
*/
const loadChartButton = document.getElementById("load-chart");
/**
* DOM canvas element where the stock chart is rendered.
* @constant {HTMLElement}
*/
const chartCanvas = document.getElementById("stock-chart");
/**
* DOM element for the loading spinner indicator.
* @constant {HTMLElement}
*/
const spinner = document.getElementById("spinner");
/**
* Global Chart.js instance used to render the stock chart.
* @type {Chart}
*/
let stockChart; // Global Chart.js instance
// Helper function to format dates from YYYY-MM-DD to MM-DD-YYYY
/**
* Format a date string from 'YYYY-MM-DD' to 'MM-DD-YYYY'.
*
* @param {string} dateStr - Date string in the format 'YYYY-MM-DD'.
* @returns {string} Formatted date string in the format 'MM-DD-YYYY'.
*/
function formatDate(dateStr) {
const [year, month, day] = dateStr.split("-");
return `${month}-${day}-${year}`;
}
// Fetch daily time series data
/**
* Fetch the daily time series data for a given stock symbol from the Alpha Vantage API.
*
* @async
* @param {string} symbol - Stock ticker symbol.
* @returns {Promise<Object|null>} Promise resolving to daily time series data or null if unavailable.
*/
async function fetchStockTimeSeries(symbol) {
const url = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${symbol}&apikey=${apiKey}`;
try {
const response = await fetch(url);
const data = await response.json();
if (data["Time Series (Daily)"]) {
return data["Time Series (Daily)"];
} else {
throw new Error("Data not available or API limit reached");
}
} catch (error) {
console.error("Error fetching time series data:", error);
alert("Error fetching data: " + error.message);
return null;
}
}
// Fetch stock overview data
/**
* Fetch the stock overview data for a given stock symbol from the Alpha Vantage API.
*
* @async
* @param {string} symbol - Stock ticker symbol.
* @returns {Promise<Object|null>} Promise resolving to the stock overview data or null if unavailable.
*/
async function fetchStockOverview(symbol) {
const url = `https://www.alphavantage.co/query?function=OVERVIEW&symbol=${symbol}&apikey=${apiKey}`;
try {
const response = await fetch(url);
const data = await response.json();
if (data && Object.keys(data).length > 0 && !data["Note"]) {
return data;
} else {
console.error("Overview data not available or API limit reached");
return null;
}
} catch (error) {
console.error("Error fetching stock overview:", error);
return null;
}
}
// Update key details section
/**
* Update the key details section with the stock's data, including price, volume, and overview metrics.
*
* @async
* @param {string} symbol - Stock ticker symbol.
* @param {string} latestDate - The latest date from the time series data.
* @param {Object} latestData - Daily time series data corresponding to the latest date.
* @param {number} avgVolume - Average trading volume over the selected period.
* @returns {Promise<void>}
*/
async function updateKeyDetails(symbol, latestDate, latestData, avgVolume) {
const overviewData = await fetchStockOverview(symbol);
let html = `<table>
<tr><th colspan="2">Key Details for ${symbol} (${formatDate(latestDate)})</th></tr>`;
html += `<tr><td>Open</td><td>$${parseFloat(latestData["1. open"]).toFixed(2)}</td></tr>`;
html += `<tr><td>Day Range</td><td>$${parseFloat(latestData["3. low"]).toFixed(2)} - $${parseFloat(latestData["2. high"]).toFixed(2)}</td></tr>`;
if (overviewData) {
if (overviewData["52WeekHigh"] && overviewData["52WeekLow"]) {
html += `<tr><td>52 Week Range</td><td>$${parseFloat(overviewData["52WeekLow"]).toFixed(2)} - $${parseFloat(overviewData["52WeekHigh"]).toFixed(2)}</td></tr>`;
}
if (overviewData["MarketCapitalization"]) {
html += `<tr><td>Market Cap</td><td>$${overviewData["MarketCapitalization"]}</td></tr>`;
}
if (overviewData["SharesOutstanding"]) {
html += `<tr><td>Shares Outstanding</td><td>${overviewData["SharesOutstanding"]}</td></tr>`;
}
if (overviewData["PERatio"]) {
html += `<tr><td>P/E Ratio</td><td>${overviewData["PERatio"]}</td></tr>`;
}
if (overviewData["EPS"]) {
html += `<tr><td>EPS</td><td>$${overviewData["EPS"]}</td></tr>`;
}
if (overviewData["DividendPerShare"]) {
html += `<tr><td>Dividend</td><td>$${overviewData["DividendPerShare"]}</td></tr>`;
}
if (overviewData["DividendYield"]) {
html += `<tr><td>Dividend Yield</td><td>${(parseFloat(overviewData["DividendYield"]) * 100).toFixed(2)}%</td></tr>`;
}
if (overviewData["ExDividendDate"]) {
html += `<tr><td>Ex-Dividend Date</td><td>${overviewData["ExDividendDate"]}</td></tr>`;
}
}
html += `<tr><td>Average Volume (30 days)</td><td>${Math.round(avgVolume)}</td></tr>`;
html += `</table>`;
const logoUrl = logos[symbol] || "";
let logoHtml = "";
if (logoUrl) {
logoHtml = `<img src="${logoUrl}" alt="${symbol} Logo" class="company-logo">`;
}
document.getElementById("key-details-section").innerHTML = `<h2>Key Details</h2>` + logoHtml + html;
}
// Update chart with selected stock and time range
/**
* Update the stock chart and key details section based on the selected stock symbol.
*
* @async
* @param {string} symbol - Stock ticker symbol.
* @returns {Promise<void>}
*/
async function updateChart(symbol) {
spinner.style.display = 'block';
const timeSeries = await fetchStockTimeSeries(symbol);
if (!timeSeries) {
spinner.style.display = 'none';
return;
}
const numDays = parseInt(timeRangeSelect.value);
const dates = Object.keys(timeSeries).sort();
const recentDates = dates.slice(-numDays);
const prices = recentDates.map(date => parseFloat(timeSeries[date]["4. close"]));
const volumes = recentDates.map(date => parseInt(timeSeries[date]["5. volume"]));
const avgVolume = volumes.reduce((acc, vol) => acc + vol, 0) / volumes.length;
const latestDate = recentDates[recentDates.length - 1];
const latestData = timeSeries[latestDate];
const chartData = {
labels: recentDates,
datasets: [{
label: `${symbol} Closing Prices`,
data: prices,
fill: false,
borderColor: '#4c6ef5',
tension: 0.1
}]
};
const config = {
type: 'line',
data: chartData,
options: {
responsive: true,
scales: {
x: {
ticks: {
maxRotation: 45,
minRotation: 45
}
}
},
plugins: {
legend: { position: 'top' },
title: {
display: true,
text: `${symbol} Stock Price (${timeRangeSelect.options[timeRangeSelect.selectedIndex].text})`
}
}
}
};
if (stockChart) {
stockChart.destroy();
}
stockChart = new Chart(chartCanvas, config);
updateKeyDetails(symbol, latestDate, latestData, avgVolume);
spinner.style.display = 'none';
}
loadChartButton.addEventListener("click", () => {
updateChart(stockSelect.value);
});
document.addEventListener("DOMContentLoaded", () => {
updateChart(stockSelect.value);
});