How to Build Candlestick Data from Tick-by-Tick Market Feed

Nitish Kumar Singh
May 30, 2025Ever wondered how those candlestick charts are made from raw tick data? Here, I’ll show you how to build them step by step — including volume.
We have an array ticks
of market feed for any stock, and we will build an array candles
that can be used to show candlestick charts and volume bars.
Each tick
object has properties as shown below:
{
last_traded_price: number, // in paise
last_traded_timestamp: number, // in UNIX seconds
vol_traded: number // cumulative volume till that tick from market open
// more properties
}
We will build a candle
object that has properties like this:
{
time: number, // in UNIX time
open: number, // in rupees
high: number, // in rupees
low: number, // in rupees
close: number, // in rupees
volume: number
}
We will create a function that takes an array ticks
of market feed, frameSec
for candle size, and a zone
offset to convert the time to that time zone, and returns an array of candles
.
const giveCandles = (ticks, frameSec, zone = 19800)=> {
if (!Array.isArray(ticks) || ticks.length === 0 || frameSec <= 0) return [];
const sorted = ticks
.filter(t => t.last_traded_timestamp > 0 && t.last_traded_price > 0 && t.vol_traded >= 0)
.sort((a, b) => a.last_traded_timestamp - b.last_traded_timestamp);
const candles = [];
let candle = null;
let lastClose = null;
let prevVolTraded = null;
for (const { last_traded_timestamp: tsRaw, last_traded_price: prRaw, vol_traded } of sorted) {
const ts = tsRaw + zone;
const price = prRaw / 100;
const slot = Math.floor(ts / frameSec) * frameSec;
// Calculate volume delta
const volume = prevVolTraded !== null && vol_traded >= prevVolTraded ? vol_traded - prevVolTraded : 0;
prevVolTraded = vol_traded;
// Fill missing candles
if (candle && slot > candle.time + frameSec) {
let gapTime = candle.time + frameSec;
while (gapTime < slot) {
candles.push({
time: gapTime,
open: lastClose,
high: lastClose,
low: lastClose,
close: lastClose,
volume: 0,
});
gapTime += frameSec;
}
}
// Start new candle or update existing
if (!candle || candle.time !== slot) {
candle = {
time: slot,
open: price,
high: price,
low: price,
close: price,
volume: volume,
};
candles.push(candle);
} else {
candle.high = Math.max(candle.high, price);
candle.low = Math.min(candle.low, price);
candle.close = price;
candle.volume += volume;
}
lastClose = candle.close;
}
return candles;
}
We can remove the filtering and sorting code if we're sure that ticks have no bad data and are in sorted order.
I’ve added time shifting because Lightweight Charts don’t support time zones.
I’ve shifted time to match the Kolkata time zone (UTC+5:30
, which is 19800 seconds
).
You can pass any zone value you want.
Just note that shifting time like this can change the date. In my case, it works because the market closes at 15:30 + 5:30 = 21:00
, which doesn’t change the date.
Volume calculation can be simplified like this, but I’ve added extra checks for safety (since volume can drop due to resets, errors, or session changes):
const volume = prevVolTraded !== null ? vol_traded - prevVolTraded : 0;
How we build the candle data mainly depends on calculating the slot
— the start time of the candle for a given time frame:
const slot = Math.floor(ts / frameSec) * frameSec;
Let’s say we have a tick at May 30, 2025 14:45:53 (which is 9:15:53 AM in IST) = 1748596553
and frameSec = 60 (for 1-minute candles)
, then: All ticks that fall in the 9:15 AM
candle will have slot = 1748596500
. We just build candle data by checking this slot
.
I’ve added the code below to fill any missing candle data, so that charts don’t have gaps:
// Fill missing candles
if (candle && slot > candle.time + frameSec) {
let gapTime = candle.time + frameSec;
while (gapTime < slot) {
candles.push({
time: gapTime,
open: lastClose,
high: lastClose,
low: lastClose,
close: lastClose,
volume: 0,
});
gapTime += frameSec;
}
}
Now you know how to build proper candle data from raw tick data. I hope this helped you understand the process better.
If you have any questions or suggestions, feel free to drop a comment. Happy coding!