@gooddata/react-components
Version:
GoodData.UI - A powerful JavaScript library for building analytical applications
88 lines (72 loc) • 2.72 kB
text/typescript
// (C) 2019 GoodData Corporation
import isFunction = require("lodash/isFunction");
import sumBy = require("lodash/sumBy");
import * as differenceInDaysModule from "date-fns/differenceInDays";
// jest is being difficult with normal imports for some reason
// enabling esModuleInterop would probably solve this issue, but it is not feasible now
const differenceInDays = isFunction(differenceInDaysModule)
? differenceInDaysModule
: differenceInDaysModule.default;
type NumericFunction = (x: number) => number;
export interface IXirrInput {
amount: number;
when: Date;
}
const newtonRaphson = (fun: NumericFunction, derivative: NumericFunction, guess: number): number => {
const precision = 4;
const errorLimit = Math.pow(10, -1 * precision);
const iterationLimit = 100;
let previousValue = 0;
let iteration = 0;
let result = guess;
do {
previousValue = result;
result = previousValue - fun(result) / derivative(result);
if (++iteration >= iterationLimit) {
return NaN;
}
} while (Math.abs(result - previousValue) > errorLimit);
return result;
};
/**
* Calculates the XIRR value for the (amount, when) pairs.
* @see https://en.wikipedia.org/wiki/Internal_rate_of_return#Exact_dates_of_cash_flows for mathematical background.
* @param transactions
* @param guess
*/
export const calculateXirr = (transactions: IXirrInput[], guess = 0.1) => {
// convert any date to a fractional year difference to allow non-uniform cash-flow distribution (the X in XIRR)
const startDate = transactions[0].when;
const data = transactions.map(t => ({
C_n: t.amount,
t_n: differenceInDays(t.when, startDate) / 365,
}));
/*
NPV is defined as
N
====
\ C_n
NPV = \ ----------
/ t_n
/ (1 + r)
====
n = 0
where n is a period and C_n is a cash-flow for the corresponding period.
IRR is defined as a real solution for r in NPV = 0
*/
const npv: NumericFunction = r => sumBy(data, ({ t_n, C_n }) => C_n / Math.pow(1 + r, t_n));
/*
We use Newton Raphson method to find the real root of NPV = 0, so we need its derivative:
N
====
dNPV \ C_n . t_n
---- = - \ ----------------
dn / (t_n + 1)
/ (1 + r)
====
n = 0
*/
const npvDerivative: NumericFunction = r =>
-1 * sumBy(data, ({ t_n, C_n }) => (t_n * C_n) / Math.pow(1 + r, t_n + 1));
return newtonRaphson(npv, npvDerivative, guess);
};