stock-indicators/lib/indicator/relative-strength-index.js
2025-03-31 11:20:04 +02:00

141 lines
4.3 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=14]
* @param {number} [options.startIndex]
* @param {number} [options.endIndex}]
* @param {boolean} [options.sliceOffset=false]
* @param {boolean} [options.lazyEvaluation=true]
* @param {number} [option.maxTickDuration=10]
*/
function RSI(options = {}) {
if (!new.target) throw new Error('ERROR: RSI() must be called with new');
this._options = {
startIndex: null,
endIndex: null,
periods: 14,
sliceOffset: false,
lazyEvaluation: true,
maxTickDuration: 10,
};
let m = [SetOptionsMixin, ValidateMixin, HandleGeneratorMixin];
Object.assign(this, ...m);
this.setOptions(options);
this._collection = [];
};
RSI.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 { periods, startIndex, endIndex, sliceOffset, lazyEvaluation } = this._options;
let convertToResultItem = (price, gain = null, loss = null, avg_gain = null, avg_loss = null) => {
let rs = null;
let rsi = null;
if (NumberUtil.isNumeric(avg_gain) && NumberUtil.isNumeric(avg_loss)) {
rs = avg_gain / avg_loss;
rsi = 100 - (100 / (1 + rs));
}
return {
price,
gain: NumberUtil.isNumeric(gain) ? NumberUtil.roundTo(gain, 2) : null,
loss: NumberUtil.isNumeric(loss) ? NumberUtil.roundTo(loss, 2) : null,
avg_gain: NumberUtil.isNumeric(avg_gain) ? NumberUtil.roundTo(avg_gain, 2) : null,
avg_loss: NumberUtil.isNumeric(avg_loss) ? NumberUtil.roundTo(avg_loss, 2) : null,
rs: NumberUtil.isNumeric(rs) ? NumberUtil.roundTo(rs, 2) : null,
rsi: NumberUtil.isNumeric(rsi) ? NumberUtil.roundTo(rsi, 2) : null,
};
};
if (!NumberUtil.isNumeric(startIndex)) startIndex = 0;
if (!NumberUtil.isNumeric(endIndex)) endIndex = this._collection.length - 1;
let sumPeriodGain = 0;
let sumPeriodLoss = 0;
for (let i = startIndex, endPeriodIndex = startIndex + periods - 1; i <= endPeriodIndex; i++) {
let resultItem = null;
if (i == startIndex) {
resultItem = convertToResultItem(this._collection[i], 0, 0);
}
else {
let { gain, loss } = this._calcGainLoss(i);
sumPeriodGain += gain;
sumPeriodLoss += loss;
resultItem = convertToResultItem(this._collection[i], gain, loss);
}
if (!sliceOffset) {
results.push(resultItem);
if (lazyEvaluation) yield resultItem;
}
}
let ag = sumPeriodGain / periods;
let al = sumPeriodLoss / periods;
let { gain, loss } = this._calcGainLoss(startIndex + periods);
let resultItem = convertToResultItem(this._collection[startIndex + periods], gain, loss, ag, al);
results.push(resultItem);
if (lazyEvaluation) yield resultItem;
for (let i = startIndex + periods + 1; i <= endIndex; i++) {
let { gain, loss } = this._calcGainLoss(i);
let prev_avg_gain = results[results.length - 1].avg_gain;
let prev_avg_loss = results[results.length - 1].avg_loss;
let ag = ((prev_avg_gain * (periods - 1)) + gain) / periods;
let al = ((prev_avg_loss * (periods - 1)) + loss) / periods;
let resultItem = convertToResultItem(this._collection[i], gain, loss, ag, al);
results.push(resultItem);
if (lazyEvaluation) yield resultItem;
}
if (!lazyEvaluation) {
return results;
}
},
_calcGainLoss(collectionIndex) {
let result = { gain: 0, loss: 0 };
if (collectionIndex >= 0 && collectionIndex < this._collection.length) {
if (collectionIndex > 0) {
let diff = this._collection[collectionIndex] - this._collection[collectionIndex - 1];
if (diff > 0) result.gain = diff;
else result.loss = Math.abs(diff);
}
}
else throw new Error('ERROR: index is outside the collection range');
return result;
},
}
export default RSI;