142 lines
4.2 KiB
JavaScript
Executable File
142 lines
4.2 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 MFI(options = {}) {
|
|
if (!new.target) throw new Error('ERROR: MFI() 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 = [];
|
|
};
|
|
|
|
MFI.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;
|
|
|
|
let convertToResultItem = (typicalPrice, rawMoneyFlow, positiveMoneyFlow, negativMoneyFlow, periodPositiveMoneyFlow = 0, periodNegativeMoneyFlow = 0) => {
|
|
let moneyFlowRatio = 0;
|
|
let moneyFlowIndex = 0;
|
|
|
|
if (periodPositiveMoneyFlow && periodNegativeMoneyFlow) {
|
|
moneyFlowRatio = periodPositiveMoneyFlow / periodNegativeMoneyFlow;
|
|
moneyFlowIndex = 100 - (100 / (1 + moneyFlowRatio));
|
|
|
|
//alternative
|
|
//moneyFlowIndex = 100 * ( periodPositiveMoneyFlow / ( periodPositiveMoneyFlow + periodNegativeMoneyFlow ) );
|
|
}
|
|
|
|
return {
|
|
typicalPrice: NumberUtil.roundTo(typicalPrice, 2),
|
|
rawMoneyFlow,
|
|
positiveMoneyFlow,
|
|
negativMoneyFlow,
|
|
moneyFlowRatio: NumberUtil.roundTo(moneyFlowRatio, 2),
|
|
moneyFlowIndex: NumberUtil.roundTo(moneyFlowIndex, 2),
|
|
periodPositiveMoneyFlow,
|
|
periodNegativeMoneyFlow,
|
|
};
|
|
};
|
|
|
|
if (!NumberUtil.isNumeric(startIndex)) startIndex = 0;
|
|
if (!NumberUtil.isNumeric(endIndex)) endIndex = this._collection.length - 1;
|
|
|
|
let prevTypicalPrice;
|
|
|
|
for (let i = startIndex; i <= endIndex; i++) {
|
|
let item = this._collection[i];
|
|
let typicalPrice = (item.high + item.low + item.close) / 3;
|
|
|
|
let rawMoneyFlow = Math.round(typicalPrice * item.volume);
|
|
|
|
let positiveMoneyFlow = 0;
|
|
let negativMoneyFlow = 0;
|
|
|
|
if (i > startIndex) {
|
|
if (typicalPrice > prevTypicalPrice) positiveMoneyFlow = rawMoneyFlow;
|
|
if (typicalPrice < prevTypicalPrice) negativMoneyFlow = rawMoneyFlow;
|
|
}
|
|
|
|
let periodPositiveMoneyFlow = 0;
|
|
let periodNegativeMoneyFlow = 0;
|
|
if (i >= startIndex + periods) {
|
|
let items = results.slice(results.length - periods + 1);
|
|
let { sumPositiveMoneyFlow, sumNegativeMoneyFlow } = this._calcPeriodMoneyFlow(items);
|
|
periodPositiveMoneyFlow = sumPositiveMoneyFlow + positiveMoneyFlow;
|
|
periodNegativeMoneyFlow = sumNegativeMoneyFlow + negativMoneyFlow;
|
|
}
|
|
|
|
let resultItem = convertToResultItem(typicalPrice, rawMoneyFlow, positiveMoneyFlow, negativMoneyFlow, periodPositiveMoneyFlow, periodNegativeMoneyFlow);
|
|
resultItem.high = item.high;
|
|
resultItem.low = item.low;
|
|
resultItem.close = item.close;
|
|
resultItem.volume = item.volume;
|
|
|
|
results.push(resultItem);
|
|
|
|
if (lazyEvaluation) {
|
|
if (sliceOffset) {
|
|
if (i >= startIndex + periods) yield resultItem;
|
|
}
|
|
else yield resultItem;
|
|
}
|
|
|
|
prevTypicalPrice = typicalPrice;
|
|
}
|
|
|
|
return (sliceOffset) ? results.slice(startIndex + periods) : results;
|
|
},
|
|
|
|
_calcPeriodMoneyFlow(collection) {
|
|
let sumPositiveMoneyFlow = 0;
|
|
let sumNegativeMoneyFlow = 0;
|
|
for (let i = 0; i < collection.length; i++) {
|
|
let item = collection[i];
|
|
sumPositiveMoneyFlow += item.positiveMoneyFlow;
|
|
sumNegativeMoneyFlow += item.negativMoneyFlow;
|
|
}
|
|
return { sumPositiveMoneyFlow, sumNegativeMoneyFlow };
|
|
},
|
|
|
|
};
|
|
|
|
export default MFI;
|