Fetch real-time and historical stock data from Twelve Data and render interactive candlestick charts in the browser. 100% client-side, zero backend required.
Enter your Twelve Data 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 Twelve Data when you change timeframes.
Everything you need to get Twelve Data into a chart
Head to twelvedata.com/register and create a free account. Once logged in, your API key is displayed on the dashboard. The free tier gives you 8 API calls per minute and 800 per day with access to real-time and historical stock data across global exchanges.
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>
Use the Time Series endpoint to fetch OHLCV data. This works directly in the browser — Twelve Data supports CORS on their REST API. You specify the symbol, interval, and outputsize (number of bars, up to 5000) as query parameters.
const API_KEY = 'YOUR_TWELVEDATA_API_KEY';
const ticker = 'AAPL';
const url = `https://api.twelvedata.com/time_series?symbol=${ticker}&interval=1day&outputsize=500&apikey=${API_KEY}`;
const response = await fetch(url);
const json = await response.json();
Twelve Data returns data as an array of objects in the values field. Unlike some APIs that use parallel arrays, each bar is a self-contained object. However, all numeric values are returned as strings that need to be parsed, and the array is in reverse chronological order (newest first):
{
"meta": { "symbol": "AAPL", "interval": "1day", ... },
"values": [
{ "datetime": "2024-05-10", "open": "183.45", "high": "184.90", "low": "182.76", "close": "183.05", "volume": "45123456" },
{ "datetime": "2024-05-09", "open": "184.90", "high": "185.09", "low": "182.13", "close": "183.05", "volume": "50383100" },
...
],
"status": "ok"
}
| Twelve Data Field | Type | Chart Field |
|---|---|---|
datetime | String (date/datetime) | t (ms timestamp) |
open | String → parseFloat() | open (number) |
high | String → parseFloat() | high (number) |
low | String → parseFloat() | low (number) |
close | String → parseFloat() | close (number) |
volume | String → parseInt() | volume (number) |
parseFloat() and parseInt() to convert them. Additionally, the values array is in reverse chronological order (newest first), so you must call .reverse() before passing data to the chart.
Reverse the array to get chronological order, parse all string values to numbers, and convert datetime strings to millisecond timestamps. The date property is used by the chart library for pre/post market hour shading:
function transformTwelveData(json) {
return json.values.reverse().map(function(bar) {
var ts = new Date(bar.datetime).getTime();
var d = new Date(ts);
var date = d.toLocaleString('en-US', {
timeZone: 'America/New_York',
year: 'numeric', month: 'numeric', day: 'numeric',
hour: '2-digit', minute: '2-digit', hour12: false
}).replace(',', '');
return {
t: ts, date: date,
open: parseFloat(bar.open), high: parseFloat(bar.high),
low: parseFloat(bar.low), close: parseFloat(bar.close),
volume: parseInt(bar.volume)
};
});
}
const chartData = transformTwelveData(json);
.reverse() is called to put it in chronological order, which is what the chart library expects.
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:
var chart = null;
var previousTimeframe = '1day';
var isLoadingMore = false;
var earliestTimestamp = null;
var hasMoreHistory = true;
var timeframeCheckInterval = null;
// Map chart timeframes to Twelve Data interval + outputsize
function mapTimeframeToTwelveDataParams(tf) {
var map = {
'1min': { interval: '1min', outputsize: 500 },
'2min': { interval: '5min', outputsize: 500 },
'5min': { interval: '5min', outputsize: 500 },
'15min': { interval: '15min', outputsize: 500 },
'30min': { interval: '30min', outputsize: 500 },
'1hour': { interval: '1h', outputsize: 500 },
'60min': { interval: '1h', outputsize: 500 },
'4hour': { interval: '4h', outputsize: 500 },
'1day': { interval: '1day', outputsize: 500 },
'1week': { interval: '1week', outputsize: 260 },
'1W': { interval: '1week', outputsize: 260 },
'1month': { interval: '1month', outputsize: 120 },
'1M': { interval: '1month', outputsize: 120 }
};
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;
try {
var p = mapTimeframeToTwelveDataParams(chart.timeframe || '1day');
// Format end_date from earliest timestamp
var d = new Date(earliestTimestamp - 1);
var endDate = d.getFullYear() + '-'
+ String(d.getMonth() + 1).padStart(2, '0') + '-'
+ String(d.getDate()).padStart(2, '0');
// For intraday, include time
if (['1min','5min','15min','30min','45min','1h','2h','4h','8h'].indexOf(p.interval) !== -1) {
endDate += ' ' + String(d.getHours()).padStart(2, '0') + ':'
+ String(d.getMinutes()).padStart(2, '0') + ':00';
}
var url = 'https://api.twelvedata.com/time_series?symbol=' + ticker
+ '&interval=' + p.interval
+ '&outputsize=' + p.outputsize
+ '&end_date=' + encodeURIComponent(endDate)
+ '&apikey=' + API_KEY;
var resp = await fetch(url);
var json = await resp.json();
if (json.status === 'ok' && json.values && json.values.length > 0) {
var older = transformTwelveData(json);
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 Twelve Data. The onReachingStart callback fires when the user pans to the beginning of loaded data, triggering a fetch of older candles using the end_date parameter that get prepended seamlessly. Technical indicators, pan, zoom, crosshair, volume, and more are all built-in.
Copy-paste this entire HTML file. Replace YOUR_TWELVEDATA_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>Twelve Data + 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>Twelve Data + JavaScript Stock Charts</h1>
<p class="subtitle">
Powered by <a href="https://github.com/day-trading-simulator/javascript-stock-charts">JavaScript Stock Charts</a>
</p>
<div class="controls">
<div>
<label>API Key</label>
<input type="text" id="apiKey" placeholder="Your Twelve Data 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 Twelve Data interval + outputsize
function mapTimeframeToTwelveDataParams(tf) {
var map = {
'1min': { interval: '1min', outputsize: 500 },
'2min': { interval: '5min', outputsize: 500 },
'5min': { interval: '5min', outputsize: 500 },
'15min': { interval: '15min', outputsize: 500 },
'30min': { interval: '30min', outputsize: 500 },
'1hour': { interval: '1h', outputsize: 500 },
'60min': { interval: '1h', outputsize: 500 },
'4hour': { interval: '4h', outputsize: 500 },
'1day': { interval: '1day', outputsize: 500 },
'1week': { interval: '1week', outputsize: 260 },
'1W': { interval: '1week', outputsize: 260 },
'1month': { interval: '1month', outputsize: 120 },
'1M': { interval: '1month', outputsize: 120 }
};
return map[tf] || map['1day'];
}
function transformTwelveData(json) {
return json.values.reverse().map(function(bar) {
var ts = new Date(bar.datetime).getTime();
var d = new Date(ts);
var date = d.toLocaleString('en-US', {
timeZone: 'America/New_York',
year: 'numeric', month: 'numeric', day: 'numeric',
hour: '2-digit', minute: '2-digit', hour12: false
}).replace(',', '');
return {
t: ts, date: date,
open: parseFloat(bar.open), high: parseFloat(bar.high),
low: parseFloat(bar.low), close: parseFloat(bar.close),
volume: parseInt(bar.volume)
};
});
}
// Format end_date for infinite scroll
function formatEndDate(timestamp, interval) {
var d = new Date(timestamp - 1);
var endDate = d.getFullYear() + '-'
+ String(d.getMonth() + 1).padStart(2, '0') + '-'
+ String(d.getDate()).padStart(2, '0');
var intradayIntervals = ['1min','5min','15min','30min','45min','1h','2h','4h','8h'];
if (intradayIntervals.indexOf(interval) !== -1) {
endDate += ' ' + String(d.getHours()).padStart(2, '0') + ':'
+ String(d.getMinutes()).padStart(2, '0') + ':00';
}
return endDate;
}
// Fetch data from Twelve Data 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 Twelve Data 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 = mapTimeframeToTwelveDataParams(timeframe);
status.textContent = 'Fetching ' + timeframe + ' data...';
status.className = '';
var url = 'https://api.twelvedata.com/time_series?symbol=' + ticker
+ '&interval=' + p.interval
+ '&outputsize=' + p.outputsize
+ '&apikey=' + apiKey;
try {
var resp = await fetch(url);
var json = await resp.json();
if (json.status !== 'ok' || !json.values || json.values.length === 0) {
status.textContent = json.message || 'No data for ' + ticker + '.';
status.className = 'error'; return;
}
var chartData = transformTwelveData(json);
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 = true;
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 = mapTimeframeToTwelveDataParams(chart.timeframe || '1day');
var endDate = formatEndDate(earliestTimestamp, p.interval);
var url = 'https://api.twelvedata.com/time_series?symbol=' + currentTicker
+ '&interval=' + p.interval
+ '&outputsize=' + p.outputsize
+ '&end_date=' + encodeURIComponent(endDate)
+ '&apikey=' + apiKey;
var resp = await fetch(url);
var json = await resp.json();
if (json.status === 'ok' && json.values && json.values.length > 0) {
var older = transformTwelveData(json);
earliestTimestamp = older[0].t;
chart.prependHistoricalData(older);
} else {
hasMoreHistory = false;
chart.setLoadingHistoricalData(false);
}
} catch(e) {
chart.setLoadingHistoricalData(false);
} finally {
isLoadingMore = false;
}
}
</script>
</body>
</html>
Grab the library from GitHub, get your Twelve Data API key, and start charting.