62 lines
2.7 KiB
JavaScript
62 lines
2.7 KiB
JavaScript
/**
|
|
* Aggregates fine-grained candle data into a larger timeframe.
|
|
* For example, it can convert 1-minute candles into 5-minute candles.
|
|
*
|
|
* @param {Array<Object>} data - An array of candle objects, sorted by time.
|
|
* Each object must have { time, open, high, low, close }.
|
|
* @param {number} intervalMinutes - The desired new candle interval in minutes (e.g., 5 for 5m).
|
|
* @returns {Array<Object>} A new array of aggregated candle objects.
|
|
*/
|
|
function aggregateCandles(data, intervalMinutes) {
|
|
if (!data || data.length === 0 || !intervalMinutes || intervalMinutes < 1) {
|
|
return [];
|
|
}
|
|
|
|
const intervalSeconds = intervalMinutes * 60;
|
|
const aggregated = [];
|
|
let currentAggCandle = null;
|
|
|
|
data.forEach(candle => {
|
|
// Validate candle data
|
|
if (!candle || !candle.time ||
|
|
isNaN(candle.open) || isNaN(candle.high) ||
|
|
isNaN(candle.low) || isNaN(candle.close) ||
|
|
candle.open <= 0 || candle.high <= 0 ||
|
|
candle.low <= 0 || candle.close <= 0) {
|
|
console.warn('Skipping invalid candle during aggregation:', candle);
|
|
return; // Skip this candle
|
|
}
|
|
|
|
// Calculate the timestamp for the start of the interval bucket
|
|
// Properly align to interval boundaries (e.g., 5-min intervals start at :00, :05, :10, etc.)
|
|
const bucketTimestamp = Math.floor(candle.time / intervalSeconds) * intervalSeconds;
|
|
|
|
if (!currentAggCandle || bucketTimestamp !== currentAggCandle.time) {
|
|
// If a previous aggregated candle exists, push it to the results
|
|
if (currentAggCandle) {
|
|
aggregated.push(currentAggCandle);
|
|
}
|
|
// Start a new aggregated candle
|
|
currentAggCandle = {
|
|
time: bucketTimestamp,
|
|
open: candle.open,
|
|
high: candle.high,
|
|
low: candle.low,
|
|
close: candle.close,
|
|
};
|
|
} else {
|
|
// This candle belongs to the current aggregated candle, so update it
|
|
currentAggCandle.high = Math.max(currentAggCandle.high, candle.high);
|
|
currentAggCandle.low = Math.min(currentAggCandle.low, candle.low);
|
|
currentAggCandle.close = candle.close; // The close is always the latest one
|
|
}
|
|
});
|
|
|
|
// Add the last aggregated candle if it exists
|
|
if (currentAggCandle) {
|
|
aggregated.push(currentAggCandle);
|
|
}
|
|
|
|
return aggregated;
|
|
}
|
|
|