fast-technical-indicators
Version:
High-performance technical indicators with zero dependencies - compatible with technicalindicators package
94 lines (93 loc) • 3.74 kB
JavaScript
// Helper function to check if two ranges overlap (exactly like technicalindicators)
function priceFallsBetweenBarRange(low, high, low1, high1) {
return (low <= low1 && high >= low1) || (low1 <= low && high1 >= low);
}
export function volumeprofile(input) {
const { open, high, low, close, volume, noOfBars = 14 } = input;
if (!open || !high || !low || !close || !volume ||
open.length !== high.length ||
high.length !== low.length ||
low.length !== close.length ||
close.length !== volume.length ||
close.length === 0) {
return [];
}
const result = [];
// Find price range across all OHLC data (exactly like reference)
const max = Math.max(...high, ...low, ...close, ...open);
const min = Math.min(...high, ...low, ...close, ...open);
const barRange = (max - min) / noOfBars;
let lastEnd = min;
// Create each volume profile bar
for (let i = 0; i < noOfBars; i++) {
const rangeStart = lastEnd;
const rangeEnd = rangeStart + barRange;
lastEnd = rangeEnd;
let bullishVolume = 0;
let bearishVolume = 0;
let totalVolume = 0;
// Check each price bar (candle) against this volume profile bar
for (let priceBar = 0; priceBar < high.length; priceBar++) {
const priceBarStart = low[priceBar];
const priceBarEnd = high[priceBar];
const priceBarOpen = open[priceBar];
const priceBarClose = close[priceBar];
const priceBarVolume = volume[priceBar];
// Check if this candle's range overlaps with current volume profile bar range
if (priceFallsBetweenBarRange(rangeStart, rangeEnd, priceBarStart, priceBarEnd)) {
totalVolume = totalVolume + priceBarVolume;
// Note: Reference uses open > close for bearish (opposite of what I initially thought)
if (priceBarOpen > priceBarClose) {
bearishVolume = bearishVolume + priceBarVolume;
}
else {
bullishVolume = bullishVolume + priceBarVolume;
}
}
}
// Always include all bars (even with 0 volume) - but we'll filter them out later if needed
result.push({
rangeStart,
rangeEnd,
bullishVolume,
bearishVolume,
totalVolume
});
}
return result;
}
export class VolumeProfile {
constructor(input) {
this.openValues = [];
this.highValues = [];
this.lowValues = [];
this.closeValues = [];
this.volumeValues = [];
this.noOfBars = input.noOfBars || 14;
if (input.open?.length && input.high?.length && input.low?.length &&
input.close?.length && input.volume?.length) {
const minLength = Math.min(input.open.length, input.high.length, input.low.length, input.close.length, input.volume.length);
for (let i = 0; i < minLength; i++) {
this.nextValue(input.open[i], input.high[i], input.low[i], input.close[i], input.volume[i]);
}
}
}
nextValue(open, high, low, close, volume) {
this.openValues.push(open);
this.highValues.push(high);
this.lowValues.push(low);
this.closeValues.push(close);
this.volumeValues.push(volume);
}
getResult() {
return volumeprofile({
open: this.openValues,
high: this.highValues,
low: this.lowValues,
close: this.closeValues,
volume: this.volumeValues,
noOfBars: this.noOfBars
});
}
}
VolumeProfile.calculate = volumeprofile;