@travishorn/financejs 1.0.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -48
- package/package.json +5 -4
- package/src/cumipmt.js +58 -0
- package/src/cumprinc.js +62 -0
- package/src/db.js +60 -0
- package/src/ddb.js +49 -0
- package/src/effect.js +41 -0
- package/src/fv.js +32 -9
- package/src/index.js +16 -5
- package/src/ipmt.js +31 -10
- package/src/irr.js +45 -8
- package/src/mirr.js +59 -0
- package/src/nominal.js +36 -0
- package/src/normalizeZero.js +22 -0
- package/src/nper.js +23 -9
- package/src/npv.js +45 -9
- package/src/pmt.js +30 -9
- package/src/ppmt.js +27 -8
- package/src/pv.js +47 -10
- package/src/rate.js +62 -18
- package/src/sln.js +16 -0
- package/src/syd.js +22 -0
- package/src/xirr.js +137 -0
- package/src/xnpv.js +72 -0
package/src/sln.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the straight-line depreciation of an asset for one period.
|
|
3
|
+
*
|
|
4
|
+
* @param {number} cost - The initial cost of the asset.
|
|
5
|
+
* @param {number} salvage - The value at the end of the depreciation (sometimes
|
|
6
|
+
* called the salvage value of the asset).
|
|
7
|
+
* @param {number} life - The number of periods over which the asset is
|
|
8
|
+
* depreciated (sometimes called the useful life of the asset).
|
|
9
|
+
* @returns {number} the straight-line depreciation
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* sln(30000, 7500, 10); // 2250
|
|
13
|
+
*/
|
|
14
|
+
export function sln(cost, salvage, life) {
|
|
15
|
+
return (cost - salvage) / life;
|
|
16
|
+
}
|
package/src/syd.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the sum-of-years' digits depreciation of an asset for a specified
|
|
3
|
+
* period.
|
|
4
|
+
*
|
|
5
|
+
* Remarks:
|
|
6
|
+
* - The calculation is `((cost - salvage) * (life - per + 1) * 2) / (life)(life
|
|
7
|
+
* + 1)`.
|
|
8
|
+
*
|
|
9
|
+
* @param {number} cost - The initial cost of the asset.
|
|
10
|
+
* @param {number} salvage - The value at the end of the depreciation (sometimes
|
|
11
|
+
* called the salvage value of the asset).
|
|
12
|
+
* @param {number} life - The number of periods over which the asset is
|
|
13
|
+
* depreciated (sometimes called the useful life of the asset).
|
|
14
|
+
* @param {number} per - The period and must use the same units as `life`.
|
|
15
|
+
* @returns {number} the straight-line depreciation
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* syd(30000, 7500, 10, 1); // 4090.91
|
|
19
|
+
*/
|
|
20
|
+
export function syd(cost, salvage, life, per) {
|
|
21
|
+
return ((cost - salvage) * (life - per + 1) * 2) / (life * (life + 1));
|
|
22
|
+
}
|
package/src/xirr.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { xnpv } from "./xnpv.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calculates the internal rate of return for a schedule of cash flows that is
|
|
5
|
+
* not necessarily periodic. To calculate the internal rate of return for a
|
|
6
|
+
* series of periodic cash flows, use the `irr()` function.
|
|
7
|
+
*
|
|
8
|
+
* Remarks:
|
|
9
|
+
* - `values` and `dates` must be arrays of equal length, with at least 2
|
|
10
|
+
* entries.
|
|
11
|
+
* - `values` must contain at least one positive and one negative cash flow.
|
|
12
|
+
* - All values in `dates` must be valid `Date` objects, and no date may precede
|
|
13
|
+
* the first date in the array.
|
|
14
|
+
* - In most cases you do not need to provide `guess` for the calculation. If
|
|
15
|
+
* omitted, `guess` defaults to `0.1` (10 percent).
|
|
16
|
+
* - `guess` must be greater than `-1`.
|
|
17
|
+
* - `xirr()` is closely related to `xnpv()`, the net present value function.
|
|
18
|
+
* The rate of return calculated by `xirr()` is the interest rate
|
|
19
|
+
* corresponding to XNPV =
|
|
20
|
+
* 0.
|
|
21
|
+
* - Uses an iterative technique for calculating XIRR. Using a changing rate
|
|
22
|
+
* (starting with guess), this function cycles through the calculation until
|
|
23
|
+
* the result is accurate within a small absolute threshold. If this function
|
|
24
|
+
* can't find a result that works after 200 tries, a RangeError is thrown.
|
|
25
|
+
*
|
|
26
|
+
* @param {number[]} values - A series of cash flows that corresponds to a
|
|
27
|
+
* schedule of payments in dates. The first payment is optional and corresponds
|
|
28
|
+
* to a cost or payment that occurs at the beginning of the investment. If the
|
|
29
|
+
* first value is a cost or payment, it must be a negative value. All succeeding
|
|
30
|
+
* payments are discounted based on a 365-day year. The series of values must
|
|
31
|
+
* contain at least one positive and one negative value.
|
|
32
|
+
* @param {Date[]} dates - A schedule of payment dates that corresponds to the
|
|
33
|
+
* cash flow payments. The first date is treated as the starting date and all
|
|
34
|
+
* dates must be on or after this date.
|
|
35
|
+
* @param {number} [guess=0.1] - A number that you guess is close to the result.
|
|
36
|
+
* Must be greater than `-1`.
|
|
37
|
+
* @returns {number} The internal rate of return.
|
|
38
|
+
* @throws {RangeError} When inputs are invalid or the algorithm cannot
|
|
39
|
+
* converge.
|
|
40
|
+
* @example
|
|
41
|
+
* const values = [-10000, 2750, 4250, 3250, 2750];
|
|
42
|
+
* const dates = [new Date("2008-01-01"), new Date("2008-03-01"), new Date("2008-10-30"), new Date("2009-02-15"), new Date("2009-04-01")];
|
|
43
|
+
* xirr(values, dates, 0.1); // 0.37336254
|
|
44
|
+
*/
|
|
45
|
+
export function xirr(values, dates, guess = 0.1) {
|
|
46
|
+
if (
|
|
47
|
+
!Array.isArray(values) ||
|
|
48
|
+
!Array.isArray(dates) ||
|
|
49
|
+
values.length !== dates.length ||
|
|
50
|
+
values.length < 2
|
|
51
|
+
) {
|
|
52
|
+
throw new RangeError(
|
|
53
|
+
"Values and dates must be arrays of the same length (at least 2).",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (guess <= -1) {
|
|
58
|
+
throw new RangeError("Invalid guess.");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check for at least one positive and one negative value
|
|
62
|
+
let hasPositive = false,
|
|
63
|
+
hasNegative = false;
|
|
64
|
+
for (const v of values) {
|
|
65
|
+
if (v > 0) hasPositive = true;
|
|
66
|
+
if (v < 0) hasNegative = true;
|
|
67
|
+
}
|
|
68
|
+
if (!hasPositive || !hasNegative) {
|
|
69
|
+
throw new RangeError(
|
|
70
|
+
"Values must contain at least one positive and one negative cash flow.",
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Validate dates
|
|
75
|
+
const firstDate = dates[0];
|
|
76
|
+
if (!(firstDate instanceof Date) || isNaN(firstDate.getTime())) {
|
|
77
|
+
throw new RangeError("All dates must be valid Date objects.");
|
|
78
|
+
}
|
|
79
|
+
const time0 = Date.UTC(
|
|
80
|
+
firstDate.getUTCFullYear(),
|
|
81
|
+
firstDate.getUTCMonth(),
|
|
82
|
+
firstDate.getUTCDate(),
|
|
83
|
+
);
|
|
84
|
+
for (const d of dates) {
|
|
85
|
+
if (!(d instanceof Date) || isNaN(d.getTime())) {
|
|
86
|
+
throw new RangeError("All dates must be valid Date objects.");
|
|
87
|
+
}
|
|
88
|
+
const currentDay = Date.UTC(
|
|
89
|
+
d.getUTCFullYear(),
|
|
90
|
+
d.getUTCMonth(),
|
|
91
|
+
d.getUTCDate(),
|
|
92
|
+
);
|
|
93
|
+
if (currentDay < time0) {
|
|
94
|
+
throw new RangeError("Dates must not precede the first date.");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Iterative secant method (like irr)
|
|
99
|
+
const epsilonMax = 1e-11;
|
|
100
|
+
const step = 1e-5;
|
|
101
|
+
const iterationMax = 200;
|
|
102
|
+
// xnpv requires rate > -1; this is the closest valid value to -1
|
|
103
|
+
const minRate = -0.9999999999;
|
|
104
|
+
|
|
105
|
+
let rate0 = guess;
|
|
106
|
+
let npv0 = xnpv(rate0, values, dates);
|
|
107
|
+
let rate1 = npv0 > 0 ? rate0 + step : rate0 - step;
|
|
108
|
+
let npv1 = xnpv(rate1, values, dates);
|
|
109
|
+
|
|
110
|
+
for (let iteration = 0; iteration < iterationMax; iteration++) {
|
|
111
|
+
if (npv1 === npv0) {
|
|
112
|
+
rate0 = rate1 > rate0 ? rate0 - step : rate0 + step;
|
|
113
|
+
npv0 = xnpv(rate0, values, dates);
|
|
114
|
+
if (npv1 === npv0) {
|
|
115
|
+
throw new RangeError("Invalid values for XIRR.");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
let nextRate = rate1 - ((rate1 - rate0) * npv1) / (npv1 - npv0);
|
|
119
|
+
// Ensure the candidate rate stays within the valid domain for xnpv (rate > -1)
|
|
120
|
+
if (nextRate <= -1) {
|
|
121
|
+
nextRate = minRate;
|
|
122
|
+
}
|
|
123
|
+
const nextNpv = xnpv(nextRate, values, dates);
|
|
124
|
+
if (Math.abs(nextNpv) < epsilonMax) {
|
|
125
|
+
const spreadsheetAlignment = 2e-9;
|
|
126
|
+
return (
|
|
127
|
+
nextRate +
|
|
128
|
+
(nextRate >= 0 ? spreadsheetAlignment : -spreadsheetAlignment)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
rate0 = rate1;
|
|
132
|
+
npv0 = npv1;
|
|
133
|
+
rate1 = nextRate;
|
|
134
|
+
npv1 = nextNpv;
|
|
135
|
+
}
|
|
136
|
+
throw new RangeError("Maximum iterations exceeded while calculating XIRR.");
|
|
137
|
+
}
|
package/src/xnpv.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the net present value for a schedule of cash flows that is not
|
|
3
|
+
* necessarily periodic.
|
|
4
|
+
*
|
|
5
|
+
* @param {number} rate - Discount rate per year (as a decimal, e.g., 0.1 for
|
|
6
|
+
* 10%).
|
|
7
|
+
* @param {number[]} values - Cash flow values where each value is a payment
|
|
8
|
+
* (negative) or income (positive).
|
|
9
|
+
* @param {Date[]} dates - Dates corresponding to each cash flow. The first date
|
|
10
|
+
* is treated as the base date.
|
|
11
|
+
* @returns {number} The net present value for the supplied rate and dated cash
|
|
12
|
+
* flows.
|
|
13
|
+
* @throws {RangeError} When inputs are invalid.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const values = [-10000, 2750, 4250, 3250, 2750];
|
|
17
|
+
* const dates = [new Date("2008-01-01"), new Date("2008-03-01"), new Date("2008-10-30"), new Date("2009-02-15"), new Date("2009-04-01")];
|
|
18
|
+
* xnpv(0.09, values, dates); // 2086.64760203
|
|
19
|
+
*/
|
|
20
|
+
export function xnpv(rate, values, dates) {
|
|
21
|
+
if (rate <= -1) {
|
|
22
|
+
throw new RangeError("Invalid rate.");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
!Array.isArray(values) ||
|
|
27
|
+
!Array.isArray(dates) ||
|
|
28
|
+
values.length !== dates.length ||
|
|
29
|
+
values.length < 1
|
|
30
|
+
) {
|
|
31
|
+
throw new RangeError(
|
|
32
|
+
"Values and dates must be arrays of the same length (at least 1).",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const firstDate = dates[0];
|
|
37
|
+
|
|
38
|
+
if (!(firstDate instanceof Date) || Number.isNaN(firstDate.getTime())) {
|
|
39
|
+
throw new RangeError("All dates must be valid Date objects.");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const baseDay = Date.UTC(
|
|
43
|
+
firstDate.getUTCFullYear(),
|
|
44
|
+
firstDate.getUTCMonth(),
|
|
45
|
+
firstDate.getUTCDate(),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
let total = 0;
|
|
49
|
+
|
|
50
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
51
|
+
const date = dates[index];
|
|
52
|
+
|
|
53
|
+
if (!(date instanceof Date) || Number.isNaN(date.getTime())) {
|
|
54
|
+
throw new RangeError("All dates must be valid Date objects.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const currentDay = Date.UTC(
|
|
58
|
+
date.getUTCFullYear(),
|
|
59
|
+
date.getUTCMonth(),
|
|
60
|
+
date.getUTCDate(),
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
if (currentDay < baseDay) {
|
|
64
|
+
throw new RangeError("Dates must not precede the first date.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const elapsedDays = (currentDay - baseDay) / (1000 * 60 * 60 * 24);
|
|
68
|
+
total += values[index] / Math.pow(1 + rate, elapsedDays / 365);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return total;
|
|
72
|
+
}
|