Massive.com is formerly Polygon.io
Fetch real-time and historical stock data from Massive.com and render interactive candlestick charts in the browser. 100% client-side, zero backend required.
Enter your Massive.com 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 Massive.com when you change timeframes.
Everything you need to get Massive.com data into a chart
Head to massive.com and create a free account. Once logged in, your API key is displayed on the dashboard.
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 Aggregates (Bars) endpoint to fetch OHLCV data. This works directly in the browser — Massive.com supports CORS on their REST API.
const API_KEY = 'YOUR_MASSIVE_API_KEY';
const ticker = 'AAPL';
const from = '2024-01-01';
const to = '2025-01-01';
const url = `https://api.massive.com/v2/aggs/ticker/${ticker}/range/1/day/${from}/${to}?adjusted=true&sort=asc&apiKey=${API_KEY}`;
const response = await fetch(url);
const json = await response.json();
Massive.com returns data in this format:
{
"ticker": "AAPL",
"resultsCount": 251,
"results": [
{
"o": 185.33, // open
"h": 186.10, // high
"l": 183.79, // low
"c": 185.56, // close
"v": 45123456, // volume
"t": 1704067200000 // timestamp (ms)
},
...
]
}
| Massive Field | Description | Chart Field |
|---|---|---|
o | Open price | open |
h | High price | high |
l | Low price | low |
c | Close price | close |
v | Volume | volume |
t | Timestamp (ms) | t |
Map Massive.com's shorthand field names to the chart library's expected format. This is a simple one-liner with .map():
function transformPolygonData(response) {
return response.results.map(bar => {
var d = new Date(bar.t);
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: bar.t, date: date, open: bar.o, high: bar.h,
low: bar.l, close: bar.c, volume: bar.v };
});
}
const chartData = transformPolygonData(json);
sort=asc in the URL, which is exactly 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 Massive.com API parameters
function mapTimeframeToPolygonParams(tf) {
var map = {
'1min': { mult: 1, span: 'minute', days: 5 },
'5min': { mult: 5, span: 'minute', days: 5 },
'15min': { mult: 15, span: 'minute', days: 10 },
'1hour': { mult: 1, span: 'hour', days: 30 },
'1day': { mult: 1, span: 'day', days: 365 },
'1week': { mult: 1, span: 'week', days: 1095 },
'1month':{ mult: 1, span: 'month', days: 1825 }
};
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 = mapTimeframeToPolygonParams(chart.timeframe || '1day');
var to = new Date(earliestTimestamp - 1);
var from = new Date(to); from.setDate(from.getDate() - p.days);
var url = 'https://api.massive.com/v2/aggs/ticker/' + ticker
+ '/range/' + p.mult + '/' + p.span
+ '/' + fmt(from) + '/' + fmt(to)
+ '?adjusted=true&sort=asc&apiKey=' + API_KEY;
var resp = await fetch(url);
var json = await resp.json();
if (json.results && json.results.length > 0) {
var older = transformPolygonData(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 Massive.com. The onReachingStart callback fires when the user pans to the beginning of loaded data, triggering a fetch of older candles that get prepended seamlessly. Technical indicators, pan, zoom, crosshair, volume, and more are all built-in.
Copy-paste this entire HTML file. Replace YOUR_MASSIVE_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>Massive.com + 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; }
#status.success { color: #10b981; }
</style>
</head>
<body>
<h1>Massive.com + Stock Charts</h1>
<p class="subtitle">
Powered by <a href="https://simul8or.com/Javascript-Stock-Chart.php">JavaScript Stock Charts</a>
</p>
<div class="controls">
<div>
<label>API Key</label>
<input type="text" id="apiKey" placeholder="Your Massive.com 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 Massive.com API parameters
function mapTimeframeToPolygonParams(tf) {
var map = {
'1min': { mult: 1, span: 'minute', days: 5 },
'2min': { mult: 2, span: 'minute', days: 5 },
'5min': { mult: 5, span: 'minute', days: 5 },
'15min': { mult: 15, span: 'minute', days: 10 },
'30min': { mult: 30, span: 'minute', days: 15 },
'1hour': { mult: 1, span: 'hour', days: 30 },
'60min': { mult: 1, span: 'hour', days: 30 },
'4hour': { mult: 4, span: 'hour', days: 90 },
'1day': { mult: 1, span: 'day', days: 365 },
'1week': { mult: 1, span: 'week', days: 1095 },
'1W': { mult: 1, span: 'week', days: 1095 },
'1month': { mult: 1, span: 'month', days: 1825 },
'1M': { mult: 1, span: 'month', days: 1825 }
};
return map[tf] || map['1day'];
}
function fmt(d) { return d.toISOString().split('T')[0]; }
function transformPolygonData(json) {
return json.results.map(function(bar) {
var d = new Date(bar.t);
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: bar.t, date: date, open: bar.o, high: bar.h,
low: bar.l, close: bar.c, volume: bar.v };
});
}
// Fetch data from Massive.com 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 Massive.com 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 = mapTimeframeToPolygonParams(timeframe);
status.textContent = 'Fetching ' + timeframe + ' data...';
status.className = '';
var to = new Date();
var from = new Date();
from.setDate(from.getDate() - p.days);
var url = 'https://api.massive.com/v2/aggs/ticker/' + ticker
+ '/range/' + p.mult + '/' + p.span
+ '/' + fmt(from) + '/' + fmt(to)
+ '?adjusted=true&sort=asc&apiKey=' + apiKey;
try {
var resp = await fetch(url);
var json = await resp.json();
if (json.status === 'ERROR' || json.status === 'NOT_FOUND') {
status.textContent = json.error || json.message || 'Error fetching data.';
status.className = 'error'; return;
}
if (!json.results || json.results.length === 0) {
status.textContent = 'No data for ' + ticker + '.';
status.className = 'error'; return;
}
var chartData = transformPolygonData(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 = mapTimeframeToPolygonParams(chart.timeframe || '1day');
var to = new Date(earliestTimestamp - 1);
var from = new Date(to); from.setDate(from.getDate() - p.days);
var url = 'https://api.massive.com/v2/aggs/ticker/' + currentTicker
+ '/range/' + p.mult + '/' + p.span
+ '/' + fmt(from) + '/' + fmt(to)
+ '?adjusted=true&sort=asc&apiKey=' + apiKey;
var resp = await fetch(url);
var json = await resp.json();
if (json.results && json.results.length > 0) {
var older = transformPolygonData(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>
Download the library, grab your Massive.com API key, and start charting.
See more tutorials: JavaScript Stock Charts Documentation