Candlestick charts in financial data visualization represent a typical challenge in frontend development—they must efficiently render large amounts of historical data, support real-time updates, and ensure smooth zooming and panning. While many charting libraries can draw line charts and bar charts, few are truly optimized for financial scenarios.
This article will guide you through building an interactive precious metals candlestick dashboard in under 10 minutes, complete with integration to real market data APIs. By the end, you'll master:
- Comparison of frontend libraries best suited for candlestick charts
- Quickly rendering your first candlestick using Lightweight Charts
- Fetching historical data via HTTP + receiving real-time updates via WebSocket
- Engineering essentials like volume sub-charts and secure proxy patterns
1. Library Comparison: Which Charting Library is Best for Candlesticks?
| Library | Positioning | Native Candlestick Support | Size (gzip) | Performance Characteristics | License |
|---|---|---|---|---|---|
| Lightweight Charts | Professional Financial Charts | ✅ | ~12 KB | Optimized for financial real-time, ultra-lightweight | Attribution required |
| ECharts | General Visualization | ✅ | ~80-130 KB | Smooth Canvas rendering, versatile | Apache 2.0 |
| KLineChart | Lightweight Financial Charts | ✅ | ~40 KB | Renders 50k data points in 37ms | Open Source & Free |
Selection Recommendations:
- Ultra-lightweight + financial professionalism → Lightweight Charts (used in this article)
- Need rich chart types + Chinese documentation → ECharts
- Massive data + ultimate rendering performance → KLineChart
2. Getting Started with Lightweight Charts (Mock Data Version)
2.1 Installation
npm install lightweight-charts
Or use CDN directly:
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
2.2 Minimal Code: 5 Candlesticks
<div id="chart" style="width: 800px; height: 500px;"></div>
<script>
const { createChart, CandlestickSeries } = LightweightCharts;
const chart = createChart(document.getElementById("chart"), {
width: 800,
height: 500,
layout: { backgroundColor: "#ffffff", textColor: "#333" },
});
const candlestickSeries = chart.addSeries(CandlestickSeries, {
upColor: "#26a69a", // Bullish candle (up)
downColor: "#ef5350", // Bearish candle (down)
borderVisible: false,
});
candlestickSeries.setData([
{ time: "2024-01-01", open: 2040, high: 2060, low: 2020, close: 2055 },
{ time: "2024-01-02", open: 2055, high: 2080, low: 2045, close: 2070 },
{ time: "2024-01-03", open: 2070, high: 2090, low: 2060, close: 2085 },
{ time: "2024-01-04", open: 2085, high: 2100, low: 2075, close: 2080 },
{ time: "2024-01-05", open: 2080, high: 2095, low: 2065, close: 2075 },
]);
</script>
Open the page to see a candlestick chart with crosshair cursor, zoomable and draggable.
3. Complete Runnable Example (Simulated Real-Time Updates)
Save as index.html and open it to see a gold 1-minute candlestick chart with support for "Add a New Candlestick" to simulate real-time updates:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Gold Candlestick Chart · Simulated Real-Time Market Data</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #f5f7fa;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
.card {
background: white;
border-radius: 16px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
padding: 20px;
width: 100%;
max-width: 1100px;
}
.header {
display: flex;
justify-content: space-between;
margin-bottom: 16px;
align-items: baseline;
}
h1 {
font-size: 1.5rem;
}
.price-info {
font-size: 1.25rem;
font-weight: 600;
color: #26a69a;
}
.chart-container {
width: 100%;
height: 500px;
}
.controls {
margin-top: 16px;
display: flex;
gap: 12px;
justify-content: flex-end;
}
button {
background: #f1f5f9;
border: none;
padding: 8px 16px;
border-radius: 8px;
cursor: pointer;
}
.update-badge {
font-size: 0.75rem;
color: #94a3b8;
}
</style>
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
</head>
<body>
<div class="card">
<div class="header">
<div>
<h1>Gold 1-Minute Candlestick Chart</h1>
<div class="update-badge" id="update-time">Mock Data Demo</div>
</div>
<div class="price-info">
Latest Price: <span id="latest-price">0.00</span>
</div>
</div>
<div id="kline-chart" class="chart-container"></div>
<div class="controls">
<button id="reset-view">Reset View</button>
<button id="add-data">Add 1 Simulated Candlestick</button>
</div>
</div>
<script>
(function () {
// Generate 30 simulated candlesticks
const generateMockData = () => {
let basePrice = 2040;
const data = [];
const now = new Date();
for (let i = 30; i >= 1; i--) {
const minute = new Date(now.getTime() - i * 60 * 1000);
const timeStr = minute.toISOString().slice(0, 19).replace("T", " ");
const variation = (Math.random() - 0.5) * 4;
const open = basePrice;
const close = +(open + variation).toFixed(2);
const high = Math.max(open, close) + Math.random() * 2;
const low = Math.min(open, close) - Math.random() * 2;
data.push({
time: timeStr,
open: +open.toFixed(2),
high: +high.toFixed(2),
low: +low.toFixed(2),
close,
});
basePrice = close;
}
return data;
};
let mockData = generateMockData();
const chart = LightweightCharts.createChart(
document.getElementById("kline-chart"),
{
width: document.getElementById("kline-chart").clientWidth,
height: 500,
layout: { backgroundColor: "#ffffff", textColor: "#1e293b" },
grid: {
vertLines: { color: "#e2e8f0" },
horzLines: { color: "#e2e8f0" },
},
timeScale: { timeVisible: true, secondsVisible: false },
}
);
const candlestickSeries = chart.addSeries(
LightweightCharts.CandlestickSeries,
{
upColor: "#26a69a",
downColor: "#ef5350",
borderVisible: false,
}
);
candlestickSeries.setData(mockData);
chart.timeScale().fitContent();
const updateLatestPrice = () => {
if (mockData.length) {
const latest = mockData[mockData.length - 1];
document.getElementById("latest-price").innerText =
latest.close.toFixed(2);
document.getElementById(
"update-time"
).innerHTML = `Last Update: ${latest.time}`;
}
};
updateLatestPrice();
const addNewCandle = () => {
const last = mockData[mockData.length - 1];
const now = new Date();
const timeStr = now.toISOString().slice(0, 19).replace("T", " ");
const variation = (Math.random() - 0.5) * 3;
const open = last.close;
const close = +(open + variation).toFixed(2);
const high = Math.max(open, close) + Math.random() * 2;
const low = Math.min(open, close) - Math.random() * 2;
const newCandle = {
time: timeStr,
open: +open.toFixed(2),
high: +high.toFixed(2),
low: +low.toFixed(2),
close,
};
mockData.push(newCandle);
candlestickSeries.update(newCandle);
updateLatestPrice();
chart.timeScale().scrollToRealTime();
};
document
.getElementById("add-data")
.addEventListener("click", addNewCandle);
document
.getElementById("reset-view")
.addEventListener("click", () => chart.timeScale().fitContent());
window.addEventListener("resize", () =>
chart.applyOptions({
width: document.getElementById("kline-chart").clientWidth,
})
);
})();
</script>
</body>
</html>
4. Integrating with Real Market Data APIs
In real-world scenarios, you need to connect to market data sources. Below, we use the iTick API that supports both REST + WebSocket as an example to demonstrate integration methods. You can replace it with any service providing similar interfaces (such as your own backend or third-party data providers).
4.1 Historical Candlesticks: Fetching via REST API
iTick's market data API provides futures historical candlestick endpoints:
GET https://api.itick.org/future/kline?symbol=GC®ion=US&kType=1&limit=100
Response format example:
{
"code": 0,
"data": [
{
"t": 1704067200000,
"o": 2040.5,
"h": 2060.2,
"l": 2020.8,
"c": 2055.3,
"v": 12500
}
]
}
When calling from the frontend, it's recommended to proxy through your own backend to avoid exposing API keys. Backend example (Node.js + Express):
app.get("/api/kline", async (req, res) => {
const { symbol, region, kType, limit } = req.query;
const response = await fetch(
`https://api.itick.org/future/kline?symbol=${symbol}®ion=${region}&kType=${kType}&limit=${limit}`,
{ headers: { token: process.env.API_KEY } }
);
const data = await response.json();
res.json(data);
});
Frontend call and conversion to Lightweight Charts required format:
fetch("/api/kline?symbol=GC®ion=US&kType=1&limit=100")
.then((res) => res.json())
.then((data) => {
if (data.code === 0 && data.data) {
const klineData = data.data.map((item) => ({
time: Math.floor(item.t / 1000), // Millisecond timestamp → seconds
open: item.o,
high: item.h,
low: item.l,
close: item.c,
}));
candlestickSeries.setData(klineData);
chart.timeScale().fitContent();
}
});
Note: Different APIs may return timestamps in different units (milliseconds/seconds), so conversion is needed based on actual circumstances.
4.2 Real-Time Updates: WebSocket Subscription for Candlestick Updates
Similarly, using backend proxy for WebSocket as an example, or connecting directly to the market gateway from the frontend (ensuring API Key is not exposed). Below shows a direct frontend connection example to a market WebSocket (assuming the service allows frontend to use temporary tokens directly):
let ws = null;
function connectWebSocket() {
ws = new WebSocket("wss://api.itick.org/future");
ws.onopen = () => {
// Send authentication (specific format depends on API)
ws.send(JSON.stringify({ action: "auth", token: "your_temp_token" }));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.code === 1 && msg.msg === "Connected Successfully") {
console.log("Connection successful");
}
// Subscribe to candlestick channel after successful authentication
if (msg.code === 1 && msg.resAc === "auth") {
ws.send(
JSON.stringify({
ac: "subscribe",
params: "GC$US,SI$US", // Format: {symbol}${region}
types: "kline@1", // Options: depth, quote, tick, kline@1
})
);
}
// Handle candlestick push
if (msg.type === "kline@1") {
const candle = msg.data;
candlestickSeries.update({
time: Math.floor(candle.t / 1000),
open: candle.o,
high: candle.h,
low: candle.l,
close: candle.c,
});
}
};
ws.onclose = () => setTimeout(() => connectWebSocket(), 3000);
ws.onerror = (err) => console.error("WebSocket error", err);
}
connectWebSocket();
Security Warning: API keys and tokens should never be written directly in frontend code. The above examples are for demonstration purposes only. In production environments, sensitive information should be protected through backend proxies or short-lived tokens.
5. Advanced Features: Volume Sub-Chart and Performance Optimization
5.1 Adding Volume Bar Chart
Lightweight Charts supports multiple series overlay, making it easy to add a volume sub-chart:
const volumeSeries = chart.addSeries(LightweightCharts.HistogramSeries, {
color: "#26a69a",
priceFormat: { type: "volume" },
priceScaleId: "", // Independent right axis
});
// Assuming the data obtained from API includes volume v
volumeSeries.setData(
apiData.map((item) => ({
time: Math.floor(item.t / 1000),
value: item.v,
color: item.close >= item.open ? "#26a69a" : "#ef5350",
}))
);
5.2 Technical Indicators (MACD / RSI)
Lightweight Charts doesn't provide built-in indicator calculations, but you can:
- Use
ta.jsortulindfor frontend calculations. - Overlay calculation results as
LineSerieson the chart.
const macdLine = chart.addSeries(LightweightCharts.LineSeries, {
color: "#FF9800",
});
macdLine.setData(macdValues);
If you need comprehensive built-in technical indicator support, consider using react-stockcharts or KLineChart.
5.3 Performance Optimization Tips
-
Data Updates: Use
setDatafor large amounts of historical data, andupdatefor single real-time candlesticks. - Viewport Management: No need to render hundreds of thousands of candlesticks at once; leverage the library's built-in data sampling.
-
Window Resize: Bind
resizeevent and callchart.applyOptions({ width }).
6. Summary
This article demonstrated how to build a precious metals candlestick chart frontend dashboard in 10 minutes using Lightweight Charts, and provided general methods for integrating real market data (REST for historical data + WebSocket for real-time updates). Key technical points include:
- Comparing pros and cons of mainstream candlestick chart libraries and selecting based on scenarios.
- Core usage of Lightweight Charts:
createChart,CandlestickSeries,setData/update. - Rapid prototyping with mock data and how to replace with real APIs.
- Extension ideas like volume sub-charts and indicator overlays.
- Security warning: API keys must never be written in frontend code; backend proxy is mandatory.
Reference Documentation: https://docs.itick.org/websocket/future
GitHub: https://github.com/itick-org/
Top comments (0)