ohlc-aggregator
Version:
Aggregates ohlcv values into coarse grain intervals
183 lines (158 loc) • 4.67 kB
JavaScript
const monthInSeconds = 30 * 24 * 3600;
var moment = require("moment");
/**
* If curIndex > firstLeftIndex, returns firstLeftIndex
* otherwise, Decrements curIndex by intervalRatio
* @param {Number} curIndex
* @param {Array} array
* @param {Number} intervalRatio
* @param {Number} intervalInSeconds
* @param {Number} firstLeftIndex
* Set arrayTimeCoefficient to a value other than 1 if the time values in the input data is not in seconds
* @param {Number} arrayTimeCoefficient
* @returns {Number}
*/
function getNextLowerBound(
curIndex,
intervalRatio,
intervalInSeconds,
firstLeftIndex
) {
// If not month
if (intervalInSeconds !== monthInSeconds) {
if (curIndex > firstLeftIndex) {
return firstLeftIndex;
} else {
return curIndex - intervalRatio;
}
}
throw "intervalInSeconds cannot be a month. It should only be days, hours, or minutes.";
}
//-----------------------------------------------------------------------------
/**
* Aggregates to higher intervals
* @param {Array} array : input ohlcv data
* @param {Number} intervalRatio: ratio of the larger interval to the lower interval
* @param {Number} intervalInSeconds: the larger interval in seconds
* @param {Number} arrayTimeCoefficient: set this to 1000 if the time values in array are in second
*/
function aggregate(
array,
intervalRatio,
intervalInSeconds,
arrayTimeCoefficient = 1
) {
let result = [];
if (!array || !Array.isArray(array)) {
return null;
}
if (array.length === 0) {
return [];
}
if (array.length === 1) {
return array;
}
let originalDurationInMS = (intervalInSeconds * 1000) / intervalRatio;
const rightIndex = array.length - 1;
let rightIndexTime = array[rightIndex].time * arrayTimeCoefficient;
// If the interval is days
if (originalDurationInMS === 60 * 60 * 1000) {
let startOfDay = moment(
array[rightIndex].time * arrayTimeCoefficient
).startOf("day");
rightIndexTime = array[rightIndex].time * arrayTimeCoefficient - startOfDay;
}
// Offset of the rightIndex from firstLeftIndex
let rightIndexOffset = rightIndexTime % (intervalInSeconds * 1000);
// Check to see the time values are correct
if (
Math.round(rightIndexOffset / originalDurationInMS) !=
rightIndexOffset / originalDurationInMS
) {
console.log(
`Warning [ohlc-aggregator]: the last element time ${rightIndexTime} is not divisible by ${originalDurationInMS}`
);
}
let firstLeftIndex = rightIndex - rightIndexOffset / originalDurationInMS;
if (firstLeftIndex > 0 && firstLeftIndex < array.length) {
}
for (
let index = rightIndex;
index > -intervalRatio;
index = getNextLowerBound(
index,
intervalRatio,
intervalInSeconds,
firstLeftIndex
)
) {
let nextLowerBound = index;
if (rightIndexOffset != 0) {
nextLowerBound = getNextLowerBound(
index,
intervalRatio,
intervalInSeconds,
firstLeftIndex
);
index = nextLowerBound;
rightIndexOffset = 0;
}
let lb = Math.max(0, nextLowerBound);
let ub = nextLowerBound + intervalRatio;
let intervalSlice = array.slice(lb, ub);
let open = intervalSlice[0].open || 0;
let high = intervalSlice[0].high || 0;
let low = intervalSlice[0].low || Number.MAX_SAFE_INTEGER;
let close = intervalSlice[intervalSlice.length - 1].close;
let time = intervalSlice[0].time;
// Adjust the time for incomplete candles from left
if (nextLowerBound < 0) {
time += (nextLowerBound * originalDurationInMS) / arrayTimeCoefficient;
}
let volumefrom = 0;
let volumeto = 0;
let volume = 0;
// Find low, high and volume
intervalSlice.forEach(e => {
if (low > e.low) {
low = e.low;
}
if (high < e.high) {
high = e.high;
}
volumefrom += e.volumefrom || 0;
volumeto += e.volumeto || 0;
volume += e.volume || 0;
});
let value = {
open: open,
close: close,
low: low,
high: high,
volume: volume,
time: time
};
if (Object.keys(array[0]).includes("volumefrom")) {
value["volumefrom"] = volumefrom;
}
if (Object.keys(array[0]).includes("volumeto")) {
value["volumeto"] = volumeto;
}
result.push(value);
}
return result.reverse();
}
//-----------------------------------------------------------------------------
module.exports = function(
ohlcv,
intervalRatio,
intervalInSeconds,
arrayTimeCoefficient
) {
return aggregate(
ohlcv,
intervalRatio,
intervalInSeconds,
arrayTimeCoefficient
);
};