ntopng/http_src/utilities/indicators/binary-indicators.js
2025-05-22 13:09:57 +02:00

241 lines
6.4 KiB
JavaScript

/* Adapted from https://github.com/binary-com/binary-indicators */
'use strict';
function intoSequence(n) {
if ((Array.prototype.from) && (Array.prototype.keys)) {
// Not compatible with IE9
return Array.from(Array(n).keys());
} else {
const arr = Array(n);
for (let i=0; i<n; i++) {
arr[i] = i;
}
return (arr);
}
}
const _math = {};
_math.takeField = function takeField(arr, field) {
return arr.map(function(x) {
return field ? x[field] : x;
});
};
_math.takeLast = function takeLast(arr, n, field) {
return _math.takeField(arr.slice(n > arr.length ? 0 : arr.length - n, arr.length), field);
};
_math.sum = function sum(data) {
return data.reduce(function(acc, x) {
return acc + x;
});
};
_math.weightingMultiplier = function weightingMultiplier(periods) {
return 2 / (periods + 1);
};
_math.mean = function mean(data) {
return data.reduce(function(a, b) {
return a + b;
}) / data.length;
};
_math.stddev = function stddev(data) {
const dataMean = _math.mean(data);
const sqDiff = data.map(function(n) {
return Math.pow(n - dataMean, 2);
});
const avgSqDiff = _math.mean(sqDiff);
return Math.sqrt(avgSqDiff);
};
function bollingerBands(data, config) {
const _config$periods = config.periods;
const periods = _config$periods === undefined ? 20 : _config$periods;
const field = config.field;
const _config$stdDevUp = config.stdDevUp;
const stdDevUp = _config$stdDevUp === undefined ? 2 : _config$stdDevUp;
const _config$stdDevDown = config.stdDevDown;
const stdDevDown = _config$stdDevDown === undefined ? 2 : _config$stdDevDown;
const _config$pipSize = config.pipSize;
const pipSize = _config$pipSize === undefined ? 2 : _config$pipSize;
const vals = (0, _math.takeLast)(data, periods, field);
const middle = (simpleMovingAverage)(vals, {periods: periods});
const stdDev = (0, _math.stddev)(vals);
const upper = middle + stdDev * stdDevUp;
const lower = middle - stdDev * stdDevDown;
return [+middle.toFixed(pipSize), +upper.toFixed(pipSize), +lower.toFixed(pipSize)];
};
function bollingerBandsArray(data, config) {
const periods = config.periods;
return (intoSequence)(data.length - periods + 1).map(function(x, i) {
return bollingerBands(data.slice(i, i + periods), config);
});
};
function ema(vals, periods) {
if (vals.length === 1) {
return vals[0];
}
const prev = ema(vals.slice(0, vals.length - 1), periods);
return (vals.slice(-1)[0] - prev) * (0, _math.weightingMultiplier)(periods) + prev;
};
function exponentialMovingAverage(data, config) {
const periods = config.periods;
const field = config.field;
if (data.length < periods) {
throw new Error('Periods longer than data length');
}
const vals = (0, _math.takeLast)(data, periods, field);
return ema(vals, periods);
};
function exponentialMovingAverageArray(data, config) {
const periods = config.periods;
const _config$pipSize = config.pipSize;
const pipSize = _config$pipSize === undefined ? 2 : _config$pipSize;
return (intoSequence)(data.length - periods + 1).map(function(x, i) {
return +exponentialMovingAverage(data.slice(i, i + periods), config).toFixed(pipSize);
});
};
function calcGain(q1, q2) {
return q2 > q1 ? q2 - q1 : 0;
};
function calcLoss(q1, q2) {
return q2 < q1 ? q1 - q2 : 0;
};
function calcFirstAvgDiff(vals, comp, periods) {
let prev = void 0;
return vals.reduce(function(r, q, i) {
if (i === 1) {
prev = r;
}
const diff = comp(prev, q);
prev = q;
return diff + (i === 1 ? 0 : r);
}) / periods;
};
function calcSecondAvgDiff(vals, comp, periods, initAvg) {
let prev = void 0;
if (vals.length === 1) {
// There is no data to calc avg
return initAvg;
}
return vals.reduce(function(r, q, i) {
if (i === 1) {
prev = r;
}
const diff = comp(prev, q);
prev = q;
const prevAvg = i === 1 ? initAvg : r;
return (prevAvg * (periods - 1) + diff) / periods;
});
};
function relativeStrengthIndex(data, config) {
const memoizedDiff = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
const periods = config.periods;
const field = config.field;
if (data.length < periods) {
throw new Error('Periods longer than data length');
}
if (data.length === periods) {
return 0;
}
const vals = (0, _math.takeField)(data.slice(0, periods + 1), field);
let restSeq = void 0;
let initAvgGain = void 0;
let initAvgLoss = void 0;
if (memoizedDiff && 'gain' in memoizedDiff) {
restSeq = (0, _math.takeField)(data.slice(-2), field);
initAvgGain = memoizedDiff.gain;
initAvgLoss = memoizedDiff.loss;
} else {
// include last element from above to calc diff
restSeq = (0, _math.takeField)(data.slice(periods, data.length), field);
initAvgGain = calcFirstAvgDiff(vals, calcGain, periods);
initAvgLoss = calcFirstAvgDiff(vals, calcLoss, periods);
}
const avgGain = calcSecondAvgDiff(restSeq, calcGain, periods, initAvgGain);
const avgLoss = calcSecondAvgDiff(restSeq, calcLoss, periods, initAvgLoss);
if (memoizedDiff) {
memoizedDiff.gain = avgGain;
memoizedDiff.loss = avgLoss;
}
// FIX: avgGain == 0 and avgLoss == 0 -> RSI = 50
/*
if (avgGain === 0) {
return 0;
} else if (avgLoss === 0) {
return 100;
}
*/
const RS = (avgGain+1) / (avgLoss+1);
return 100 - 100 / (1 + RS);
};
function relativeStrengthIndexArray(data, config) {
const periods = config.periods;
const _config$pipSize = config.pipSize;
const pipSize = _config$pipSize === undefined ? 2 : _config$pipSize;
const memoizedDiff = {};
return (intoSequence)(data.length - periods).map(function(x, i) {
return +relativeStrengthIndex(data.slice(0, i + periods + 1), config, memoizedDiff).toFixed(pipSize);
});
};
function simpleMovingAverage(data, config) {
const periods = config.periods;
const field = config.field;
if (data.length < periods) {
throw new Error('Periods longer than data length');
}
const vals = (0, _math.takeLast)(data, periods, field);
return (_math.sum)(vals) / periods;
};
function simpleMovingAverageArray(data, config) {
const periods = config.periods;
const _config$pipSize = config.pipSize;
const pipSize = _config$pipSize === undefined ? 2 : _config$pipSize;
return (intoSequence)(data.length - periods + 1).map(function(x, i) {
return +simpleMovingAverage(data.slice(i, i + periods), config).toFixed(pipSize);
});
};