In this tutorial, we'll create a dynamic, real-time candlestick chart that visualizes cryptocurrency price data. We'll leverage the power of Next.js for our frontend framework, Lightweight Charts for beautiful and interactive charting, and the InsightSentry API for streaming live market data via WebSockets.
Prerequisites:
Basic understanding of React and Next.js.
Node.js and npm (or yarn) installed on your system.
An API key from InsightSentry (https://insightsentry.com/).
Project Setup:
Create a Next.js App:
npx create-next-app@latest my-crypto-chart cd my-crypto-chart
Install Dependencies:
npm install lightweight-charts
Code Structure:
We'll create a single component for our chart within the app
directory (or pages
directory if you are not using the app router). Let's name it Chart.tsx
(or Chart.jsx
if you prefer JavaScript).
Chart Component (Chart.tsx
):
"use client";
import { createChart, ChartOptions, DeepPartial, UTCTimestamp } from "lightweight-charts";
import { useCallback, useEffect, useRef } from "react";
// Replace with your actual InsightSentry API key
const wsapikey = "YOUR_INSIGHTSENTRY_API_KEY";
// Helper functions for time formatting
const formatTimeToHHMMSS = (timestamp: UTCTimestamp) => {
const date = new Date(timestamp * 1000); // Convert to milliseconds
return date.toLocaleTimeString('en-US', { hour12: false }); // Convert to 24-hour hh:mm:ss format
};
const formatDateToLocalTime = (timestamp: UTCTimestamp) => {
const date = new Date(timestamp * 1000); // Convert to milliseconds
return date.toLocaleString(); // Convert to local time string
};
// Chart options (customization)
const chartOptions: DeepPartial<ChartOptions> = {
layout: {
textColor: '#1e293b',
background: { color: 'white' },
attributionLogo: false
},
grid: {
vertLines: {
visible: false,
},
horzLines: {
visible: false,
},
},
autoSize: true,
watermark: {
visible: true,
color: "rgba(0, 0, 0, 0.05)",
text: "InsightSentry",
},
timeScale: {
visible: true,
timeVisible: true,
ticksVisible: true,
tickMarkFormatter: formatTimeToHHMMSS,
borderColor: "#D1D5DB",
},
localization: {
timeFormatter: formatDateToLocalTime,
},
rightPriceScale: {
borderColor: "#e2e8f0",
},
crosshair: {
horzLine: {
visible: true,
labelVisible: true,
color: 'rgba(59, 130, 246, 0.5)', // Semi-transparent blue
},
vertLine: {
visible: true,
labelVisible: true,
color: 'rgba(59, 130, 246, 0.5)', // Semi-transparent blue
},
},
};
export default function Chart() {
const chartContainerRef = useRef<HTMLDivElement>(null);
const wsRef = useRef<WebSocket | null>(null);
const chartRef = useRef<any>(null);
const seriesRef = useRef<any>(null);
// WebSocket connection function (memoized with useCallback)
const connectWebSocket = useCallback(() => {
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
console.log("WebSocket already connected.");
return;
}
const ws = new WebSocket('wss://data.insightsentry.com/live');
ws.onopen = () => {
const subMsg = JSON.stringify({
api_key: wsapikey,
subscriptions: [
{ code: 'BINANCE:BTCUSDT', bar_type: 'second', bar_interval: 1, type: 'series' }
]
});
ws.send(subMsg);
};
ws.onerror = () => {
console.error('WebSocket error');
}
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
if (!data.code) {
return
}
if (data.series && seriesRef.current) {
const formattedSeries = data.series.map(
(item: {
time: number
open: number
high: number
low: number
close: number
volume: number
}) => ({
time: item.time,
open: item.open,
high: item.high,
low: item.low,
close: item.close,
volume: item.volume
})
)
for (const item of formattedSeries) {
seriesRef.current.update(item)
}
}
} catch (error) {
console.error(error)
}
}
ws.onclose = () => {
console.log('WebSocket closed');
setTimeout(() => {
connectWebSocket();
}, 2000);
};
wsRef.current = ws;
}, []);
// useEffect for chart creation and WebSocket connection
useEffect(() => {
chartRef.current = createChart(chartContainerRef.current, chartOptions);
seriesRef.current = chartRef.current.addCandlestickSeries({
priceFormat: {
type: "price",
minMove: 0.001
},
upColor: "#10B981",
downColor: "#EF4444",
borderVisible: false,
wickUpColor: "#10B981",
wickDownColor: "#EF4444",
});
seriesRef.current.setData([]);
chartRef.current.timeScale().fitContent();
connectWebSocket();
return () => {
if (wsRef.current) {
wsRef.current.close();
wsRef.current = null;
}
if (chartRef.current) {
chartRef.current.remove();
}
};
}, [connectWebSocket]);
return (
<div ref={chartContainerRef} className="w-full h-[500px]" />
);
}
Explanation:
Imports: Import necessary modules from
lightweight-charts
andreact
.API Key: Replace
YOUR_INSIGHTSENTRY_API_KEY
with your actual API key.Helper Functions:
formatTimeToHHMMSS
andformatDateToLocalTime
format the timestamps for the chart's time scale.Chart Options: The
chartOptions
object configures the appearance and behavior of the chart (colors, gridlines, watermark, crosshair, etc.).Refs:
chartContainerRef
: A ref to the DOM element where the chart will be rendered.wsRef
: A ref to store the WebSocket connection.chartRef
: A ref to the created chart object.seriesRef
: A ref to the candlestick series object.
connectWebSocket
Function:Establishes a WebSocket connection to the InsightSentry API.
Sends a subscription message to receive real-time data for
BINANCE:BTCUSDT
(you can change this to other symbols).Handles incoming data (
onmessage
) and updates the chart's series with the new data points.Implements reconnection logic (
onclose
) to automatically reconnect if the connection drops.
useEffect
Hook:Creates the chart using
createChart
and attaches it to thechartContainerRef
element.Adds a candlestick series to the chart using
addCandlestickSeries
.Sets initial data to an empty array (
[]
).Calls
connectWebSocket
to initiate the WebSocket connection.Cleanup Function: Closes the WebSocket connection and removes the chart when the component unmounts.
JSX: Renders a
div
element with thechartContainerRef
to serve as the container for the chart.
Integrating the Chart Component:
Now, import and use the Chart
component in your main page component (e.g., app/page.tsx
):
import Chart from './Chart';
export default function Home() {
return (
<main>
<h1>Real-Time Crypto Chart</h1>
<Chart />
</main>
);
}
Running the Application:
Start the Next.js development server:
npm run dev
Open your browser and go to http://localhost:3000
(or the port indicated in your console). You should see a beautiful candlestick chart updating in real-time with data from InsightSentry!
Customization:
Chart Options: Explore the extensive options provided by Lightweight Charts to customize the chart's appearance (colors, grid, axes, etc.). Refer to the Lightweight Charts documentation for details.
Data Subscription: Modify the
subscriptions
array in theconnectWebSocket
function to subscribe to different symbols or data types offered by the InsightSentry API.Error Handling: Add more robust error handling to the
onmessage
event handler to gracefully handle potential issues with the data received from the API.UI Enhancements: Add UI elements like a symbol selector, time interval controls, or technical indicators to enhance the user experience.
Conclusion:
This tutorial demonstrated how to build a real-time candlestick chart using Next.js, Lightweight Charts, and the InsightSentry API. You can expand upon this foundation to create sophisticated financial dashboards and data visualization tools. Remember to consult the documentation for Lightweight Charts and InsightSentry to explore their full capabilities. Happy charting!