stock-indicators/lib/indicator/bollinger-bands.js
2025-03-31 11:20:04 +02:00

98 lines
2.7 KiB
JavaScript
Executable File

'use strict';
import MathUtil from './../utils/math.js';
import SMA from './simple-moving-average.js';
import _ from 'underscore';
import NumberUtil from '../utils/number.js';
import ValidateMixin from '../mixin/validate.js';
import SetOptionsMixin from '../mixin/set-options.js';
import HandleGeneratorMixin from '../mixin/handle-generator.js';
/**
* @param {object} [options]
* @param {number} [optons.periods=20]
* @param {number} [options.startIndex]
* @param {number} [options.endIndex}]
* @param {boolean} [options.sliceOffset=false]
* @param {boolean} [options.lazyEvaluation=true]
* @param {number} [option.maxTickDuration=10]
*/
function BB(options = {}) {
if (!new.target) throw new Error('ERROR: BB() must be called with new');
this._options = {
periods: 20,
startIndex: null,
endIndex: null,
sliceOffset: false,
lazyEvaluation: true,
maxTickDuration: 10,
}
let m = [SetOptionsMixin, ValidateMixin, HandleGeneratorMixin];
Object.assign(this, ...m);
this.setOptions(options);
this._collection = [];
}
BB.prototype = {
setValues(values) {
if (!Array.isArray(values)) {
throw new Error('ERROR: values param is not an array');
}
this._collection = values;
},
calculate() {
this._validate(this._collection, this._options);
return this._handleGenerator(this._compute());
},
_compute: function* () {
let results = [];
let { periods, startIndex, endIndex, sliceOffset, lazyEvaluation } = this._options;
if (_.isNull(startIndex)) startIndex = 0;
if (_.isNull(endIndex)) endIndex = this._collection.length - 1 - periods;
let idx = startIndex;
let endRangeIndex = this._collection.length - periods;
if (!sliceOffset) {
let i = idx;
while (i < startIndex + periods - 1) {
let resultItem = {
upper: 0, middle: 0, lower: 0, price: this._collection[i],
};
results.push(resultItem);
if (lazyEvaluation) yield resultItem;
i++;
}
}
while (idx <= endRangeIndex) {
let sma = new SMA({ periods, lazyEvaluation: false });
let items = this._collection.slice(idx, idx + periods);
sma.setValues(items);
let r = sma.calculate();
let resultItem = { upper: 0, middle: 0, lower: 0, price: this._collection[idx + periods - 1] };
let stdDev = NumberUtil.roundTo(MathUtil.standardDeviation(items, "population"), 2);
r.forEach((item) => {
resultItem.upper = NumberUtil.roundTo(item.sma + (2 * stdDev), 2);
resultItem.middle = item.sma;
resultItem.lower = NumberUtil.roundTo(item.sma - (2 * stdDev), 2);
});
results.push(resultItem);
if (lazyEvaluation) yield resultItem;
idx++;
}
return results;
}
}
export default BB;