@travishorn/financejs 1.0.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/LICENSE +21 -0
- package/README.md +197 -0
- package/package.json +53 -0
- package/src/fv.js +24 -0
- package/src/index.js +9 -0
- package/src/ipmt.js +36 -0
- package/src/irr.js +104 -0
- package/src/nper.js +41 -0
- package/src/npv.js +41 -0
- package/src/pmt.js +25 -0
- package/src/ppmt.js +25 -0
- package/src/pv.js +24 -0
- package/src/rate.js +82 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright 2020 kgkars
|
|
4
|
+
Copyright 2026 Travis Horn
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
7
|
+
this software and associated documentation files (the “Software”), to deal in
|
|
8
|
+
the Software without restriction, including without limitation the rights to
|
|
9
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
10
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
11
|
+
subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
18
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
19
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
20
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
21
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# financejs
|
|
2
|
+
|
|
3
|
+
Excel-style time value of money and cash-flow formulas. This is a modern rewrite
|
|
4
|
+
of [tvm-financejs](https://github.com/kgkars/tvm-financejs/) by
|
|
5
|
+
[kgkars](https://github.com/kgkars).
|
|
6
|
+
|
|
7
|
+
## Why this rewrite
|
|
8
|
+
|
|
9
|
+
This project keeps the formula behavior and conventions from the original
|
|
10
|
+
library, but modernizes the implementation and tooling:
|
|
11
|
+
|
|
12
|
+
- Native ESM
|
|
13
|
+
- Named function exports (no class wrapper)
|
|
14
|
+
- Native error handling
|
|
15
|
+
- Strict type-checking
|
|
16
|
+
- JSDoc documentation
|
|
17
|
+
- Vitest-based test suite
|
|
18
|
+
- 100% test coverage
|
|
19
|
+
- ESLint + Prettier setup
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @travishorn/financejs
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
import { pmt, rate, irr } from "@travishorn/financejs";
|
|
31
|
+
|
|
32
|
+
const payment = pmt(0.0525, 5, -10000);
|
|
33
|
+
// 2325.7331680465
|
|
34
|
+
|
|
35
|
+
const periodicRate = rate(60, 500, -25000);
|
|
36
|
+
// 0.00618341316125388
|
|
37
|
+
|
|
38
|
+
const internalRate = irr([-1500, 500, 500, 500, 500]);
|
|
39
|
+
// 0.125898324962364
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Excel-style conventions
|
|
43
|
+
|
|
44
|
+
- Outputs are not rounded automatically.
|
|
45
|
+
- `pv` is typically negative for loans/investments (same convention as Excel).
|
|
46
|
+
- `type` means payment timing:
|
|
47
|
+
- `0` = end of period (arrears)
|
|
48
|
+
- `1` = beginning of period (advance)
|
|
49
|
+
- `rate` must match period frequency (e.g., annual rate divided by 12 for
|
|
50
|
+
monthly periods).
|
|
51
|
+
|
|
52
|
+
## API
|
|
53
|
+
|
|
54
|
+
### Input Variables
|
|
55
|
+
|
|
56
|
+
| Variable | Description |
|
|
57
|
+
| -------- | ------------------------------------------------------------------------------------- |
|
|
58
|
+
| `pv` | Present value |
|
|
59
|
+
| `fv` | Future value |
|
|
60
|
+
| `pmt` | Payment |
|
|
61
|
+
| `nper` | Total number of periods |
|
|
62
|
+
| `per` | A specific period |
|
|
63
|
+
| `rate` | Rate for the period(s) |
|
|
64
|
+
| `type` | When payments are due. `0` = end of period/arrears. `1` = beginning of period/advance |
|
|
65
|
+
| `guess` | A guess at the rate |
|
|
66
|
+
| `values` | A set of periodic cash flows |
|
|
67
|
+
|
|
68
|
+
### `pv(rate, nper, pmt, fv = 0, type = 0)`
|
|
69
|
+
|
|
70
|
+
Returns the present value of an investment, or the total amount that a series of
|
|
71
|
+
future payments is worth now.
|
|
72
|
+
|
|
73
|
+
### `fv(rate, nper, pmt, pv, type = 0)`
|
|
74
|
+
|
|
75
|
+
Returns the future value of an investment based on periodic, equal, payments and
|
|
76
|
+
a constant interest rate.
|
|
77
|
+
|
|
78
|
+
### `pmt(rate, nper, pv, fv = 0, type = 0)`
|
|
79
|
+
|
|
80
|
+
Calculates the payment for a loan based on a constant stream of equal payments
|
|
81
|
+
and a constant interest rate.
|
|
82
|
+
|
|
83
|
+
### `nper(rate, pmt, pv, fv = 0, type = 0)`
|
|
84
|
+
|
|
85
|
+
Number of periods.
|
|
86
|
+
|
|
87
|
+
### `ipmt(rate, per, nper, pv, fv = 0, type = 0)`
|
|
88
|
+
|
|
89
|
+
Returns the calculated interest portion of a payment for a specific period based
|
|
90
|
+
on a constant stream of equal payments and a constant interest rate.
|
|
91
|
+
|
|
92
|
+
### `ppmt(rate, per, nper, pv, fv = 0, type = 0)`
|
|
93
|
+
|
|
94
|
+
Returns the calculated principal portion of a payment for a specific period
|
|
95
|
+
based on a constant stream of equal payments and a constant interest rate.
|
|
96
|
+
|
|
97
|
+
### `rate(nper, pmt, pv, fv = 0, type = 0, guess = 0.1)`
|
|
98
|
+
|
|
99
|
+
Returns the interest rate per period for a loan or investment (iterative solve).
|
|
100
|
+
|
|
101
|
+
### `npv(rate, ...values)`
|
|
102
|
+
|
|
103
|
+
Returns the net present value of an investment based on a constant rate of
|
|
104
|
+
return and a series of future payments/investments (as negative values) and
|
|
105
|
+
income/return (as positive values).
|
|
106
|
+
|
|
107
|
+
### `irr(values, guess = 0.1)`
|
|
108
|
+
|
|
109
|
+
Returns the internal rate of return for a series of cash flows.
|
|
110
|
+
|
|
111
|
+
A couple of items to note about this formula:
|
|
112
|
+
|
|
113
|
+
- The variable values must be input as an array.
|
|
114
|
+
- There must be at least one negative and one positive value as part of the cash flow.
|
|
115
|
+
- Cash flows are assumed to be due in the same order they are arranged in the Array.
|
|
116
|
+
|
|
117
|
+
Example usage:
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
returnIRR() {
|
|
121
|
+
const values = [-1500, 500, 500, 500, 500];
|
|
122
|
+
return Math.round(irr(values) * 100 ) / 100 * 100;
|
|
123
|
+
}
|
|
124
|
+
// returns 12.59
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Error behavior
|
|
128
|
+
|
|
129
|
+
All functions will either return a number, or throw `RangeError` for invalid
|
|
130
|
+
inputs or non-convergent iterative solves.
|
|
131
|
+
|
|
132
|
+
## Development
|
|
133
|
+
|
|
134
|
+
Clone the repository:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
git clone https://github.com/travishorn/financejs
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Change into the directory:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
cd financejs
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Run tests:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npm test
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Lint with ESLint:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npm run lint
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Check types:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm run lint:types
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Format with Prettier:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
npm run format
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Notes on compatibility
|
|
171
|
+
|
|
172
|
+
This rewrite is intended to match Excel-style formulas closely (tests validate
|
|
173
|
+
to 8 decimal places), while using a modern JavaScript module API.
|
|
174
|
+
|
|
175
|
+
## License
|
|
176
|
+
|
|
177
|
+
The MIT License
|
|
178
|
+
|
|
179
|
+
Copyright 2020 kgkars
|
|
180
|
+
Copyright 2026 Travis Horn
|
|
181
|
+
|
|
182
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
183
|
+
this software and associated documentation files (the “Software”), to deal in
|
|
184
|
+
the Software without restriction, including without limitation the rights to
|
|
185
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
186
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
187
|
+
subject to the following conditions:
|
|
188
|
+
|
|
189
|
+
The above copyright notice and this permission notice shall be included in all
|
|
190
|
+
copies or substantial portions of the Software.
|
|
191
|
+
|
|
192
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
193
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
194
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
195
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
196
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
197
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@travishorn/financejs",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Modern JavaScript time value of money and cash-flow financial formulas with Excel-style behavior.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/travishorn/financejs.git"
|
|
8
|
+
},
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/travishorn/financejs/issues"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/travishorn/financejs#readme",
|
|
13
|
+
"exports": "./src/index.js",
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"test": "vitest --run",
|
|
24
|
+
"lint": "eslint .",
|
|
25
|
+
"lint:types": "tsc --noEmit",
|
|
26
|
+
"format": "prettier --write ."
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"finance",
|
|
30
|
+
"financial",
|
|
31
|
+
"tvm",
|
|
32
|
+
"time-value-of-money",
|
|
33
|
+
"excel",
|
|
34
|
+
"npv",
|
|
35
|
+
"irr",
|
|
36
|
+
"rate",
|
|
37
|
+
"loan",
|
|
38
|
+
"amortization"
|
|
39
|
+
],
|
|
40
|
+
"author": "Travis Horn <travis@travishorn.com> (https://travishorn.com/)",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"type": "module",
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@eslint/js": "^10.0.1",
|
|
45
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
46
|
+
"eslint": "^10.0.2",
|
|
47
|
+
"eslint-config-prettier": "^10.1.8",
|
|
48
|
+
"globals": "^17.4.0",
|
|
49
|
+
"prettier": "^3.8.1",
|
|
50
|
+
"typescript": "^5.9.3",
|
|
51
|
+
"vitest": "^4.0.18"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/fv.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the future value of an investment or loan.
|
|
3
|
+
*
|
|
4
|
+
* @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
|
+
* @param {number} pv - The present value.
|
|
8
|
+
* @param {0|1} [type=0] - Payment timing: 0 = end of period, 1 = beginning of period.
|
|
9
|
+
* @returns {number} The future value.
|
|
10
|
+
*/
|
|
11
|
+
export function fv(rate, nper, pmt, pv, type = 0) {
|
|
12
|
+
if (rate === 0) {
|
|
13
|
+
return -pv - pmt * nper;
|
|
14
|
+
} else {
|
|
15
|
+
const paymentTimingFactor = type !== 0 ? 1 + rate : 1;
|
|
16
|
+
const interestFactor = 1 + rate;
|
|
17
|
+
const compoundFactor = Math.pow(interestFactor, nper);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
-pv * compoundFactor -
|
|
21
|
+
(pmt / rate) * paymentTimingFactor * (compoundFactor - 1)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { pv } from "./pv.js";
|
|
2
|
+
export { nper } from "./nper.js";
|
|
3
|
+
export { pmt } from "./pmt.js";
|
|
4
|
+
export { fv } from "./fv.js";
|
|
5
|
+
export { ipmt } from "./ipmt.js";
|
|
6
|
+
export { ppmt } from "./ppmt.js";
|
|
7
|
+
export { npv } from "./npv.js";
|
|
8
|
+
export { irr } from "./irr.js";
|
|
9
|
+
export { rate } from "./rate.js";
|
package/src/ipmt.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { fv } from "./fv.js";
|
|
2
|
+
import { pmt } from "./pmt.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Calculates the interest portion of a payment for a specific period.
|
|
6
|
+
*
|
|
7
|
+
* @param {number} rate - The interest rate per period.
|
|
8
|
+
* @param {number} per - The target period (1-based).
|
|
9
|
+
* @param {number} nper - The total number of payment periods.
|
|
10
|
+
* @param {number} pv - The present value.
|
|
11
|
+
* @param {number} [futureValue=0] - The future value.
|
|
12
|
+
* @param {0|1} [type=0] - Payment timing: 0 = end of period, 1 = beginning of period.
|
|
13
|
+
* @returns {number} The interest payment for the specified period.
|
|
14
|
+
* @throws {RangeError} When `per` is outside the valid range.
|
|
15
|
+
*/
|
|
16
|
+
export function ipmt(rate, per, nper, pv, futureValue = 0, type = 0) {
|
|
17
|
+
if (per <= 0 || per >= nper + 1) {
|
|
18
|
+
throw new RangeError("Invalid period.");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (type !== 0 && per === 1) {
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const periodOffset = type !== 0 ? 2 : 1;
|
|
26
|
+
const periodicPayment = pmt(rate, nper, pv, futureValue, type);
|
|
27
|
+
const adjustedPresentValue = type !== 0 ? pv + periodicPayment : pv;
|
|
28
|
+
const periodFutureValue = fv(
|
|
29
|
+
rate,
|
|
30
|
+
per - periodOffset,
|
|
31
|
+
periodicPayment,
|
|
32
|
+
adjustedPresentValue,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return periodFutureValue * rate;
|
|
36
|
+
}
|
package/src/irr.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluates present value for an IRR iteration guess.
|
|
3
|
+
*
|
|
4
|
+
* @param {number[]} values - Cash flow values.
|
|
5
|
+
* @param {number} [guess=0.1] - Rate guess.
|
|
6
|
+
* @returns {number} Present value at the supplied guess.
|
|
7
|
+
*/
|
|
8
|
+
function internalPv(values, guess = 0.1) {
|
|
9
|
+
let lowerBound = 0;
|
|
10
|
+
const upperBound = values.length - 1;
|
|
11
|
+
let total = 0;
|
|
12
|
+
const discountRate = 1 + guess;
|
|
13
|
+
|
|
14
|
+
while (lowerBound <= upperBound && values[lowerBound] === 0) {
|
|
15
|
+
lowerBound += 1;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (let index = upperBound; index >= lowerBound; index -= 1) {
|
|
19
|
+
total /= discountRate;
|
|
20
|
+
total += values[index];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return total;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Calculates the internal rate of return for a series of cash flows.
|
|
28
|
+
*
|
|
29
|
+
* @param {number[]} values - Cash flow values where negatives are investments and positives are returns.
|
|
30
|
+
* @param {number} [guess=0.1] - Initial guess for the IRR iteration.
|
|
31
|
+
* @returns {number} The internal rate of return.
|
|
32
|
+
* @throws {RangeError} When inputs are invalid or the algorithm cannot converge.
|
|
33
|
+
*/
|
|
34
|
+
export function irr(values, guess = 0.1) {
|
|
35
|
+
if (guess <= -1) {
|
|
36
|
+
throw new RangeError("Invalid guess.");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (values.length < 1) {
|
|
40
|
+
throw new RangeError("Invalid values.");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const epsilonMax = 0.0000001;
|
|
44
|
+
const step = 0.00001;
|
|
45
|
+
const iterationMax = 39;
|
|
46
|
+
|
|
47
|
+
let maxAbsoluteValue = Math.abs(values[0]);
|
|
48
|
+
|
|
49
|
+
for (let index = 0; index < values.length; index += 1) {
|
|
50
|
+
const absoluteValue = Math.abs(values[index]);
|
|
51
|
+
|
|
52
|
+
if (absoluteValue > maxAbsoluteValue) {
|
|
53
|
+
maxAbsoluteValue = absoluteValue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const npvEpsilon = maxAbsoluteValue * epsilonMax * 0.01;
|
|
58
|
+
|
|
59
|
+
let rate0 = guess;
|
|
60
|
+
let npv0 = internalPv(values, rate0);
|
|
61
|
+
let rate1 = npv0 > 0 ? rate0 + step : rate0 - step;
|
|
62
|
+
|
|
63
|
+
if (rate1 <= -1) {
|
|
64
|
+
throw new RangeError("Invalid values.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let npv1 = internalPv(values, rate1);
|
|
68
|
+
|
|
69
|
+
for (let iteration = 0; iteration <= iterationMax; iteration += 1) {
|
|
70
|
+
if (npv1 === npv0) {
|
|
71
|
+
rate0 = rate1 > rate0 ? rate0 - step : rate0 + step;
|
|
72
|
+
npv0 = internalPv(values, rate0);
|
|
73
|
+
|
|
74
|
+
if (npv1 === npv0) {
|
|
75
|
+
throw new RangeError("Invalid values.");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
rate0 = rate1 - ((rate1 - rate0) * npv1) / (npv1 - npv0);
|
|
80
|
+
|
|
81
|
+
if (rate0 <= -1) {
|
|
82
|
+
rate0 = (rate1 - 1) * 0.5;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
npv0 = internalPv(values, rate0);
|
|
86
|
+
|
|
87
|
+
const rateDelta = Math.abs(rate0 - rate1);
|
|
88
|
+
const absoluteNpv = Math.abs(npv0);
|
|
89
|
+
|
|
90
|
+
if (absoluteNpv < npvEpsilon && rateDelta < epsilonMax) {
|
|
91
|
+
return rate0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const nextNpv = npv0;
|
|
95
|
+
npv0 = npv1;
|
|
96
|
+
npv1 = nextNpv;
|
|
97
|
+
|
|
98
|
+
const nextRate = rate0;
|
|
99
|
+
rate0 = rate1;
|
|
100
|
+
rate1 = nextRate;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw new RangeError("Maximum iterations exceeded while calculating IRR.");
|
|
104
|
+
}
|
package/src/nper.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the number of periods for an investment/loan.
|
|
3
|
+
*
|
|
4
|
+
* @param {number} rate - Interest rate per period.
|
|
5
|
+
* @param {number} pmt - Payment made each period.
|
|
6
|
+
* @param {number} pv - Present value.
|
|
7
|
+
* @param {number} [fv=0] - Future value.
|
|
8
|
+
* @param {0|1} [type=0] - Payment timing: 0 = end of period, 1 = beginning.
|
|
9
|
+
* @returns {number} Number of periods.
|
|
10
|
+
* @throws {RangeError} When calculation is impossible with the provided inputs.
|
|
11
|
+
*/
|
|
12
|
+
export function nper(rate, pmt, pv, fv = 0, type = 0) {
|
|
13
|
+
if (rate === 0) {
|
|
14
|
+
if (pmt === 0) {
|
|
15
|
+
throw new RangeError("Payment cannot be 0 when rate is 0.");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return -(pv + fv) / pmt;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const paymentAdjustment = type !== 0 ? pmt * (1 + rate) : pmt;
|
|
22
|
+
const paymentOverRate = paymentAdjustment / rate;
|
|
23
|
+
|
|
24
|
+
let futureValueTerm = -fv + paymentOverRate;
|
|
25
|
+
let presentValueTerm = pv + paymentOverRate;
|
|
26
|
+
|
|
27
|
+
// Ensure values are valid for logarithms.
|
|
28
|
+
if (futureValueTerm < 0 && presentValueTerm < 0) {
|
|
29
|
+
futureValueTerm *= -1;
|
|
30
|
+
presentValueTerm *= -1;
|
|
31
|
+
} else if (futureValueTerm <= 0 || presentValueTerm <= 0) {
|
|
32
|
+
throw new RangeError("Cannot calculate NPER with the provided values.");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const growthFactor = 1 + rate;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
(Math.log(futureValueTerm) - Math.log(presentValueTerm)) /
|
|
39
|
+
Math.log(growthFactor)
|
|
40
|
+
);
|
|
41
|
+
}
|
package/src/npv.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluates net present value across a bounded portion of a values array.
|
|
3
|
+
*
|
|
4
|
+
* @param {number} rate - Discount rate per period.
|
|
5
|
+
* @param {number[]} values - Cash flow values.
|
|
6
|
+
* @param {number} [lowerBound=0] - Start index (inclusive).
|
|
7
|
+
* @param {number} [upperBound=values.length - 1] - End index (inclusive).
|
|
8
|
+
* @returns {number} The evaluated NPV for the specified range.
|
|
9
|
+
*/
|
|
10
|
+
function evalNpv(rate, values, lowerBound = 0, upperBound = values.length - 1) {
|
|
11
|
+
let discountFactor = 1;
|
|
12
|
+
let total = 0;
|
|
13
|
+
|
|
14
|
+
for (let index = lowerBound; index <= upperBound; index += 1) {
|
|
15
|
+
const value = values[index];
|
|
16
|
+
discountFactor += discountFactor * rate;
|
|
17
|
+
total += value / discountFactor;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return total;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Calculates the net present value of a series of cash flows.
|
|
25
|
+
*
|
|
26
|
+
* @param {number} rate - Discount rate per period.
|
|
27
|
+
* @param {...number} values - Cash flow values.
|
|
28
|
+
* @returns {number} The net present value.
|
|
29
|
+
* @throws {RangeError} When there are no cash flow values or rate is invalid.
|
|
30
|
+
*/
|
|
31
|
+
export function npv(rate, ...values) {
|
|
32
|
+
if (values.length < 1) {
|
|
33
|
+
throw new RangeError("Invalid values.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (rate === -1) {
|
|
37
|
+
throw new RangeError("Invalid rate.");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return evalNpv(rate, values, 0, values.length - 1);
|
|
41
|
+
}
|
package/src/pmt.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the periodic payment for a loan or investment.
|
|
3
|
+
*
|
|
4
|
+
* @param {number} rate - The interest rate per period.
|
|
5
|
+
* @param {number} nper - The total number of payment periods.
|
|
6
|
+
* @param {number} pv - The present value.
|
|
7
|
+
* @param {number} [fv=0] - The future value, or remaining balance after the last payment. Defaults to 0.
|
|
8
|
+
* @param {0|1} [type=0] - Payment timing: 0 = end of period, 1 = beginning of period. Defaults to 0.
|
|
9
|
+
* @returns {number} The periodic payment amount.
|
|
10
|
+
*/
|
|
11
|
+
export function pmt(rate, nper, pv, fv = 0, type = 0) {
|
|
12
|
+
if (rate === 0) {
|
|
13
|
+
return (-fv - pv) / nper;
|
|
14
|
+
} else {
|
|
15
|
+
const paymentTimingFactor = type !== 0 ? 1 + rate : 1;
|
|
16
|
+
const interestFactor = 1 + rate;
|
|
17
|
+
const compoundFactor = Math.pow(interestFactor, nper);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
((-fv - pv * compoundFactor) /
|
|
21
|
+
(paymentTimingFactor * (compoundFactor - 1))) *
|
|
22
|
+
rate
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/ppmt.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ipmt } from "./ipmt.js";
|
|
2
|
+
import { pmt } from "./pmt.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Calculates the principal portion of a payment for a specific period.
|
|
6
|
+
*
|
|
7
|
+
* @param {number} rate - The interest rate per period.
|
|
8
|
+
* @param {number} per - The target period (1-based).
|
|
9
|
+
* @param {number} nper - The total number of payment periods.
|
|
10
|
+
* @param {number} pv - The present value.
|
|
11
|
+
* @param {number} [futureValue=0] - The future value.
|
|
12
|
+
* @param {0|1} [type=0] - Payment timing: 0 = end of period, 1 = beginning of period.
|
|
13
|
+
* @returns {number} The principal payment for the specified period.
|
|
14
|
+
* @throws {RangeError} When `per` is outside the valid range.
|
|
15
|
+
*/
|
|
16
|
+
export function ppmt(rate, per, nper, pv, futureValue = 0, type = 0) {
|
|
17
|
+
if (per <= 0 || per >= nper + 1) {
|
|
18
|
+
throw new RangeError("Invalid period.");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const periodicPayment = pmt(rate, nper, pv, futureValue, type);
|
|
22
|
+
const interestPayment = ipmt(rate, per, nper, pv, futureValue, type);
|
|
23
|
+
|
|
24
|
+
return periodicPayment - interestPayment;
|
|
25
|
+
}
|
package/src/pv.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the present value of an investment based on a series of regular payments and a constant interest rate.
|
|
3
|
+
*
|
|
4
|
+
* @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; cannot change over the life of the annuity.
|
|
7
|
+
* @param {number} fv - The future value, or a cash balance you want to attain after the last payment is made. Defaults to 0.
|
|
8
|
+
* @param {0|1} [type=0] - The number 0 or 1 and indicates when payments are due. 0 = end of period, 1 = beginning of period. Defaults to 0.
|
|
9
|
+
* @returns {number} The present value of the investment.
|
|
10
|
+
*/
|
|
11
|
+
export function pv(rate, nper, pmt, fv = 0, type = 0) {
|
|
12
|
+
if (rate === 0) {
|
|
13
|
+
return -pmt * nper - fv;
|
|
14
|
+
} else {
|
|
15
|
+
const paymentTimingFactor = type !== 0 ? 1 + rate : 1;
|
|
16
|
+
const interestFactor = 1 + rate;
|
|
17
|
+
const compoundFactor = Math.pow(interestFactor, nper);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
-(fv + pmt * paymentTimingFactor * ((compoundFactor - 1) / rate)) /
|
|
21
|
+
compoundFactor
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/rate.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluates the RATE equation for a candidate rate.
|
|
3
|
+
*
|
|
4
|
+
* @param {number} rate - Candidate rate.
|
|
5
|
+
* @param {number} nper - Total number of periods.
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
function evalRate(rate, nper, pmt, pv, fv = 0, type = 0) {
|
|
13
|
+
if (rate === 0) {
|
|
14
|
+
return pv + pmt * nper + fv;
|
|
15
|
+
} else {
|
|
16
|
+
const interestFactor = 1 + rate;
|
|
17
|
+
const compoundFactor = Math.pow(interestFactor, nper);
|
|
18
|
+
const paymentTimingFactor = type !== 0 ? 1 + rate : 1;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
pv * compoundFactor +
|
|
22
|
+
(pmt * paymentTimingFactor * (compoundFactor - 1)) / rate +
|
|
23
|
+
fv
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Calculates the interest rate per period using iterative approximation.
|
|
30
|
+
*
|
|
31
|
+
* @param {number} nper - Total number of periods.
|
|
32
|
+
* @param {number} pmt - Periodic payment.
|
|
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.
|
|
39
|
+
*/
|
|
40
|
+
export function rate(nper, pmt, pv, fv = 0, type = 0, guess = 0.1) {
|
|
41
|
+
if (nper <= 0) {
|
|
42
|
+
throw new RangeError("Invalid period.");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const epsilonMax = 0.0000001;
|
|
46
|
+
const step = 0.00001;
|
|
47
|
+
const iterationMax = 128;
|
|
48
|
+
|
|
49
|
+
let rate0 = guess;
|
|
50
|
+
let y0 = evalRate(rate0, nper, pmt, pv, fv, type);
|
|
51
|
+
|
|
52
|
+
let rate1 = y0 > 0 ? rate0 / 2 : rate0 * 2;
|
|
53
|
+
let y1 = evalRate(rate1, nper, pmt, pv, fv, type);
|
|
54
|
+
|
|
55
|
+
for (let iteration = 0; iteration < iterationMax; iteration += 1) {
|
|
56
|
+
if (y1 === y0) {
|
|
57
|
+
rate0 = rate0 < rate1 ? rate0 - step : rate0 + step;
|
|
58
|
+
y0 = evalRate(rate0, nper, pmt, pv, fv, type);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (y1 === y0) {
|
|
62
|
+
throw new RangeError("Cannot calculate RATE with the provided values.");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
rate0 = rate1 - ((rate1 - rate0) * y1) / (y1 - y0);
|
|
66
|
+
y0 = evalRate(rate0, nper, pmt, pv, fv, type);
|
|
67
|
+
|
|
68
|
+
if (Math.abs(y0) < epsilonMax) {
|
|
69
|
+
return rate0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const nextY = y0;
|
|
73
|
+
y0 = y1;
|
|
74
|
+
y1 = nextY;
|
|
75
|
+
|
|
76
|
+
const nextRate = rate0;
|
|
77
|
+
rate0 = rate1;
|
|
78
|
+
rate1 = nextRate;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
throw new RangeError("Maximum iterations exceeded while calculating RATE.");
|
|
82
|
+
}
|