Fetch real-time and historical stock data from Alpha Vantage and render interactive candlestick charts in the browser. 100% client-side, zero backend required.
Enter your Alpha Vantage API key and a ticker symbol to fetch real market data. Use the chart's built-in timeframe selector to switch between intraday, daily, weekly, and monthly views — new data is automatically fetched from Alpha Vantage when you change timeframes.
Everything you need to get Alpha Vantage data into a chart
Head to alphavantage.co/support/#api-key and claim your free API key. No account creation needed — just enter your name and email. The free tier gives you 25 API calls per day, which is quite limited. Premium plans are available for higher limits.
Add the JavaScript Stock Charts library to your HTML page via CDN. It's zero-dependency — just three script tags and one CSS link. No downloads or installs required.
<!-- CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/day-trading-simulator/javascript-stock-charts@main/stock-chart.css">
<!-- Chart container -->
<div id="chart" style="width: 100%; height: 500px;"></div>
<!-- JS (order matters) -->
<script src="https://cdn.jsdelivr.net/gh/day-trading-simulator/javascript-stock-charts@main/indicators.js"></script>
<script src="https://cdn.jsdelivr.net/gh/day-trading-simulator/javascript-stock-charts@main/patterns.js"></script>
<script src="https://cdn.jsdelivr.net/gh/day-trading-simulator/javascript-stock-charts@main/stock-chart.js"></script>
Alpha Vantage uses different function parameters for different timeframes: TIME_SERIES_INTRADAY for 1min–60min, TIME_SERIES_DAILY for daily, TIME_SERIES_WEEKLY for weekly, and TIME_SERIES_MONTHLY for monthly. This works directly in the browser — Alpha Vantage supports CORS on their REST API. The API key is passed as a query parameter.
const API_KEY = 'YOUR_ALPHA_VANTAGE_API_KEY';
const ticker = 'AAPL';
// Daily data
const url = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${ticker}&outputsize=compact&apikey=${API_KEY}`;
// Intraday data (5min with extended hours)
// const url = `https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=${ticker}&interval=5min&outputsize=compact&extended_hours=true&apikey=${API_KEY}`;
const response = await fetch(url);
const json = await response.json();
Alpha Vantage returns data as an object keyed by datetime strings (not an array). The time series key name changes depending on the endpoint and interval. Field names have numbered prefixes like "1. open":
{
"Meta Data": {
"1. Information": "Daily Prices (open, high, low, close) and Volumes",
"2. Symbol": "AAPL",
"3. Last Refreshed": "2026-03-11",
"4. Output Size": "Compact",
"5. Time Zone": "US/Eastern"
},
"Time Series (Daily)": {
"2026-03-11": {
"1. open": "260.14",
"2. high": "262.48",
"3. low": "258.90",
"4. close": "261.35",
"5. volume": "45123456"
},
"2026-03-10": { ... }
}
}
| Alpha Vantage Field | Description | Chart Field |
|---|---|---|
"1. open" | Open price (string) | open |
"2. high" | High price (string) | high |
"3. low" | Low price (string) | low |
"4. close" | Close price (string) | close |
"5. volume" | Volume (string) | volume |
parseFloat/parseInt). Data is an object keyed by datetime, not an array. The response key name changes per endpoint: "Time Series (Daily)", "Time Series (5min)", "Weekly Time Series", etc. Data comes in reverse chronological order (newest first).
Convert Alpha Vantage's datetime-keyed object into a sorted array of objects that the chart library expects. Use Object.keys().sort() to handle the reverse chronological order and parseFloat() to convert string values to numbers. The datetime strings from Alpha Vantage are already in US/Eastern timezone, so we use them directly as the date property:
function transformAlphaVantageData(json, seriesKey) {
var series = json[seriesKey];
if (!series) return [];
return Object.keys(series).sort().map(function(datetime) {
var bar = series[datetime];
return {
t: new Date(datetime).getTime(),
date: datetime,
open: parseFloat(bar['1. open']),
high: parseFloat(bar['2. high']),
low: parseFloat(bar['3. low']),
close: parseFloat(bar['4. close']),
volume: parseInt(bar['5. volume'])
};
});
}
const chartData = transformAlphaVantageData(json, 'Time Series (Daily)');
Object.keys().sort() sorts the datetime keys alphabetically, which gives chronological order since they use YYYY-MM-DD format. The date property uses the raw Alpha Vantage datetime string directly — it's already in US/Eastern timezone.
Create a StockChart instance with an onReachingStart callback for infinite scroll. Then monitor the chart's built-in timeframe selector to re-fetch data when the user switches timeframes. Alpha Vantage uses different function parameters for each timeframe:
var chart = null;
var previousTimeframe = '1day';
var isLoadingMore = false;
var earliestTimestamp = null;
var hasMoreHistory = true;
var timeframeCheckInterval = null;
// Map chart timeframes to Alpha Vantage function + params
function mapTimeframeToAlphaVantageParams(tf) {
var map = {
'1min': { fn: 'TIME_SERIES_INTRADAY', interval: '1min', outputsize: 'compact', seriesKey: 'Time Series (1min)' },
'2min': { fn: 'TIME_SERIES_INTRADAY', interval: '5min', outputsize: 'compact', seriesKey: 'Time Series (5min)' },
'5min': { fn: 'TIME_SERIES_INTRADAY', interval: '5min', outputsize: 'compact', seriesKey: 'Time Series (5min)' },
'15min': { fn: 'TIME_SERIES_INTRADAY', interval: '15min', outputsize: 'compact', seriesKey: 'Time Series (15min)' },
'30min': { fn: 'TIME_SERIES_INTRADAY', interval: '30min', outputsize: 'compact', seriesKey: 'Time Series (30min)' },
'1hour': { fn: 'TIME_SERIES_INTRADAY', interval: '60min', outputsize: 'compact', seriesKey: 'Time Series (60min)' },
'60min': { fn: 'TIME_SERIES_INTRADAY', interval: '60min', outputsize: 'compact', seriesKey: 'Time Series (60min)' },
'4hour': { fn: 'TIME_SERIES_INTRADAY', interval: '60min', outputsize: 'compact', seriesKey: 'Time Series (60min)' },
'1day': { fn: 'TIME_SERIES_DAILY', interval: null, outputsize: 'compact', seriesKey: 'Time Series (Daily)' },
'1week': { fn: 'TIME_SERIES_WEEKLY', interval: null, outputsize: null, seriesKey: 'Weekly Time Series' },
'1W': { fn: 'TIME_SERIES_WEEKLY', interval: null, outputsize: null, seriesKey: 'Weekly Time Series' },
'1month': { fn: 'TIME_SERIES_MONTHLY', interval: null, outputsize: null, seriesKey: 'Monthly Time Series' },
'1M': { fn: 'TIME_SERIES_MONTHLY', interval: null, outputsize: null, seriesKey: 'Monthly Time Series' }
};
return map[tf] || map['1day'];
}
// Initialize chart
chart = new StockChart('chart', {
data: chartData,
ticker: ticker,
chartType: 'candlestick',
darkMode: true,
timeframe: '1day',
useAfterHoursStyling: true,
onReachingStart: handleReachingStart
});
earliestTimestamp = chartData[0].t;
// Monitor chart's built-in timeframe selector
setupTimeframeMonitor();
function setupTimeframeMonitor() {
if (timeframeCheckInterval) clearInterval(timeframeCheckInterval);
timeframeCheckInterval = setInterval(function() {
if (chart && chart.timeframe !== previousTimeframe) {
previousTimeframe = chart.timeframe;
loadChart(); // re-fetch with new timeframe
}
}, 500);
}
// Infinite scroll - fetch older data when user pans left
async function handleReachingStart() {
if (isLoadingMore || !hasMoreHistory || !earliestTimestamp) return;
isLoadingMore = true;
var p = mapTimeframeToAlphaVantageParams(chart.timeframe || '1day');
if (p.fn !== 'TIME_SERIES_INTRADAY') {
// Daily/weekly/monthly with full output already has all data
hasMoreHistory = false;
chart.setLoadingHistoricalData(false);
isLoadingMore = false;
return;
}
// For intraday, fetch previous month
var d = new Date(earliestTimestamp);
d.setMonth(d.getMonth() - 1);
var month = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0');
var url = 'https://www.alphavantage.co/query?function=' + p.fn
+ '&symbol=' + ticker
+ '&interval=' + p.interval
+ '&month=' + month
+ '&outputsize=compact'
+ '&extended_hours=true'
+ '&apikey=' + API_KEY;
try {
var resp = await fetch(url);
var json = await resp.json();
if (json['Error Message'] || json['Note']) {
hasMoreHistory = false;
chart.setLoadingHistoricalData(false);
} else {
var older = transformAlphaVantageData(json, p.seriesKey);
if (older.length > 0) {
earliestTimestamp = older[0].t;
chart.prependHistoricalData(older);
} else {
hasMoreHistory = false;
chart.setLoadingHistoricalData(false);
}
}
} catch(e) {
chart.setLoadingHistoricalData(false);
} finally {
isLoadingMore = false;
}
}
The chart includes a built-in timeframe dropdown — when the user clicks it, we detect the change via setInterval polling on chart.timeframe and automatically re-fetch data from Alpha Vantage. For infinite scroll, intraday data uses the month parameter to fetch previous months, while daily/weekly/monthly data with outputsize=compact already includes 20+ years of history. Technical indicators, pan, zoom, crosshair, volume, and more are all built-in.
Copy-paste this entire HTML file. Replace YOUR_ALPHA_VANTAGE_API_KEY with your key, open it in a browser, and you'll have a working stock chart in seconds.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Alpha Vantage + JavaScript Stock Charts</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/day-trading-simulator/javascript-stock-charts@main/stock-chart.css">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
padding: 20px;
background: #0a0a0f;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
color: #fff;
}
h1 { font-size: 1.4rem; margin-bottom: 4px; }
.subtitle { color: #888; margin-bottom: 16px; }
.subtitle a { color: #22d3ee; }
.controls {
display: flex; gap: 10px; margin-bottom: 12px;
flex-wrap: wrap; align-items: flex-end;
}
.controls label {
display: block; font-size: 0.7rem;
text-transform: uppercase; letter-spacing: 1px;
color: #888; margin-bottom: 4px;
}
.controls input {
background: #16161f; border: 1px solid #333;
color: #fff; padding: 8px 12px; font-size: 0.9rem;
border-radius: 2px;
}
.controls button {
background: #10b981; border: none; color: #fff;
padding: 8px 24px; font-weight: 700; cursor: pointer;
border-radius: 2px;
}
.controls button:hover { background: #059669; }
.sc-chart-type-button.d-none { display: inline-flex !important; }
#status { font-size: 0.85rem; color: #888; margin-bottom: 8px; }
#status.error { color: #ef4444; }
</style>
</head>
<body>
<h1>Alpha Vantage + JavaScript Stock Charts</h1>
<p class="subtitle">
Powered by <a href="https://simul8or.com/Javascript-Stock-Chart.php">JavaScript Stock Charts</a>
| Free tier: 25 API calls/day
</p>
<div class="controls">
<div>
<label>API Key</label>
<input type="text" id="apiKey" placeholder="Your Alpha Vantage API key" style="width:280px">
</div>
<div>
<label>Ticker</label>
<input type="text" id="ticker" value="AAPL" style="width:100px">
</div>
<button onclick="loadChart()">Load Chart</button>
</div>
<div id="status"></div>
<div id="chart" style="width:100%; height:500px;"></div>
<script src="https://cdn.jsdelivr.net/gh/day-trading-simulator/javascript-stock-charts@main/indicators.js"></script>
<script src="https://cdn.jsdelivr.net/gh/day-trading-simulator/javascript-stock-charts@main/patterns.js"></script>
<script src="https://cdn.jsdelivr.net/gh/day-trading-simulator/javascript-stock-charts@main/stock-chart.js"></script>
<script>
var chart = null;
var currentTicker = 'AAPL';
var previousTimeframe = '1day';
var timeframeCheckInterval = null;
var isLoadingMore = false;
var earliestTimestamp = null;
var hasMoreHistory = true;
// Map chart timeframes to Alpha Vantage function + params
function mapTimeframeToAlphaVantageParams(tf) {
var map = {
'1min': { fn: 'TIME_SERIES_INTRADAY', interval: '1min', outputsize: 'compact', seriesKey: 'Time Series (1min)' },
'2min': { fn: 'TIME_SERIES_INTRADAY', interval: '5min', outputsize: 'compact', seriesKey: 'Time Series (5min)' },
'5min': { fn: 'TIME_SERIES_INTRADAY', interval: '5min', outputsize: 'compact', seriesKey: 'Time Series (5min)' },
'15min': { fn: 'TIME_SERIES_INTRADAY', interval: '15min', outputsize: 'compact', seriesKey: 'Time Series (15min)' },
'30min': { fn: 'TIME_SERIES_INTRADAY', interval: '30min', outputsize: 'compact', seriesKey: 'Time Series (30min)' },
'1hour': { fn: 'TIME_SERIES_INTRADAY', interval: '60min', outputsize: 'compact', seriesKey: 'Time Series (60min)' },
'60min': { fn: 'TIME_SERIES_INTRADAY', interval: '60min', outputsize: 'compact', seriesKey: 'Time Series (60min)' },
'4hour': { fn: 'TIME_SERIES_INTRADAY', interval: '60min', outputsize: 'compact', seriesKey: 'Time Series (60min)' },
'1day': { fn: 'TIME_SERIES_DAILY', interval: null, outputsize: 'compact', seriesKey: 'Time Series (Daily)' },
'1week': { fn: 'TIME_SERIES_WEEKLY', interval: null, outputsize: null, seriesKey: 'Weekly Time Series' },
'1W': { fn: 'TIME_SERIES_WEEKLY', interval: null, outputsize: null, seriesKey: 'Weekly Time Series' },
'1month': { fn: 'TIME_SERIES_MONTHLY', interval: null, outputsize: null, seriesKey: 'Monthly Time Series' },
'1M': { fn: 'TIME_SERIES_MONTHLY', interval: null, outputsize: null, seriesKey: 'Monthly Time Series' }
};
return map[tf] || map['1day'];
}
function transformAlphaVantageData(json, seriesKey) {
var series = json[seriesKey];
if (!series) return [];
return Object.keys(series).sort().map(function(datetime) {
var bar = series[datetime];
return {
t: new Date(datetime).getTime(),
date: datetime,
open: parseFloat(bar['1. open']),
high: parseFloat(bar['2. high']),
low: parseFloat(bar['3. low']),
close: parseFloat(bar['4. close']),
volume: parseInt(bar['5. volume'])
};
});
}
// Fetch data from Alpha Vantage and render chart
async function loadChart() {
var apiKey = document.getElementById('apiKey').value.trim();
var ticker = document.getElementById('ticker').value.trim().toUpperCase();
var status = document.getElementById('status');
if (!apiKey) { status.textContent = 'Enter your Alpha Vantage API key.'; status.className = 'error'; return; }
if (!ticker) { status.textContent = 'Enter a ticker symbol.'; status.className = 'error'; return; }
var timeframe = (chart && chart.timeframe) ? chart.timeframe : '1day';
var p = mapTimeframeToAlphaVantageParams(timeframe);
status.textContent = 'Fetching ' + timeframe + ' data...';
status.className = '';
var url = 'https://www.alphavantage.co/query?function=' + p.fn + '&symbol=' + ticker;
if (p.interval) url += '&interval=' + p.interval;
if (p.outputsize) url += '&outputsize=' + p.outputsize;
if (p.fn === 'TIME_SERIES_INTRADAY') url += '&extended_hours=true';
url += '&apikey=' + apiKey;
try {
var resp = await fetch(url);
var json = await resp.json();
if (json['Error Message']) {
status.textContent = json['Error Message'];
status.className = 'error'; return;
}
if (json['Note'] || json['Information']) {
status.textContent = json['Note'] || json['Information'];
status.className = 'error'; return;
}
var chartData = transformAlphaVantageData(json, p.seriesKey);
if (chartData.length === 0) {
status.textContent = 'No data returned for ' + ticker + '.';
status.className = 'error'; return;
}
if (chart) chart.destroy();
if (timeframeCheckInterval) clearInterval(timeframeCheckInterval);
currentTicker = ticker;
chart = new StockChart('chart', {
data: chartData, ticker: ticker,
chartType: 'candlestick', darkMode: true,
timeframe: timeframe,
useAfterHoursStyling: true,
onReachingStart: handleReachingStart
});
isLoadingMore = false;
hasMoreHistory = (p.fn === 'TIME_SERIES_INTRADAY');
earliestTimestamp = chartData[0].t;
previousTimeframe = chart.timeframe || timeframe;
setupTimeframeMonitor();
status.textContent = '';
status.className = '';
} catch (err) {
status.textContent = 'Network error: ' + err.message;
status.className = 'error';
}
}
// Monitor chart's built-in timeframe selector for changes
function setupTimeframeMonitor() {
if (timeframeCheckInterval) clearInterval(timeframeCheckInterval);
timeframeCheckInterval = setInterval(function() {
if (chart && chart.timeframe !== previousTimeframe) {
previousTimeframe = chart.timeframe;
loadChart();
}
}, 500);
}
// Infinite scroll - fetch older data when user pans to start
async function handleReachingStart() {
if (isLoadingMore || !hasMoreHistory || !earliestTimestamp) return;
isLoadingMore = true;
var apiKey = document.getElementById('apiKey').value.trim();
if (!apiKey) { isLoadingMore = false; return; }
try {
var p = mapTimeframeToAlphaVantageParams(chart.timeframe || '1day');
if (p.fn !== 'TIME_SERIES_INTRADAY') {
hasMoreHistory = false;
chart.setLoadingHistoricalData(false);
isLoadingMore = false;
return;
}
var d = new Date(earliestTimestamp);
d.setMonth(d.getMonth() - 1);
var month = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0');
var url = 'https://www.alphavantage.co/query?function=' + p.fn
+ '&symbol=' + currentTicker
+ '&interval=' + p.interval
+ '&month=' + month
+ '&outputsize=compact'
+ '&extended_hours=true'
+ '&apikey=' + apiKey;
var resp = await fetch(url);
var json = await resp.json();
if (json['Error Message'] || json['Note']) {
hasMoreHistory = false;
chart.setLoadingHistoricalData(false);
} else {
var older = transformAlphaVantageData(json, p.seriesKey);
if (older.length > 0) {
earliestTimestamp = older[0].t;
chart.prependHistoricalData(older);
} else {
hasMoreHistory = false;
chart.setLoadingHistoricalData(false);
}
}
} catch(e) {
chart.setLoadingHistoricalData(false);
} finally {
isLoadingMore = false;
}
}
</script>
</body>
</html>
Download the library, grab your Alpha Vantage API key, and start charting.
See more tutorials: JavaScript Stock Charts Documentation | Finnhub API Tutorial | Twelve Data API Tutorial