stock-indicators/lib/indicator/exponential-moving-average.js
2025-03-31 11:20:04 +02:00

118 lines
3.5 KiB
JavaScript
Executable File

'use strict';
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=12]
* @param {number} [options.startIndex=0]
* @param {number} [options.endIndex}]
* @param {boolean} [options.emaResultsOnly=false]
* @param {boolean} [option.startWithFirst=false]
* @param {boolean} [options.sliceOffset=false]
* @param {boolean} [options.lazyEvaluation=true]
* @param {number} [option.maxTickDuration=10]
*/
function EMA(options = {}) {
if (!new.target) throw new Error('ERROR: EMA() must be called with new');
this._options = {
startIndex: 0,
endIndex: null,
periods: 12,
emaResultsOnly: false,
startWithFirst: false,
sliceOffset: false,
lazyEvaluation: true,
maxTickDuration: 10,
};
let m = [SetOptionsMixin, ValidateMixin, HandleGeneratorMixin];
Object.assign(this, ...m);
this.setOptions(options);
this._collection = [];
}
EMA.prototype = {
setValues(values) {
if (!Array.isArray(values)) {
throw new Error('ERROR: values param is not an array');
}
this._collection = values;
},
clear() {
this._collection = [];
},
calculate() {
this._validate(this._collection, this._options);
return this._handleGenerator(this._compute());
},
_compute: function* () {
let results = [];
let firstVal = 0;
let { startIndex, endIndex, periods, startWithFirst, lazyEvaluation, floating } = this._options;
let convertToResultItem = (price, ema) => {
return { price, ema: NumberUtil.roundTo(ema, 2) };
}
if (!NumberUtil.isNumeric(startIndex)) startIndex = 0;
if (!NumberUtil.isNumeric(endIndex)) endIndex = this._collection.length - 1;
if (startWithFirst) {
firstVal = this._collection[startIndex];
}
else {
firstVal = this._avg(startIndex, startIndex + periods - 1);
if (!this._options.sliceOffset) {
for (let i = startIndex; i < startIndex + periods - 1; i++) {
let resultItem = convertToResultItem(this._collection[i], 0);
results.push(resultItem);
if (lazyEvaluation) yield resultItem;
}
}
startIndex = startIndex + periods - 1;
}
let resultItem = convertToResultItem(this._collection[startIndex], firstVal);
results.push(resultItem);
if (lazyEvaluation) yield resultItem;
let z = 1;
let multiplier = 2 / (periods + 1);
for (let i = startIndex + 1; i <= endIndex; i++) {
let ema = (this._collection[i] * multiplier) + (results[results.length - 1].ema * (1 - multiplier));
let resultItem = convertToResultItem(this._collection[i], ema);
results.push(resultItem);
if (lazyEvaluation) yield resultItem;
z++;
}
if (!lazyEvaluation) {
if (this._options.emaResultsOnly) return _.pluck(results, 'ema');
return results;
}
},
_avg(indexStart, indexEnd) {
if (!_.isNumber(indexStart) || (indexStart >= indexEnd)) throw new Error('Error: Invalid indexStart argument');
if (!_.isNumber(indexEnd)) throw new Error('Error: Invalid indexEnd argument');
let coll_len = this._collection.length;
if (coll_len - 1 < indexStart || coll_len - 1 < indexEnd) throw new Error('Error: Invalid collection length');
let n = 0;
for (let i = indexStart; i <= indexEnd; i++) n += this._collection[i];
return (n / (indexEnd - indexStart + 1));
}
}
export default EMA;