@travishorn/financejs 1.0.0 → 1.1.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 +69 -50
- package/package.json +5 -4
- package/src/fv.js +32 -9
- package/src/index.js +7 -5
- package/src/ipmt.js +31 -10
- package/src/irr.js +45 -8
- 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/xirr.js +137 -0
- package/src/xnpv.js +72 -0
package/README.md
CHANGED
|
@@ -30,13 +30,13 @@ npm install @travishorn/financejs
|
|
|
30
30
|
import { pmt, rate, irr } from "@travishorn/financejs";
|
|
31
31
|
|
|
32
32
|
const payment = pmt(0.0525, 5, -10000);
|
|
33
|
-
// 2325.
|
|
33
|
+
// 2325.733168046526
|
|
34
34
|
|
|
35
35
|
const periodicRate = rate(60, 500, -25000);
|
|
36
|
-
// 0.
|
|
36
|
+
// 0.006183413161254404
|
|
37
37
|
|
|
38
38
|
const internalRate = irr([-1500, 500, 500, 500, 500]);
|
|
39
|
-
// 0.
|
|
39
|
+
// 0.12589832495374934
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
## Excel-style conventions
|
|
@@ -53,76 +53,81 @@ const internalRate = irr([-1500, 500, 500, 500, 500]);
|
|
|
53
53
|
|
|
54
54
|
### Input Variables
|
|
55
55
|
|
|
56
|
-
| Variable | Description
|
|
57
|
-
| -------- |
|
|
58
|
-
| `pv` |
|
|
59
|
-
| `fv` |
|
|
60
|
-
| `pmt` |
|
|
61
|
-
| `nper` |
|
|
62
|
-
| `per` |
|
|
63
|
-
| `rate` |
|
|
64
|
-
| `type` |
|
|
65
|
-
| `guess` | A guess
|
|
66
|
-
| `values` |
|
|
56
|
+
| Variable | Description |
|
|
57
|
+
| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
58
|
+
| `pv` | The present value, or the lump-sum amount that a series of future payments is worth right now. |
|
|
59
|
+
| `fv` | The future value or a cash balance you want to attain after the last payment is made. If `fv` is omitted, it is assumed to be `0` (the future value of a loan, for example, is 0). For example, if you want to save $50,000 to pay for a special project in 18 years, then $50,000 is the future value. You could then make a conservative guess at an interest rate and determine how much you must save each month. |
|
|
60
|
+
| `pmt` | The payment made each period and cannot change over the life of the annuity. Typically, `pmt` includes principal and interest but no other fees or taxes. For example, the monthly payments on a $10,000, four-year car loan at 12 percent are $263.33. You would enter `-263.33` as the `pmt`. |
|
|
61
|
+
| `nper` | The total number of payment periods in an annuity. For example, if you get a four-year car loan and make monthly payments, your loan has 4 \* 12 (or 48) periods. You would enter `48` for `per`. |
|
|
62
|
+
| `per` | The period for which you want to find the interest and must be in the range `1` to `nper`. |
|
|
63
|
+
| `rate` | The interest rate per period. For example, if you obtain an automobile loan at a 10 percent annual interest rate and make monthly payments, your interest rate per month is 10% / 12, or 0.83%. You would enter `0.10 / 12` or `0.0083`, into the formula as the rate. |
|
|
64
|
+
| `type` | The number `0` or `1` and indicates when payments are due. Set `type` equal to `0` or omitted if payments are due at the end of the period. Set `type` equal to `1` if payments are due at the beginning of the period. |
|
|
65
|
+
| `guess` | A number that you guess is close to the result. In most cases you do not need to provide `guess` for the calculation to succeeed. If a RangeError is thrown, or if the result is not close to what you expected, try again with a different value for `guess`. |
|
|
66
|
+
| `values` | Array of cash flows, where each entry represents a payment (negative) or income (positive) at a regular interval. |
|
|
67
67
|
|
|
68
|
-
### `
|
|
68
|
+
### `fv(rate, nper, pmt, pv, type = 0)`
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
Calculates the future value of an investment based on a constant interest rate.
|
|
71
|
+
You can use FV with either periodic, constant payments, or a single lump sum
|
|
72
|
+
payment.
|
|
72
73
|
|
|
73
|
-
### `
|
|
74
|
+
### `ipmt(rate, per, nper, pv, fv = 0, type = 0)`
|
|
74
75
|
|
|
75
|
-
Returns the
|
|
76
|
-
a constant interest rate.
|
|
76
|
+
Returns the interest payment for a given period for an investment based on
|
|
77
|
+
periodic, constant payments and a constant interest rate.
|
|
77
78
|
|
|
78
|
-
### `
|
|
79
|
+
### `irr(values, guess = 0.1)`
|
|
79
80
|
|
|
80
|
-
Calculates the
|
|
81
|
-
|
|
81
|
+
Calculates the internal rate of return for a series of cash flows represented by
|
|
82
|
+
the numbers in `values`. These cash flows do not have to be even, as they would
|
|
83
|
+
be for an annuity. However, the cash flows must occur at regular intervals, such
|
|
84
|
+
as monthly or annually. The internal rate of return is the interest rate
|
|
85
|
+
received for an investment consisting of payments (negative values) and income
|
|
86
|
+
(positive values) that occur at regular periods.
|
|
82
87
|
|
|
83
88
|
### `nper(rate, pmt, pv, fv = 0, type = 0)`
|
|
84
89
|
|
|
85
|
-
|
|
90
|
+
Calculates the number of periods for an investment based on periodic, constant
|
|
91
|
+
payments and a constant interest rate.
|
|
86
92
|
|
|
87
|
-
### `
|
|
93
|
+
### `npv(rate, ...values)`
|
|
88
94
|
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
Calculates the net present value of an investment by using a discount rate and a
|
|
96
|
+
series of future payments (negative values) and income (positive values).
|
|
91
97
|
|
|
92
|
-
### `
|
|
98
|
+
### `pmt(rate, nper, pv, fv = 0, type = 0)`
|
|
93
99
|
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
Calculates the payment for a loan based on constant payments and a constant
|
|
101
|
+
interest rate.
|
|
96
102
|
|
|
97
|
-
### `rate
|
|
103
|
+
### `ppmt(rate, per, nper, pv, fv = 0, type = 0)`
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
Calculates the payment on the principal for a given period for an investment
|
|
106
|
+
based on periodic, constant payments and a constant interest rate.
|
|
100
107
|
|
|
101
|
-
### `
|
|
108
|
+
### `pv(rate, nper, pmt, fv = 0, type = 0)`
|
|
102
109
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
Calculates the present value of a loan or an investment, based on a constant
|
|
111
|
+
interest rate. You can use PV with either periodic, constant payments (such as a
|
|
112
|
+
mortgage or other loan), or a future value that's your investment goal.
|
|
106
113
|
|
|
107
|
-
### `
|
|
114
|
+
### `rate(nper, pmt, pv, fv = 0, type = 0, guess = 0.1)`
|
|
108
115
|
|
|
109
|
-
|
|
116
|
+
Calculates the interest rate per period of an annuity. The rate is calculated by
|
|
117
|
+
iteration and can have zero or more solutions. If the successive results of this
|
|
118
|
+
function do not converge to within 0.0000001 after 128 iterations, a RangeError
|
|
119
|
+
is thrown.
|
|
110
120
|
|
|
111
|
-
|
|
121
|
+
### `xirr(values, dates, guess = 0.1)`
|
|
112
122
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
123
|
+
Calculates the internal rate of return for a schedule of cash flows that is
|
|
124
|
+
not necessarily periodic. To calculate the internal rate of return for a
|
|
125
|
+
series of periodic cash flows, use the `irr()` function.
|
|
116
126
|
|
|
117
|
-
|
|
127
|
+
### `xnpv(rate, values, dates)`
|
|
118
128
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const values = [-1500, 500, 500, 500, 500];
|
|
122
|
-
return Math.round(irr(values) * 100 ) / 100 * 100;
|
|
123
|
-
}
|
|
124
|
-
// returns 12.59
|
|
125
|
-
```
|
|
129
|
+
Calculates the net present value for a schedule of cash flows that is not
|
|
130
|
+
necessarily periodic.
|
|
126
131
|
|
|
127
132
|
## Error behavior
|
|
128
133
|
|
|
@@ -172,6 +177,20 @@ npm run format
|
|
|
172
177
|
This rewrite is intended to match Excel-style formulas closely (tests validate
|
|
173
178
|
to 8 decimal places), while using a modern JavaScript module API.
|
|
174
179
|
|
|
180
|
+
## Roadmap
|
|
181
|
+
|
|
182
|
+
I want to add more [Excel financial
|
|
183
|
+
functions](https://support.microsoft.com/en-us/office/financial-functions-reference-5658d81e-6035-4f24-89c1-fbf124c2b1d8)
|
|
184
|
+
to the project. Since there are over 50 functions, I'll break them into "tiers."
|
|
185
|
+
|
|
186
|
+
- **Tier 1:** ✓pmt, ✓pv, ✓fv, ✓npv, ✓irr, ✓rate, ✓nper, ✓xnpv, ✓xirr
|
|
187
|
+
- **Tier 2:** ✓ipmt, ✓ppmt, cumipmt, cumprinc, sln, db, ddb, effect, nominal, syd,
|
|
188
|
+
mirr
|
|
189
|
+
- **Tier 3:** rri, pduration, vdb, fvschedule, dollarde, dollarfr, ispmt
|
|
190
|
+
- **Tier 4:** yield, price, duration, mduration, disc, intrate, received,
|
|
191
|
+
pricedisc, pricemat, yielddisc, yieldmat
|
|
192
|
+
- **Tier 5:** all others
|
|
193
|
+
|
|
175
194
|
## License
|
|
176
195
|
|
|
177
196
|
The MIT License
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travishorn/financejs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Modern JavaScript time value of money and cash-flow financial formulas with Excel-style behavior.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,10 +20,11 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
|
-
"
|
|
24
|
-
"lint": "
|
|
23
|
+
"format": "prettier --write .",
|
|
24
|
+
"lint:format": "prettier --check .",
|
|
25
25
|
"lint:types": "tsc --noEmit",
|
|
26
|
-
"
|
|
26
|
+
"lint": "eslint .",
|
|
27
|
+
"test": "vitest --run"
|
|
27
28
|
},
|
|
28
29
|
"keywords": [
|
|
29
30
|
"finance",
|
package/src/fv.js
CHANGED
|
@@ -1,24 +1,47 @@
|
|
|
1
|
+
import { normalizeZero } from "./normalizeZero.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* Calculates the future value of an investment
|
|
4
|
+
* Calculates the future value of an investment based on a constant interest
|
|
5
|
+
* rate. You can use FV with either periodic, constant payments, or a single
|
|
6
|
+
* lump sum payment.
|
|
7
|
+
*
|
|
8
|
+
* Remarks:
|
|
9
|
+
* - Be consistent with units for `rate` and `nper`. For example, for monthly
|
|
10
|
+
* payments on a four-year loan at 12% annual interest, use `12%/12` for
|
|
11
|
+
* `rate` and `4*12` for `nper`. For annual payments, use `12%` for `rate` and
|
|
12
|
+
* `4` for `nper`.
|
|
13
|
+
* - Cash paid out (for example, deposits to savings) is represented by negative
|
|
14
|
+
* numbers; cash received is represented by positive numbers.
|
|
3
15
|
*
|
|
4
16
|
* @param {number} rate - The interest rate per period.
|
|
5
|
-
* @param {number} nper - The total number of payment periods.
|
|
6
|
-
* @param {number} pmt - The payment made each period
|
|
7
|
-
*
|
|
8
|
-
*
|
|
17
|
+
* @param {number} nper - The total number of payment periods in an annuity.
|
|
18
|
+
* @param {number} [pmt=0] - The payment made each period; it cannot change over
|
|
19
|
+
* the life of the annuity. Typically, pmt contains principal and interest but
|
|
20
|
+
* no other fees or taxes. If `pmt` is omitted, you must include the `pv`
|
|
21
|
+
* argument.
|
|
22
|
+
* @param {number} [pv=0] - The present value, or the lump-sum amount that a
|
|
23
|
+
* series of future payments is worth right now. If `pv` is omitted, it is
|
|
24
|
+
* assumed to be `0` (zero), and you must include the `pmt` argument.
|
|
25
|
+
* @param {0|1} [type=0] - The number `0` or `1` and indicates when payments are
|
|
26
|
+
* due. If `type` is omitted, it is assumed to be `0`. Set `type` equal to `0`
|
|
27
|
+
* if payments are due at the end of the period. Set `type` equal to `1` if
|
|
28
|
+
* payments are due at the beginning of the period.
|
|
9
29
|
* @returns {number} The future value.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* fv(0.06 / 12, 10, -200, -500, 1); // 2581.40
|
|
10
33
|
*/
|
|
11
|
-
export function fv(rate, nper, pmt, pv, type = 0) {
|
|
34
|
+
export function fv(rate, nper, pmt = 0, pv = 0, type = 0) {
|
|
12
35
|
if (rate === 0) {
|
|
13
|
-
return -pv - pmt * nper;
|
|
36
|
+
return normalizeZero(-pv - pmt * nper);
|
|
14
37
|
} else {
|
|
15
38
|
const paymentTimingFactor = type !== 0 ? 1 + rate : 1;
|
|
16
39
|
const interestFactor = 1 + rate;
|
|
17
40
|
const compoundFactor = Math.pow(interestFactor, nper);
|
|
18
41
|
|
|
19
|
-
return (
|
|
42
|
+
return normalizeZero(
|
|
20
43
|
-pv * compoundFactor -
|
|
21
|
-
|
|
44
|
+
(pmt / rate) * paymentTimingFactor * (compoundFactor - 1),
|
|
22
45
|
);
|
|
23
46
|
}
|
|
24
47
|
}
|
package/src/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
export { pv } from "./pv.js";
|
|
2
|
-
export { nper } from "./nper.js";
|
|
3
|
-
export { pmt } from "./pmt.js";
|
|
4
1
|
export { fv } from "./fv.js";
|
|
5
2
|
export { ipmt } from "./ipmt.js";
|
|
6
|
-
export { ppmt } from "./ppmt.js";
|
|
7
|
-
export { npv } from "./npv.js";
|
|
8
3
|
export { irr } from "./irr.js";
|
|
4
|
+
export { nper } from "./nper.js";
|
|
5
|
+
export { npv } from "./npv.js";
|
|
6
|
+
export { pmt } from "./pmt.js";
|
|
7
|
+
export { ppmt } from "./ppmt.js";
|
|
8
|
+
export { pv } from "./pv.js";
|
|
9
9
|
export { rate } from "./rate.js";
|
|
10
|
+
export { xirr } from "./xirr.js";
|
|
11
|
+
export { xnpv } from "./xnpv.js";
|
package/src/ipmt.js
CHANGED
|
@@ -1,19 +1,40 @@
|
|
|
1
|
-
import { fv } from "./fv.js";
|
|
1
|
+
import { fv as calculateFv } from "./fv.js";
|
|
2
2
|
import { pmt } from "./pmt.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Returns the interest payment for a given period for an investment based on
|
|
6
|
+
* periodic, constant payments and a constant interest rate.
|
|
7
|
+
*
|
|
8
|
+
* Remarks:
|
|
9
|
+
* - Make sure that you are consistent about the units you use for specifying
|
|
10
|
+
* `rate` and `nper`. If you make monthly payments on a four-year loan at 12
|
|
11
|
+
* percent annual interest, use `.12/12` for `rate` and `4*12` for `nper`. If
|
|
12
|
+
* you make annual payments on the same loan, use `.12` for `rate` and `4` for
|
|
13
|
+
* `nper`.
|
|
14
|
+
* - For all the arguments, cash you pay out, such as deposits to savings, is
|
|
15
|
+
* represented by negative numbers. Cash you receive, such as dividend checks,
|
|
16
|
+
* is represented by positive numbers.
|
|
6
17
|
*
|
|
7
18
|
* @param {number} rate - The interest rate per period.
|
|
8
|
-
* @param {number} per - The
|
|
9
|
-
*
|
|
10
|
-
* @param {number}
|
|
11
|
-
* @param {number}
|
|
12
|
-
*
|
|
19
|
+
* @param {number} per - The period for which you want to find the interest and
|
|
20
|
+
* must be in the range `1` to `nper`.
|
|
21
|
+
* @param {number} nper - The total number of payment periods in an annuity.
|
|
22
|
+
* @param {number} pv - The present value, or the lump-sum amount that a series
|
|
23
|
+
* of future payments is worth right now.
|
|
24
|
+
* @param {number} [fv=0] - The future value, or a cash balance you
|
|
25
|
+
* want to attain after the last payment is made. If `fv` is omitted, it is
|
|
26
|
+
* assumed to be `0` (the future value of a loan, for example, is `0`).
|
|
27
|
+
* @param {0|1} [type=0] - The number `0` or `1` and indicates when payments are
|
|
28
|
+
* due. If type is omitted, it is assumed to be `0`. Set `type` equal to `0` if
|
|
29
|
+
* payments are due at the end of the period. Set `type` equal to `1` if
|
|
30
|
+
* payments are due at the beginning of the period.
|
|
13
31
|
* @returns {number} The interest payment for the specified period.
|
|
14
32
|
* @throws {RangeError} When `per` is outside the valid range.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ipmt(0.1 / 12, 1, 3, 8000); // -66.67
|
|
15
36
|
*/
|
|
16
|
-
export function ipmt(rate, per, nper, pv,
|
|
37
|
+
export function ipmt(rate, per, nper, pv, fv = 0, type = 0) {
|
|
17
38
|
if (per <= 0 || per >= nper + 1) {
|
|
18
39
|
throw new RangeError("Invalid period.");
|
|
19
40
|
}
|
|
@@ -23,9 +44,9 @@ export function ipmt(rate, per, nper, pv, futureValue = 0, type = 0) {
|
|
|
23
44
|
}
|
|
24
45
|
|
|
25
46
|
const periodOffset = type !== 0 ? 2 : 1;
|
|
26
|
-
const periodicPayment = pmt(rate, nper, pv,
|
|
47
|
+
const periodicPayment = pmt(rate, nper, pv, fv, type);
|
|
27
48
|
const adjustedPresentValue = type !== 0 ? pv + periodicPayment : pv;
|
|
28
|
-
const periodFutureValue =
|
|
49
|
+
const periodFutureValue = calculateFv(
|
|
29
50
|
rate,
|
|
30
51
|
per - periodOffset,
|
|
31
52
|
periodicPayment,
|
package/src/irr.js
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Computes the net present value (NPV) of a series of cash flows at a given
|
|
3
|
+
* discount rate.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Used internally by the IRR algorithm to evaluate the present value of cash
|
|
6
|
+
* flows for a specific rate guess. Skips leading zero cash flows for
|
|
7
|
+
* efficiency. Cash flows are discounted in reverse order, from last to first
|
|
8
|
+
* nonzero value.
|
|
9
|
+
*
|
|
10
|
+
* @param {number[]} values - Array of cash flows, where each entry represents a
|
|
11
|
+
* payment (negative) or income (positive) at a regular interval.
|
|
12
|
+
* @param {number} [guess=0.1] - Discount rate guess (as a decimal, e.g., 0.1
|
|
13
|
+
* for 10%).
|
|
14
|
+
* @returns {number} The net present value of the cash flows at the supplied
|
|
15
|
+
* discount rate.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* internalPv([-1000, 300, 400, 500], 0.1); // -21.036814425244188
|
|
7
19
|
*/
|
|
8
20
|
function internalPv(values, guess = 0.1) {
|
|
9
21
|
let lowerBound = 0;
|
|
@@ -24,12 +36,37 @@ function internalPv(values, guess = 0.1) {
|
|
|
24
36
|
}
|
|
25
37
|
|
|
26
38
|
/**
|
|
27
|
-
* Calculates the internal rate of return for a series of cash flows
|
|
39
|
+
* Calculates the internal rate of return for a series of cash flows represented
|
|
40
|
+
* by the numbers in `values`. These cash flows do not have to be even, as they
|
|
41
|
+
* would be for an annuity. However, the cash flows must occur at regular
|
|
42
|
+
* intervals, such as monthly or annually. The internal rate of return is the
|
|
43
|
+
* interest rate received for an investment consisting of payments (negative
|
|
44
|
+
* values) and income (positive values) that occur at regular periods.
|
|
45
|
+
*
|
|
46
|
+
* Remarks:
|
|
47
|
+
* - Uses an iterative technique for calculating IRR. Starting with guess, this
|
|
48
|
+
* function cycles through the calculation until the result is accurate within
|
|
49
|
+
* a small absolute threshold. If this function can't find a result that works
|
|
50
|
+
* after 39 tries, a RangeError is thrown.
|
|
51
|
+
* - `irr()` is closely related to `npv()`, the net present value function. The
|
|
52
|
+
* rate of return calculated by this function is the interest rate
|
|
53
|
+
* corresponding to a 0 (zero) net present value.
|
|
28
54
|
*
|
|
29
|
-
* @param {number[]} values -
|
|
30
|
-
*
|
|
55
|
+
* @param {number[]} values - An array that contains numbers for which you want
|
|
56
|
+
* to calculate the internal rate of return. Values must contain at least one
|
|
57
|
+
* positive value and one negative value to calculate the internal rate of
|
|
58
|
+
* return. This function uses the order of values to interpret the order of cash
|
|
59
|
+
* flows. Be sure to enter your payment and income values in the sequence you
|
|
60
|
+
* want.
|
|
61
|
+
* @param {number} [guess=0.1] - A number that you guess is close to the result
|
|
62
|
+
* of this function. In most cases you do not need to provide guess for this
|
|
63
|
+
* calculation. If a RangeError is thrown, or if the result is not close to what
|
|
64
|
+
* you expected, try again with a different value for `guess`.
|
|
31
65
|
* @returns {number} The internal rate of return.
|
|
32
|
-
* @throws {RangeError} When inputs are invalid or the algorithm cannot
|
|
66
|
+
* @throws {RangeError} When inputs are invalid or the algorithm cannot
|
|
67
|
+
* converge.
|
|
68
|
+
* @example
|
|
69
|
+
* irr([-70000,12000,15000,18000,21000]); // -0.021244848272975403
|
|
33
70
|
*/
|
|
34
71
|
export function irr(values, guess = 0.1) {
|
|
35
72
|
if (guess <= -1) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes zero values to ensure consistent handling of positive and negative
|
|
3
|
+
* zero in calculations.
|
|
4
|
+
*
|
|
5
|
+
* In JavaScript, +0 and -0 are distinct values that can yield different results
|
|
6
|
+
* in equality checks (e.g., Object.is(+0, -0) is false), sign checks
|
|
7
|
+
* (Math.sign), and division (1/0 vs 1/-0). This function coerces any zero input
|
|
8
|
+
* (either +0 or -0) to positive zero (+0), providing a consistent
|
|
9
|
+
* representation for downstream calculations, comparisons, and serialization.
|
|
10
|
+
*
|
|
11
|
+
* @param {number} value - The numeric value to normalize. If the value is +0 or
|
|
12
|
+
* -0, returns +0; otherwise, returns the original value.
|
|
13
|
+
* @returns {number} The normalized value, with all zeroes represented as +0.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* normalizeZero(-0); // 0
|
|
17
|
+
* normalizeZero(+0); // 0
|
|
18
|
+
* normalizeZero(5); // 5
|
|
19
|
+
*/
|
|
20
|
+
export function normalizeZero(value) {
|
|
21
|
+
return value === 0 ? 0 : value;
|
|
22
|
+
}
|
package/src/nper.js
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
|
+
import { normalizeZero } from "./normalizeZero.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* Calculates the number of periods for an investment
|
|
4
|
+
* Calculates the number of periods for an investment based on periodic,
|
|
5
|
+
* constant payments and a constant interest rate.
|
|
3
6
|
*
|
|
4
|
-
* @param {number} rate -
|
|
5
|
-
* @param {number} pmt -
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @param {
|
|
7
|
+
* @param {number} rate - The interest rate per period.
|
|
8
|
+
* @param {number} pmt - The payment made each period; it cannot change over the
|
|
9
|
+
* life of the annuity. Typically, `pmt` contains principal and interest but no
|
|
10
|
+
* other fees or taxes.
|
|
11
|
+
* @param {number} pv - The present value, or the lump-sum amount that a series
|
|
12
|
+
* of future payments is worth right now.
|
|
13
|
+
* @param {number} [fv=0] - The future value, or a cash balance you want to
|
|
14
|
+
* attain after the last payment is made. If `fv` is omitted, it is assumed to
|
|
15
|
+
* be `0` (the future value of a loan, for example, is 0).
|
|
16
|
+
* @param {0|1} [type=0] - The number 0 or 1 and indicates when payments are
|
|
17
|
+
* due. Set `type` equal to `0` or omitted if payments are due at the end of the
|
|
18
|
+
* period. Set `type` equal to `1` if payments are due at the beginning of the
|
|
19
|
+
* period.
|
|
9
20
|
* @returns {number} Number of periods.
|
|
10
21
|
* @throws {RangeError} When calculation is impossible with the provided inputs.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* nper(0.12 / 12, -100, -1000, 10000, 1); // 59.67386567
|
|
11
25
|
*/
|
|
12
26
|
export function nper(rate, pmt, pv, fv = 0, type = 0) {
|
|
13
27
|
if (rate === 0) {
|
|
@@ -15,7 +29,7 @@ export function nper(rate, pmt, pv, fv = 0, type = 0) {
|
|
|
15
29
|
throw new RangeError("Payment cannot be 0 when rate is 0.");
|
|
16
30
|
}
|
|
17
31
|
|
|
18
|
-
return -(pv + fv) / pmt;
|
|
32
|
+
return normalizeZero(-(pv + fv) / pmt);
|
|
19
33
|
}
|
|
20
34
|
|
|
21
35
|
const paymentAdjustment = type !== 0 ? pmt * (1 + rate) : pmt;
|
|
@@ -34,8 +48,8 @@ export function nper(rate, pmt, pv, fv = 0, type = 0) {
|
|
|
34
48
|
|
|
35
49
|
const growthFactor = 1 + rate;
|
|
36
50
|
|
|
37
|
-
return (
|
|
51
|
+
return normalizeZero(
|
|
38
52
|
(Math.log(futureValueTerm) - Math.log(presentValueTerm)) /
|
|
39
|
-
|
|
53
|
+
Math.log(growthFactor),
|
|
40
54
|
);
|
|
41
55
|
}
|
package/src/npv.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Calculates the net present value (NPV) of a subset of cash flows at a
|
|
3
|
+
* specified discount rate.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Used internally by the `npv()` function to evaluate the present value of cash
|
|
6
|
+
* flows between given indices. Each cash flow is discounted to its present
|
|
7
|
+
* value using the supplied rate, starting from lowerBound to upperBound
|
|
8
|
+
* (inclusive).
|
|
9
|
+
*
|
|
10
|
+
* @param {number} rate - Discount rate per period (as a decimal, e.g., `0.1`
|
|
11
|
+
* for 10%).
|
|
12
|
+
* @param {number[]} values - Array of cash flows, where each entry represents a
|
|
13
|
+
* payment (negative) or income (positive) at a regular interval.
|
|
14
|
+
* @param {number} [lowerBound=0] - Start index (inclusive) for the range of
|
|
15
|
+
* values to evaluate.
|
|
16
|
+
* @param {number} [upperBound=values.length - 1] - End index (inclusive) for
|
|
17
|
+
* the range of values to evaluate.
|
|
18
|
+
* @returns {number} The net present value of the specified range of cash flows
|
|
19
|
+
* at the given discount rate.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* evalNpv(0.1, [-1000, 300, 400, 500], 0, 2); // -360.6311044327573
|
|
9
23
|
*/
|
|
10
24
|
function evalNpv(rate, values, lowerBound = 0, upperBound = values.length - 1) {
|
|
11
25
|
let discountFactor = 1;
|
|
@@ -21,12 +35,34 @@ function evalNpv(rate, values, lowerBound = 0, upperBound = values.length - 1) {
|
|
|
21
35
|
}
|
|
22
36
|
|
|
23
37
|
/**
|
|
24
|
-
* Calculates the net present value of
|
|
38
|
+
* Calculates the net present value of an investment by using a discount rate
|
|
39
|
+
* and a series of future payments (negative values) and income (positive
|
|
40
|
+
* values).
|
|
25
41
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
42
|
+
* Remarks:
|
|
43
|
+
* - The NPV investment begins one period before the date of the first value in
|
|
44
|
+
* the cash flow and ends with the last value in the cash flow. The NPV
|
|
45
|
+
* calculation is based on future cash flows. If your first cash flow occurs
|
|
46
|
+
* at the beginning of the first period, the first value must be added to the
|
|
47
|
+
* NPV result, not included in the values arguments.
|
|
48
|
+
* - `npv()` is similar to the `pv()` function (present value). The primary
|
|
49
|
+
* difference between `pv()` and `npv()` is that `pv()` allows cash flows to
|
|
50
|
+
* begin either at the end or at the beginning of the period. Unlike the
|
|
51
|
+
* variable NPV cash flow values, PV cash flows must be constant throughout
|
|
52
|
+
* the investment.
|
|
53
|
+
* - `npv()` is also related to the `irr()` function (internal rate of return).
|
|
54
|
+
* IRR is the rate for which NPV equals zero: `npv(irr(...), ...) = 0`.
|
|
55
|
+
*
|
|
56
|
+
* @param {number} rate - The rate of discount over the length of one period.
|
|
57
|
+
* @param {...number} values - At least one value is required. Values must be
|
|
58
|
+
* equally spaced in time and occur at the end of each period. This function
|
|
59
|
+
* uses the order of the values to interpret the order of cash flows. Be sure to
|
|
60
|
+
* enter your payment and income values in the correct sequence.
|
|
28
61
|
* @returns {number} The net present value.
|
|
29
62
|
* @throws {RangeError} When there are no cash flow values or rate is invalid.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* npv(0.1, -10000, 3000, 4200, 6800); // 1188.44
|
|
30
66
|
*/
|
|
31
67
|
export function npv(rate, ...values) {
|
|
32
68
|
if (values.length < 1) {
|
package/src/pmt.js
CHANGED
|
@@ -1,25 +1,46 @@
|
|
|
1
|
+
import { normalizeZero } from "./normalizeZero.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* Calculates the
|
|
4
|
+
* Calculates the payment for a loan based on constant payments and a constant
|
|
5
|
+
* interest rate.
|
|
6
|
+
*
|
|
7
|
+
* Remarks:
|
|
8
|
+
* - The payment returned by this function includes principal and interest but
|
|
9
|
+
* no taxes, reserve payments, or fees sometimes associated with loans.
|
|
10
|
+
* - Make sure that you are consistent about the units you use for specifying
|
|
11
|
+
* `rate` and `nper`. If you make monthly payments on a four-year loan at an
|
|
12
|
+
* annual interest rate of 12 percent, use `0.12 / 12` for `rate` and `4 * 12`
|
|
13
|
+
* for `nper`. If you make annual payments on the same loan, use `0.12` for
|
|
14
|
+
* `rate` and `4` for `nper`.
|
|
3
15
|
*
|
|
4
|
-
* @param {number} rate - The interest rate
|
|
5
|
-
* @param {number} nper - The total number of
|
|
6
|
-
* @param {number} pv - The present value
|
|
7
|
-
*
|
|
8
|
-
* @param {
|
|
16
|
+
* @param {number} rate - The interest rate for the loan.
|
|
17
|
+
* @param {number} nper - The total number of payments for the loan.
|
|
18
|
+
* @param {number} pv - The present value, or the total amount that a series of
|
|
19
|
+
* future payments is worth now; also known as the principal.
|
|
20
|
+
* @param {number} [fv=0] - The future value, or a cash balance you want to
|
|
21
|
+
* attain after the last payment is made. If `fv `is omitted, it is assumed to
|
|
22
|
+
* be `0 `(zero), that is, the future value of a loan is 0.
|
|
23
|
+
* @param {0|1} [type=0] - The number `0` (zero) or `1` and indicates when
|
|
24
|
+
* payments are due. Set `type` equal to `0` or omitted if payments are due at
|
|
25
|
+
* the end of the period. Set `type` equal to `1` if payments are due at the
|
|
26
|
+
* beginning of the period.
|
|
9
27
|
* @returns {number} The periodic payment amount.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* pmt(0.08 / 12, 10, 10000); // -1037.03
|
|
10
31
|
*/
|
|
11
32
|
export function pmt(rate, nper, pv, fv = 0, type = 0) {
|
|
12
33
|
if (rate === 0) {
|
|
13
|
-
return (-fv - pv) / nper;
|
|
34
|
+
return normalizeZero((-fv - pv) / nper);
|
|
14
35
|
} else {
|
|
15
36
|
const paymentTimingFactor = type !== 0 ? 1 + rate : 1;
|
|
16
37
|
const interestFactor = 1 + rate;
|
|
17
38
|
const compoundFactor = Math.pow(interestFactor, nper);
|
|
18
39
|
|
|
19
|
-
return (
|
|
40
|
+
return normalizeZero(
|
|
20
41
|
((-fv - pv * compoundFactor) /
|
|
21
42
|
(paymentTimingFactor * (compoundFactor - 1))) *
|
|
22
|
-
|
|
43
|
+
rate,
|
|
23
44
|
);
|
|
24
45
|
}
|
|
25
46
|
}
|
package/src/ppmt.js
CHANGED
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
import { ipmt } from "./ipmt.js";
|
|
2
|
+
import { normalizeZero } from "./normalizeZero.js";
|
|
2
3
|
import { pmt } from "./pmt.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
* Calculates the
|
|
6
|
+
* Calculates the payment on the principal for a given period for an investment
|
|
7
|
+
* based on periodic, constant payments and a constant interest rate.
|
|
8
|
+
*
|
|
9
|
+
* Remarks:
|
|
10
|
+
* - Make sure that you are consistent about the units you use for specifying
|
|
11
|
+
* `rate` and `nper`. If you make monthly payments on a four-year loan at 12
|
|
12
|
+
* percent annual interest, use `0.12 / 12` for `rate` and `4 * 12` for
|
|
13
|
+
* `nper`. If you make annual payments on the same loan, use `0.12` for `rate`
|
|
14
|
+
* and `4` for `nper`.
|
|
6
15
|
*
|
|
7
16
|
* @param {number} rate - The interest rate per period.
|
|
8
|
-
* @param {number} per -
|
|
9
|
-
*
|
|
10
|
-
* @param {number}
|
|
11
|
-
* @param {number}
|
|
12
|
-
*
|
|
13
|
-
* @
|
|
17
|
+
* @param {number} per - Specifies the period and must be in the range `1` to
|
|
18
|
+
* `nper`.
|
|
19
|
+
* @param {number} nper - The total number of payment periods in an annuity.
|
|
20
|
+
* @param {number} pv - The present value — the total amount that a series of
|
|
21
|
+
* future payments is worth now.
|
|
22
|
+
* @param {number} [futureValue=0] - The future value, or a cash balance you
|
|
23
|
+
* want to attain after the last payment is made. If `fv` is omitted, it is
|
|
24
|
+
* assumed to be `0` (zero), that is, the future value of a loan is 0.
|
|
25
|
+
* @param {0|1} [type=0] - The number `0` (zero) or `1` and indicates when
|
|
26
|
+
* payments are due. Set `type` equal to `0` or omitted if payments are due at
|
|
27
|
+
* the end of the period. Set `type` equal to `1` if payments are due at the
|
|
28
|
+
* beginning of the period.
|
|
29
|
+
* @returns {number} The payment on the principal for the specified period.
|
|
14
30
|
* @throws {RangeError} When `per` is outside the valid range.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ppmt(0.1 / 12, 1, 2 * 12, 2000); // -75.62
|
|
15
34
|
*/
|
|
16
35
|
export function ppmt(rate, per, nper, pv, futureValue = 0, type = 0) {
|
|
17
36
|
if (per <= 0 || per >= nper + 1) {
|
|
@@ -21,5 +40,5 @@ export function ppmt(rate, per, nper, pv, futureValue = 0, type = 0) {
|
|
|
21
40
|
const periodicPayment = pmt(rate, nper, pv, futureValue, type);
|
|
22
41
|
const interestPayment = ipmt(rate, per, nper, pv, futureValue, type);
|
|
23
42
|
|
|
24
|
-
return periodicPayment - interestPayment;
|
|
43
|
+
return normalizeZero(periodicPayment - interestPayment);
|
|
25
44
|
}
|
package/src/pv.js
CHANGED
|
@@ -1,24 +1,61 @@
|
|
|
1
|
+
import { normalizeZero } from "./normalizeZero.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* Calculates the present value of an investment based on a
|
|
4
|
+
* Calculates the present value of a loan or an investment, based on a constant
|
|
5
|
+
* interest rate. You can use PV with either periodic, constant payments (such
|
|
6
|
+
* as a mortgage or other loan), or a future value that's your investment goal.
|
|
7
|
+
*
|
|
8
|
+
* Remarks:
|
|
9
|
+
* - Make sure that you are consistent about the units you use for specifying
|
|
10
|
+
* `rate` and `nper`. If you make monthly payments on a four-year loan at 12
|
|
11
|
+
* percent annual interest, use 12%/12 for `rate` and 4*12 for `nper`. If you
|
|
12
|
+
* make annual payments on the same loan, use 12% for `rate` and 4 for `nper`.
|
|
13
|
+
* - An annuity is a series of constant cash payments made over a continuous
|
|
14
|
+
* period. For example, a car loan or a mortgage is an annuity.
|
|
15
|
+
* - In annuity functions, cash you pay out, such as a deposit to savings, is
|
|
16
|
+
* represented by a negative number. Cash you receive, such as a dividend
|
|
17
|
+
* check, is represented by a positive number. For example, a $1,000 deposit
|
|
18
|
+
* to the bank would be represented by the argument -1000 if you are the
|
|
19
|
+
* depositor and by the argument 1000 if you are the bank.
|
|
3
20
|
*
|
|
4
|
-
* @param {number} rate - The interest rate per period.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* @param {
|
|
21
|
+
* @param {number} rate - The interest rate per period. For example, if you
|
|
22
|
+
* obtain an automobile loan at a 10 percent annual interest rate and make
|
|
23
|
+
* monthly payments, your interest rate per month is 10%/12, or 0.83%. You would
|
|
24
|
+
* enter 10%/12, or 0.83%, or 0.0083, into the formula as the rate.
|
|
25
|
+
* @param {number} nper - The total number of payment periods in an annuity. For
|
|
26
|
+
* example, if you get a four-year car loan and make monthly payments, your loan
|
|
27
|
+
* has 4*12 (or 48) periods. You would enter 48 into the formula for nper.
|
|
28
|
+
* @param {number} [pmt=0] - The payment made each period and cannot change over
|
|
29
|
+
* the life of the annuity. Typically, pmt includes principal and interest but
|
|
30
|
+
* no other fees or taxes. For example, the monthly payments on a $10,000,
|
|
31
|
+
* four-year car loan at 12 percent are $263.33. You would enter -263.33 into
|
|
32
|
+
* the formula as the pmt. If pmt is omitted, you must include the fv argument.
|
|
33
|
+
* @param {number} [fv=0] - The future value or a cash balance you want to
|
|
34
|
+
* attain after the last payment is made. If fv is omitted, it is assumed to be
|
|
35
|
+
* 0 (the future value of a loan, for example, is 0). For example, if you want
|
|
36
|
+
* to save $50,000 to pay for a special project in 18 years, then $50,000 is the
|
|
37
|
+
* future value. You could then make a conservative guess at an interest rate
|
|
38
|
+
* and determine how much you must save each month. If fv is omitted, you must
|
|
39
|
+
* include the pmt argument.
|
|
40
|
+
* @param {0|1} [type=0] - The number 0 or 1 and indicates when payments are
|
|
41
|
+
* due. Set `type` equal to `0` or omitted if payments are due at the end of the
|
|
42
|
+
* period. Set `type` equal to `1` if payments are due at the end of the period.
|
|
9
43
|
* @returns {number} The present value of the investment.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* pv(0.08 / 12, 20 * 12, 500); // -59777.15
|
|
10
47
|
*/
|
|
11
|
-
export function pv(rate, nper, pmt, fv = 0, type = 0) {
|
|
48
|
+
export function pv(rate, nper, pmt = 0, fv = 0, type = 0) {
|
|
12
49
|
if (rate === 0) {
|
|
13
|
-
return -pmt * nper - fv;
|
|
50
|
+
return normalizeZero(-pmt * nper - fv);
|
|
14
51
|
} else {
|
|
15
52
|
const paymentTimingFactor = type !== 0 ? 1 + rate : 1;
|
|
16
53
|
const interestFactor = 1 + rate;
|
|
17
54
|
const compoundFactor = Math.pow(interestFactor, nper);
|
|
18
55
|
|
|
19
|
-
return (
|
|
56
|
+
return normalizeZero(
|
|
20
57
|
-(fv + pmt * paymentTimingFactor * ((compoundFactor - 1) / rate)) /
|
|
21
|
-
|
|
58
|
+
compoundFactor,
|
|
22
59
|
);
|
|
23
60
|
}
|
|
24
61
|
}
|
package/src/rate.js
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
|
+
import { normalizeZero } from "./normalizeZero.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* Evaluates the
|
|
4
|
+
* Evaluates the annuity equation for a candidate interest rate.
|
|
5
|
+
*
|
|
6
|
+
* Used internally by the `rate()` function to compute the result of the annuity
|
|
7
|
+
* equation for a given rate guess. This function supports both ordinary
|
|
8
|
+
* annuities (payments at end of period) and annuities due (payments at
|
|
9
|
+
* beginning of period), and accounts for present value, future value, and
|
|
10
|
+
* periodic payments.
|
|
11
|
+
*
|
|
12
|
+
* @param {number} rate - Candidate interest rate per period (as a decimal,
|
|
13
|
+
* e.g., `0.1` for 10%).
|
|
14
|
+
* @param {number} nper - Total number of payment periods.
|
|
15
|
+
* @param {number} pmt - Payment made each period (negative for outflows,
|
|
16
|
+
* positive for inflows).
|
|
17
|
+
* @param {number} pv - Present value (the lump sum amount at the start).
|
|
18
|
+
* @param {number} [fv=0] - Future value (the desired balance after the last
|
|
19
|
+
* payment; defaults to `0`).
|
|
20
|
+
* @param {0|1} [type=0] - Payment timing: `0` = end of period (ordinary annuity),
|
|
21
|
+
* `1` = beginning of period (annuity due).
|
|
22
|
+
* @returns {number} The result of the annuity equation for the supplied rate
|
|
23
|
+
* and parameters. A value close to zero indicates a solution for the interest
|
|
24
|
+
* rate.
|
|
3
25
|
*
|
|
4
|
-
* @
|
|
5
|
-
*
|
|
6
|
-
* @param {number} pmt - Periodic payment.
|
|
7
|
-
* @param {number} pv - Present value.
|
|
8
|
-
* @param {number} [fv=0] - Future value.
|
|
9
|
-
* @param {0|1} [type=0] - Payment timing: 0 = end of period, 1 = beginning.
|
|
10
|
-
* @returns {number} Equation result for the supplied rate.
|
|
26
|
+
* @example
|
|
27
|
+
* evalRate(0.05, 12, -100, 1000, 0, 0); // 204.1436739778701
|
|
11
28
|
*/
|
|
12
29
|
function evalRate(rate, nper, pmt, pv, fv = 0, type = 0) {
|
|
13
30
|
if (rate === 0) {
|
|
@@ -26,16 +43,43 @@ function evalRate(rate, nper, pmt, pv, fv = 0, type = 0) {
|
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
/**
|
|
29
|
-
* Calculates the interest rate per period
|
|
46
|
+
* Calculates the interest rate per period of an annuity. The rate is calculated
|
|
47
|
+
* by iteration and can have zero or more solutions. If the successive results
|
|
48
|
+
* of this function do not converge to within 0.0000001 after 128 iterations, a
|
|
49
|
+
* RangeError is thrown.
|
|
50
|
+
*
|
|
51
|
+
* Remarks:
|
|
52
|
+
* - Make sure that you are consistent about the units you use for specifying
|
|
53
|
+
* `rate` and `nper`. If you make monthly payments on a four-year loan at 12
|
|
54
|
+
* percent annual interest, use `0.12 / 12` for `rate` and `4 * 12` for
|
|
55
|
+
* `nper`. If you make annual payments on the same loan, use `0.12` for `rate`
|
|
56
|
+
* and `4` for `nper`.
|
|
57
|
+
*
|
|
58
|
+
* @param {number} nper - The total number of payment periods in an annuity.
|
|
59
|
+
* @param {number} pmt - The payment made each period and cannot change over the
|
|
60
|
+
* life of the annuity. Typically, `pmt` includes principal and interest but no
|
|
61
|
+
* other fees or taxes. If `pmt` is omitted, you must include the `fv` argument
|
|
62
|
+
* for a meaningful equation.
|
|
63
|
+
* @param {number} pv - The present value — the total amount that a series of
|
|
64
|
+
* future payments is worth now.
|
|
65
|
+
* @param {number} [fv=0] - The future value, or a cash balance you want to
|
|
66
|
+
* attain after the last payment is made. If `fv` is omitted, it is assumed to
|
|
67
|
+
* be `0` (the future value of a loan, for example, is 0). If `fv` is omitted,
|
|
68
|
+
* you must include the `pmt` argument.
|
|
69
|
+
* @param {0|1} [type=0] - The number `0` (zero) or `1` and indicates when
|
|
70
|
+
* payments are due. Set `type` equal to `0` or omitted if payments are due at
|
|
71
|
+
* the end of the period. Set `type` equal to `1` if payments are due at the
|
|
72
|
+
* beginning of the period.
|
|
73
|
+
* @param {number} [guess=0.1] - Your guess for what the rate will be. If you
|
|
74
|
+
* omit `guess`, it is assumed to be `0.1` (10 percent). If the calculation does
|
|
75
|
+
* not converge, try different values for `guess`. The calculation usually
|
|
76
|
+
* converges if guess is between `0` and `1`.
|
|
77
|
+
* @returns {number} The calculated interest rate per period of an annuity.
|
|
78
|
+
* @throws {RangeError} When inputs are invalid or the algorithm cannot
|
|
79
|
+
* converge.
|
|
30
80
|
*
|
|
31
|
-
* @
|
|
32
|
-
*
|
|
33
|
-
* @param {number} pv - Present value.
|
|
34
|
-
* @param {number} [fv=0] - Future value.
|
|
35
|
-
* @param {0|1} [type=0] - Payment timing: 0 = end of period, 1 = beginning.
|
|
36
|
-
* @param {number} [guess=0.1] - Initial guess for rate.
|
|
37
|
-
* @returns {number} The calculated rate per period.
|
|
38
|
-
* @throws {RangeError} When inputs are invalid or the algorithm cannot converge.
|
|
81
|
+
* @example
|
|
82
|
+
* rate(4 * 12, -200, 8000); // 0.007701472488210098
|
|
39
83
|
*/
|
|
40
84
|
export function rate(nper, pmt, pv, fv = 0, type = 0, guess = 0.1) {
|
|
41
85
|
if (nper <= 0) {
|
|
@@ -66,7 +110,7 @@ export function rate(nper, pmt, pv, fv = 0, type = 0, guess = 0.1) {
|
|
|
66
110
|
y0 = evalRate(rate0, nper, pmt, pv, fv, type);
|
|
67
111
|
|
|
68
112
|
if (Math.abs(y0) < epsilonMax) {
|
|
69
|
-
return rate0;
|
|
113
|
+
return normalizeZero(rate0);
|
|
70
114
|
}
|
|
71
115
|
|
|
72
116
|
const nextY = y0;
|
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
|
+
}
|