@travishorn/financejs 1.1.0 → 1.16.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 CHANGED
@@ -39,6 +39,9 @@ const internalRate = irr([-1500, 500, 500, 500, 500]);
39
39
  // 0.12589832495374934
40
40
  ```
41
41
 
42
+ For full API documentation, visit
43
+ [https://travishorn.github.io/financejs/](https://travishorn.github.io/financejs/).
44
+
42
45
  ## Excel-style conventions
43
46
 
44
47
  - Outputs are not rounded automatically.
@@ -49,86 +52,6 @@ const internalRate = irr([-1500, 500, 500, 500, 500]);
49
52
  - `rate` must match period frequency (e.g., annual rate divided by 12 for
50
53
  monthly periods).
51
54
 
52
- ## API
53
-
54
- ### Input Variables
55
-
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
-
68
- ### `fv(rate, nper, pmt, pv, type = 0)`
69
-
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.
73
-
74
- ### `ipmt(rate, per, nper, pv, fv = 0, type = 0)`
75
-
76
- Returns the interest payment for a given period for an investment based on
77
- periodic, constant payments and a constant interest rate.
78
-
79
- ### `irr(values, guess = 0.1)`
80
-
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.
87
-
88
- ### `nper(rate, pmt, pv, fv = 0, type = 0)`
89
-
90
- Calculates the number of periods for an investment based on periodic, constant
91
- payments and a constant interest rate.
92
-
93
- ### `npv(rate, ...values)`
94
-
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).
97
-
98
- ### `pmt(rate, nper, pv, fv = 0, type = 0)`
99
-
100
- Calculates the payment for a loan based on constant payments and a constant
101
- interest rate.
102
-
103
- ### `ppmt(rate, per, nper, pv, fv = 0, type = 0)`
104
-
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.
107
-
108
- ### `pv(rate, nper, pmt, fv = 0, type = 0)`
109
-
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.
113
-
114
- ### `rate(nper, pmt, pv, fv = 0, type = 0, guess = 0.1)`
115
-
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.
120
-
121
- ### `xirr(values, dates, guess = 0.1)`
122
-
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.
126
-
127
- ### `xnpv(rate, values, dates)`
128
-
129
- Calculates the net present value for a schedule of cash flows that is not
130
- necessarily periodic.
131
-
132
55
  ## Error behavior
133
56
 
134
57
  All functions will either return a number, or throw `RangeError` for invalid
@@ -181,12 +104,13 @@ to 8 decimal places), while using a modern JavaScript module API.
181
104
 
182
105
  I want to add more [Excel financial
183
106
  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."
107
+ to the project. Since there are over 50 functions, broke them into "tiers."
108
+ Tiers 1-3 are complete.
185
109
 
186
110
  - **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
111
+ - **Tier 2:** ✓ipmt, ✓ppmt, cumipmt, cumprinc, sln, db, ddb, effect,
112
+ ✓nominal, ✓syd, ✓mirr
113
+ - **Tier 3:** rri, pduration, vdb, fvschedule, dollarde, dollarfr, ispmt
190
114
  - **Tier 4:** yield, price, duration, mduration, disc, intrate, received,
191
115
  pricedisc, pricemat, yielddisc, yieldmat
192
116
  - **Tier 5:** all others
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travishorn/financejs",
3
- "version": "1.1.0",
3
+ "version": "1.16.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,6 +20,7 @@
20
20
  "access": "public"
21
21
  },
22
22
  "scripts": {
23
+ "docs": "typedoc --entryPoints src/index.js --out docs",
23
24
  "format": "prettier --write .",
24
25
  "lint:format": "prettier --check .",
25
26
  "lint:types": "tsc --noEmit",
@@ -48,6 +49,7 @@
48
49
  "eslint-config-prettier": "^10.1.8",
49
50
  "globals": "^17.4.0",
50
51
  "prettier": "^3.8.1",
52
+ "typedoc": "^0.28.17",
51
53
  "typescript": "^5.9.3",
52
54
  "vitest": "^4.0.18"
53
55
  }
package/src/cumipmt.js ADDED
@@ -0,0 +1,58 @@
1
+ import { ipmt } from "./ipmt.js";
2
+
3
+ /**
4
+ * Calculates the cumulative interest paid on a loan between a start period and
5
+ * and end period.
6
+ *
7
+ * Remarks:
8
+ * - Make sure that you are consistent about the units you use for specifying
9
+ * `rate` and `nper`. If you make monthly payments on a four-year loan at an
10
+ * annual interest rate of 12 percent, use `0.12 / 12` for `rate` and `4 * 12`
11
+ * for `nper`. If you make annual payments on the same loan, use `0.12` for
12
+ * `rate` and `4` for `nper`.
13
+ * - If `rate` <= `0`, `nper` <= `0`, or `pv` <= `0`, this function throws a
14
+ * RangeError.
15
+ * - If `startPeriod` < `1`, `endPeriod` < `1`, or `startPeriod` >
16
+ * `endPeriod`, this function throws a RangeError.
17
+ *
18
+ * @param {number} rate - The interest rate.
19
+ * @param {number} nper - The total number of payment periods.
20
+ * @param {number} pv - The present value.
21
+ * @param {number} startPeriod - The first period in the calculation. Payment
22
+ * periods are numbered beginning with 1.
23
+ * @param {number} endPeriod - The last period in the calculation.
24
+ * @param {0|1} type - The timing of the payment. `0` (zero) = payment at
25
+ * the end of the period. `1` = payment at the beginning of the period.
26
+ * @returns {number} The cumulative interest paid
27
+ *
28
+ * @example
29
+ * cumipmt(0.09 / 12, 30 * 12, 125000, 13, 24, 0); // -11135.23213075
30
+ */
31
+ export function cumipmt(rate, nper, pv, startPeriod, endPeriod, type) {
32
+ // Input validation
33
+ if (rate <= 0 || nper <= 0 || pv <= 0) {
34
+ throw new RangeError("rate, nper, and pv must be > 0");
35
+ }
36
+
37
+ if (
38
+ startPeriod < 1 ||
39
+ endPeriod < 1 ||
40
+ startPeriod > endPeriod ||
41
+ startPeriod > nper ||
42
+ endPeriod > nper
43
+ ) {
44
+ throw new RangeError("Invalid startPeriod or endPeriod");
45
+ }
46
+
47
+ if (type !== 0 && type !== 1) {
48
+ throw new RangeError("type must be 0 or 1");
49
+ }
50
+
51
+ let cumInterest = 0;
52
+
53
+ for (let per = startPeriod; per <= endPeriod; per++) {
54
+ cumInterest += ipmt(rate, per, nper, pv, 0, type);
55
+ }
56
+
57
+ return cumInterest;
58
+ }
@@ -0,0 +1,62 @@
1
+ import { pmt } from "./pmt.js";
2
+ import { ipmt } from "./ipmt.js";
3
+
4
+ /**
5
+ * Calculates the cumulative principal paid on a loan between a start period and
6
+ * an end period.
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 an
11
+ * annual interest rate of 12 percent, use `0.12 / 12` for `rate` and `4 * 12`
12
+ * for `nper`. If you make annual payments on the same loan, use `0.12` for
13
+ * `rate` and `4` for `nper`.
14
+ * - If `rate` <= `0`, `nper` <= `0`, or `pv` <= `0`, this function throws a
15
+ * RangeError.
16
+ * - If `startPeriod` < `1`, `endPeriod` < `1`, or `startPeriod` > `endPeriod`,
17
+ * this function throws a RangeError.
18
+ *
19
+ * @param {number} rate - The interest rate.
20
+ * @param {number} nper - The total number of payment periods.
21
+ * @param {number} pv - The present value.
22
+ * @param {number} startPeriod - The first period in the calculation. Payment
23
+ * periods are numbered beginning with 1.
24
+ * @param {number} endPeriod - The last period in the calculation.
25
+ * @param {0|1} type - The timing of the payment. `0` (zero) = payment at the
26
+ * end of the period. `1` = payment at the beginning of the period.
27
+ * @returns {number} The cumulative interest paid
28
+ *
29
+ * @example
30
+ * cumprinc(0.09 / 12, 30 * 12, 125000, 13, 24, 0); // -934.10712342
31
+ */
32
+ export function cumprinc(rate, nper, pv, startPeriod, endPeriod, type) {
33
+ // Input validation
34
+ if (rate <= 0 || nper <= 0 || pv <= 0) {
35
+ throw new RangeError("rate, nper, and pv must be > 0");
36
+ }
37
+
38
+ if (
39
+ startPeriod < 1 ||
40
+ endPeriod < 1 ||
41
+ startPeriod > endPeriod ||
42
+ startPeriod > nper ||
43
+ endPeriod > nper
44
+ ) {
45
+ throw new RangeError("Invalid startPeriod or endPeriod");
46
+ }
47
+
48
+ if (type !== 0 && type !== 1) {
49
+ throw new RangeError("type must be 0 or 1");
50
+ }
51
+
52
+ let cumPrincipal = 0;
53
+
54
+ for (let per = startPeriod; per <= endPeriod; per++) {
55
+ // Principal = payment - interest for this period
56
+ const payment = pmt(rate, nper, pv, 0, type);
57
+ const interest = ipmt(rate, per, nper, pv, 0, type);
58
+ cumPrincipal += payment - interest;
59
+ }
60
+
61
+ return cumPrincipal;
62
+ }
package/src/db.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Calculates the depreciation of an asset for a specified period using the
3
+ * fixed-declining balance method.
4
+ *
5
+ * Remarks:
6
+ * - The fixed-declining balance method computes depreciation at a fixed rate.
7
+ * Uses the following formulas to calculate depreciation for a period: `(cost
8
+ * - total depreciation from prior periods) * rate`, where `rate = 1 -
9
+ * ((salvage / cost) ^ (1 / life))`, rounded to three decimal places.
10
+ * - Depreciation for the first and last periods is a special case. For the
11
+ * first period, this function uses this formula: `cost * rate * month / 12`
12
+ * - For the last period, DB uses this formula: `((cost - total depreciation
13
+ * from prior periods) * rate * (12 - month)) / 12`
14
+ *
15
+ * @param {number} cost - The initial cost of the asset.
16
+ * @param {number} salvage - The value at the end of the depreciation (sometimes
17
+ * called the salvage value of the asset).
18
+ * @param {number} life - The number of periods over which the asset is
19
+ * depreciated (sometimes called the useful life of the asset).
20
+ * @param {number} period - The period for which you want to calculate the
21
+ * depreciation. Period must use the same units as `life`.
22
+ * @param {number} [month=12] - The number of months in the first year. If month
23
+ * is omitted, it is assumed to be `12`.
24
+ * @returns {number} the depreciation
25
+ *
26
+ * @example
27
+ * db(1000000, 100000, 6, 1, 7); // 186083.33333333
28
+ */
29
+ export function db(cost, salvage, life, period, month = 12) {
30
+ // Calculate the fixed rate, rounded to 3 decimal places
31
+ const rate = +(1 - Math.pow(salvage / cost, 1 / life)).toFixed(3);
32
+
33
+ if (period === 1) {
34
+ // First period: prorated by month
35
+ return (cost * rate * month) / 12;
36
+ }
37
+
38
+ // Calculate value after first period (prorated)
39
+ let value = cost - (cost * rate * month) / 12;
40
+
41
+ // For periods > 2, apply normal declining balance for each period
42
+ for (let p = 2; p < period; p++) {
43
+ value -= value * rate;
44
+ }
45
+
46
+ // Determine if this is the last period (partial year)
47
+ // The last period is when the sum of months in all periods reaches 12*life
48
+ // For the test case, period 7 is the last period (since month=7, life=6)
49
+ const totalMonths = (period - 1) * 12 + month;
50
+ const isLastPeriod = totalMonths > life * 12;
51
+
52
+ let dep;
53
+ if (isLastPeriod) {
54
+ // Last period: prorate by (12 - month)
55
+ dep = (value * rate * (12 - month)) / 12;
56
+ } else {
57
+ dep = value * rate;
58
+ }
59
+ return dep;
60
+ }
package/src/ddb.js ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Calculates the depreciation of an asset for a specified period using the
3
+ * double-declining balance method or some other method you specify.
4
+ *
5
+ * Remarks:
6
+ * - The double-declining balance method computes depreciation at an accelerated
7
+ * rate. Depreciation is highest in the first period and decreases in
8
+ * successive periods. This function uses the following formula to calculate
9
+ * depreciation for a period: `Min( (cost - total depreciation from prior
10
+ * periods) * (factor/life), (cost - salvage - total depreciation from prior
11
+ * periods) )`
12
+ * - Change `factor` if you do not want to use the double-declining balance
13
+ * method.
14
+ *
15
+ * @param {number} cost - The initial cost of the asset.
16
+ * @param {number} salvage - The value at the end of the depreciation (sometimes
17
+ * called the salvage value of the asset). This value can be `0`.
18
+ * @param {number} life - The number of periods over which the asset is
19
+ * depreciated (sometimes called the useful life of the asset).
20
+ * @param {number} period - The period for which you want to calculate the
21
+ * depreciation. Period must use the same units as `life`.
22
+ * @param {number} [factor=2] - The rate at which the balance declines. If
23
+ * `factor` is omitted, it is assumed to be `2` (the double-declining balance
24
+ * method).
25
+ * @returns {number} the depreciation
26
+ * @throws {RangeError} When `period` is outside the valid range.
27
+ *
28
+ * @example
29
+ * ddb(2400, 300, 10 * 365, 1); // 1.31506849
30
+ */
31
+ export function ddb(cost, salvage, life, period, factor = 2) {
32
+ let accDep = 0;
33
+ let value = cost;
34
+
35
+ for (let p = 1; p <= period; p++) {
36
+ // Calculate depreciation for this period
37
+ let dep = Math.min(value * (factor / life), cost - salvage - accDep);
38
+
39
+ if (p === period) {
40
+ return dep;
41
+ }
42
+
43
+ accDep += dep;
44
+ value -= dep;
45
+ }
46
+
47
+ // Period is out of range
48
+ throw new RangeError("Invalid period");
49
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Converts a dollar price expressed as an integer part and a fraction part,
3
+ * such as `1.02`, into a dollar price expressed as a decimal number. Fractional
4
+ * dollar numbers are sometimes used for security prices.
5
+ *
6
+ * The fraction part of the value is divided by an integer that you specify. For
7
+ * example, if you want your price to be expressed to a precision of 1/16 of a
8
+ * dollar, you divide the fraction part by 16. In this case, `1.02` represents
9
+ * `$1.125 ($1 + 2/16 = $1.125)`.
10
+ *
11
+ * Remarks:
12
+ * - If `fraction` is not an integer, it is truncated.
13
+ * - If `fraction` is less than `0`, this function will throw a RangeError.
14
+ * - If `fraction` is greater than or equal to `0` and less than `1`, division
15
+ * by zero is impossible and this function will throw a RangeError.
16
+ *
17
+ * @param {number} fractionalDollar - A number expressed as an integer part and
18
+ * a fraction part, separated by a decimal symbol.
19
+ * @param {number} fraction - The integer to use in the denominator of the
20
+ * fraction.
21
+ * @returns {number} the converted dollar price expressed as a decimal number
22
+ *
23
+ * @example
24
+ * dollarde(1.02, 16); // 1.125
25
+ */
26
+ export function dollarde(fractionalDollar, fraction) {
27
+ if (typeof fraction !== "number" || isNaN(fraction) || !isFinite(fraction)) {
28
+ throw new RangeError("fraction must be a finite number");
29
+ }
30
+ if (fraction < 0) throw new RangeError("fraction must be >= 0");
31
+ if (fraction > 0 && fraction < 1)
32
+ throw new RangeError("fraction must be >= 1 or 0");
33
+
34
+ // Truncate fraction to integer
35
+ fraction = Math.trunc(fraction);
36
+
37
+ const sign = fractionalDollar < 0 ? -1 : 1;
38
+ const abs = Math.abs(fractionalDollar);
39
+ const intPart = Math.trunc(abs);
40
+ const fracPart = abs - intPart;
41
+
42
+ // Multiply the decimal piece by 10 to the power of the length of fraction digits
43
+ const power = Math.ceil(Math.log10(fraction));
44
+ const result = intPart + (fracPart * Math.pow(10, power)) / fraction;
45
+
46
+ return sign * result;
47
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Converts a decimal number to a fractional dollar number, such as a securities
3
+ * price.
4
+ *
5
+ * Remarks:
6
+ * - If `fraction` is not an integer, it is truncated.
7
+ * - If `fraction` is less than `0`, this function will throw a RangeError.
8
+ * - If `fraction` is `0`, division by zero is impossible and this function will
9
+ * throw a RangeError.
10
+ *
11
+ * @param {number} decimalDollar - A decimal number.
12
+ * @param {number} fraction - The integer to use in the denominator of the
13
+ * fraction.
14
+ * @returns {number} the converted dollar price expressed as a fractional dollar
15
+ * number
16
+ *
17
+ * @example
18
+ * dollarfr(1.125, 16); // 1.02
19
+ */
20
+ export function dollarfr(decimalDollar, fraction) {
21
+ if (typeof fraction !== "number" || isNaN(fraction) || !isFinite(fraction)) {
22
+ throw new RangeError("fraction must be a finite number");
23
+ }
24
+ if (fraction < 0) throw new RangeError("fraction must be >= 0");
25
+ if (fraction === 0) throw new RangeError("fraction must be >= 1 or 0");
26
+
27
+ // Truncate fraction to integer
28
+ fraction = Math.trunc(fraction);
29
+
30
+ const sign = decimalDollar < 0 ? -1 : 1;
31
+ const abs = Math.abs(decimalDollar);
32
+ const intPart = Math.trunc(abs);
33
+ const fracPart = abs - intPart;
34
+
35
+ // Use the same scale as dollarde() so this function is its algebraic inverse.
36
+ const power = Math.ceil(Math.log10(fraction));
37
+ const result = intPart + (fracPart * fraction) / Math.pow(10, power);
38
+
39
+ return sign * result;
40
+ }
package/src/effect.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Calculates the effective annual interest rate, given the nominal annual
3
+ * interest rate and the number of compounding periods per year.
4
+ *
5
+ * Remarks:
6
+ * - `npery` is truncated to an integer.
7
+ * - If either argument is nonnumeric, an error is thrown.
8
+ * - If `nominalRate` <= `0` or if `npery` < `1`, an error is thrown.
9
+ * - Rate is calculated as follows: `(1 + nominalRate / npery)^npery - 1`
10
+ * - This function is related to `nominal()` through `effectiveRate = (1 +
11
+ * (nominalRate / npery)) * npery - 1`.
12
+ *
13
+ * @param {number} nominalRate - The nominal interest rate.
14
+ * @param {number} npery - The number of compounding periods per year.
15
+ * @returns {number} the effective annual interest rate
16
+ *
17
+ * @example
18
+ * effect(0.0525, 4); // 0.05354267
19
+ */
20
+ export function effect(nominalRate, npery) {
21
+ if (
22
+ typeof nominalRate !== "number" ||
23
+ typeof npery !== "number" ||
24
+ isNaN(nominalRate) ||
25
+ isNaN(npery)
26
+ ) {
27
+ throw new TypeError("Both arguments must be numbers");
28
+ }
29
+
30
+ if (nominalRate <= 0) {
31
+ throw new RangeError("nominalRate must be > 0");
32
+ }
33
+
34
+ npery = Math.trunc(npery);
35
+
36
+ if (npery < 1) {
37
+ throw new RangeError("npery must be >= 1");
38
+ }
39
+
40
+ return Math.pow(1 + nominalRate / npery, npery) - 1;
41
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Calculates the future value of an initial principal after applying a series
3
+ * of compound interest rates. Use this function to calculate the future value
4
+ * of an investment with a variable or adjustable rate.
5
+ *
6
+ * Remarks:
7
+ * - The values in schedule can be numbers or `null`; any other value will cause
8
+ * this function to throw a RangeError. Null values are taken as zeros (no
9
+ * interest).
10
+ *
11
+ * @param {number} principal - The present value.
12
+ * @param {number[]} schedule - An array of interest rates to apply.
13
+ * @returns {number} the future value
14
+ *
15
+ * @example
16
+ * fvschedule(1, [0.09, 0.11, 0.1]); // 1.33089000
17
+ */
18
+ export function fvschedule(principal, schedule) {
19
+ if (typeof principal !== "number" || !Array.isArray(schedule)) {
20
+ throw new RangeError("Invalid input types");
21
+ }
22
+
23
+ let fv = principal;
24
+
25
+ for (let i = 0; i < schedule.length; ++i) {
26
+ let rate = schedule[i];
27
+ if (rate === null) {
28
+ rate = 0;
29
+ } else if (
30
+ typeof rate !== "number" ||
31
+ Number.isNaN(rate) ||
32
+ rate === Infinity ||
33
+ rate === -Infinity
34
+ ) {
35
+ throw new RangeError("Schedule must contain only finite numbers or null");
36
+ }
37
+ fv *= 1 + rate;
38
+ }
39
+
40
+ return fv;
41
+ }
package/src/index.js CHANGED
@@ -1,11 +1,27 @@
1
+ export { cumipmt } from "./cumipmt.js";
2
+ export { cumprinc } from "./cumprinc.js";
3
+ export { db } from "./db.js";
4
+ export { ddb } from "./ddb.js";
5
+ export { dollarde } from "./dollarde.js";
6
+ export { dollarfr } from "./dollarfr.js";
7
+ export { effect } from "./effect.js";
1
8
  export { fv } from "./fv.js";
9
+ export { fvschedule } from "./fvschedule.js";
2
10
  export { ipmt } from "./ipmt.js";
3
11
  export { irr } from "./irr.js";
12
+ export { ispmt } from "./ispmt.js";
13
+ export { mirr } from "./mirr.js";
14
+ export { nominal } from "./nominal.js";
4
15
  export { nper } from "./nper.js";
5
16
  export { npv } from "./npv.js";
17
+ export { pduration } from "./pduration.js";
6
18
  export { pmt } from "./pmt.js";
7
19
  export { ppmt } from "./ppmt.js";
8
20
  export { pv } from "./pv.js";
9
21
  export { rate } from "./rate.js";
22
+ export { rri } from "./rri.js";
23
+ export { sln } from "./sln.js";
24
+ export { syd } from "./syd.js";
25
+ export { vdb } from "./vdb.js";
10
26
  export { xirr } from "./xirr.js";
11
27
  export { xnpv } from "./xnpv.js";
package/src/ispmt.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Calculates the interest paid (or received) for the specified period of a loan
3
+ * (or investment) with even principal payments.
4
+ *
5
+ * Remarks:
6
+ * - Make sure that you are consistent about the units you use for specifying
7
+ * `rate` and `nper`. If you make monthly payments on a four-year loan at 12
8
+ * percent annual interest, use `.12/12` for `rate` and `4*12` for `nper`. If
9
+ * you make annual payments on the same loan, use `.12` for `rate` and `4` for
10
+ * `nper`.
11
+ * - For all the arguments, cash you pay out, such as deposits to savings, is
12
+ * represented by negative numbers. Cash you receive, such as dividend checks,
13
+ * is represented by positive numbers.
14
+ * - This function counts each period beginning with zero, not with one.
15
+ * - Most loans use a repayment schedule with even periodic payments. This
16
+ * function returns the interest payment for a given period for this type of
17
+ * loan.
18
+ * - Some loans use a repayment schedule with even principal payments. This
19
+ * function returns the interest payment for a given period for this type of
20
+ * loan.
21
+ * - The interest charge each period is equal to the `rate` times the unpaid
22
+ * balance for the previous period. And the payment each period is equal to
23
+ * the ven principal plus th einterest for the period.
24
+ *
25
+ * @param {number} rate - The interest rate for the investment.
26
+ * @param {number} per - The period for which you want to find the interest, and
27
+ * must be between `1` and `nper`.
28
+ * @param {number} nper - The total number of payment periods for the
29
+ * investment.
30
+ * @param {number} pv - The present value of the investment. For a loan, this is
31
+ * the loan amount.
32
+ * @returns {number} the interest paid (or received)
33
+ *
34
+ * @example
35
+ * ispmt(0.1, 0, 4, 4000); // -400
36
+ */
37
+ export function ispmt(rate, per, nper, pv) {
38
+ // Validate inputs
39
+ if (typeof rate !== "number" || isNaN(rate) || !isFinite(rate))
40
+ throw new RangeError("rate must be a finite number");
41
+ if (typeof per !== "number" || isNaN(per) || !isFinite(per))
42
+ throw new RangeError("per must be a finite number");
43
+ if (typeof nper !== "number" || isNaN(nper) || !isFinite(nper))
44
+ throw new RangeError("nper must be a finite number");
45
+ if (typeof pv !== "number" || isNaN(pv) || !isFinite(pv))
46
+ throw new RangeError("pv must be a finite number");
47
+ if (nper <= 0) throw new RangeError("nper must be > 0");
48
+ if (per < 0 || per >= nper)
49
+ throw new RangeError("per must be between 0 and nper-1");
50
+
51
+ // ISPIMT formula: -pv * rate * (1 - per / nper)
52
+ return -pv * rate * (1 - per / nper);
53
+ }
package/src/mirr.js ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Calculates the modified internal rate of return for a series of periodic cash
3
+ * flows. Considers both the cost of the investment and the interest received on
4
+ * reinvestment of cash.
5
+ *
6
+ * Remarks:
7
+ * - Uses the order of values to interpret the order of cash flows. Be sure to
8
+ * enter your payment and income values in the sequence you want and with the
9
+ * correct signs (positive values for cash received, negative values for cash
10
+ * paid).
11
+ * - If `n` is the number of cash flows in values, `frate` is the `financeRate`,
12
+ * and `rrate` is the `reinvestRate`, then the equation is: `((-NPV(rrate,
13
+ * values[positive]) * (1 + rrate)) / (NPV(frate, values[negative]) * (1 +
14
+ * frate)))^(1 / (n - 1)) - 1`
15
+ *
16
+ * @param {number[]} values - An array that contains numbers. These numbers
17
+ * represent a series of payments (negative values) and income (positive values)
18
+ * occurring at regular periods. Values must contain at least one positive value
19
+ * and one negative value to calculate the modified internal rate of return.
20
+ * Otherwise, an error is thrown (divide by zero).
21
+ * @param {number} [financeRate] - The interest rate you pay on the money used in the cash flows.
22
+ * @param {number} [reinvestRate] - The interest rate you receive on the cash flows as you reinvest them.
23
+ * @returns {number} the modified internal rate of return
24
+ * @throws {RangeError} If `values` is not an array of at least two elements, or if there are not both positive and negative cash flows.
25
+ * @throws {TypeError} If `financeRate` or `reinvestRate` is not a number.
26
+ *
27
+ * @example
28
+ * mirr([-120000, 39000, 30000, 21000, 37000, 46000], 0.1, 0.12); // 0.12609413
29
+ */
30
+ export function mirr(values, financeRate, reinvestRate) {
31
+ if (!Array.isArray(values) || values.length < 2) {
32
+ throw new RangeError("values must be an array with at least two elements");
33
+ }
34
+
35
+ if (typeof financeRate !== "number" || typeof reinvestRate !== "number") {
36
+ throw new TypeError("financeRate and reinvestRate must be numbers");
37
+ }
38
+
39
+ const n = values.length;
40
+ let fvPos = 0;
41
+ let pvNeg = 0;
42
+
43
+ for (let i = 0; i < n; i++) {
44
+ const v = values[i];
45
+ if (v > 0) {
46
+ fvPos += v * Math.pow(1 + reinvestRate, n - 1 - i);
47
+ } else if (v < 0) {
48
+ pvNeg += v * Math.pow(1 + financeRate, i);
49
+ }
50
+ }
51
+
52
+ if (fvPos === 0 || pvNeg === 0) {
53
+ throw new RangeError(
54
+ "At least one negative and one positive cash flow required",
55
+ );
56
+ }
57
+
58
+ return Math.pow(fvPos / -pvNeg, 1 / (n - 1)) - 1;
59
+ }
package/src/nominal.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Calculates the nominal annual interest rate, given the effective rate and the
3
+ * number of compounding periods per year.
4
+ *
5
+ * Remarks:
6
+ * - `npery` is truncated to an integer.
7
+ * - If either argument is nonnumeric, an error is thrown.
8
+ * - If `effectRate` <= `0` or if `npery` < `1`, an error is thrown.
9
+ * - `nominal()` is related to `effect()` through `effectiveRate = (1 +
10
+ * (nominalRate / npery)) * npery - 1`.
11
+ *
12
+ * @param {number} effectRate - The effective interest rate.
13
+ * @param {number} npery - The number of compounding periods per year.
14
+ * @returns {number} the nominal annual interest rate
15
+ *
16
+ * @example
17
+ * nominal(0.053543, 4); // 0.05250032
18
+ */
19
+ export function nominal(effectRate, npery) {
20
+ if (
21
+ typeof effectRate !== "number" ||
22
+ typeof npery !== "number" ||
23
+ isNaN(effectRate) ||
24
+ isNaN(npery)
25
+ ) {
26
+ throw new TypeError("Both arguments must be numbers");
27
+ }
28
+ if (effectRate <= 0) {
29
+ throw new RangeError("effectRate must be > 0");
30
+ }
31
+ npery = Math.trunc(npery);
32
+ if (npery < 1) {
33
+ throw new RangeError("npery must be >= 1");
34
+ }
35
+ return npery * (Math.pow(1 + effectRate, 1 / npery) - 1);
36
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Calculates the number of periods required by an investment to reach a
3
+ * specified value.
4
+ *
5
+ * @param {number} rate - Rate is the interest rate per period.
6
+ * @param {number} pv - The present value of the investment.
7
+ * @param {number} fv - The future value of the investment.
8
+ * @returns {number} the number of periods required
9
+ * @throws {RangeError} if `rate` is less than or equal to zero
10
+ * @throws {RangeError} if `pv` is zero
11
+ *
12
+ * @example
13
+ * pduration(0.025, 2000, 2200); // 3.86
14
+ */
15
+ export function pduration(rate, pv, fv) {
16
+ if (rate <= 0) throw new RangeError("Rate must be positive");
17
+ if (pv === 0) throw new RangeError("Present value cannot be zero");
18
+ return Math.log(fv / pv) / Math.log(1 + rate);
19
+ }
package/src/rri.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Calculates an equivalent interest rate for the growth of an investment.
3
+ *
4
+ * @param {number} nper - The number of periods for the investment.
5
+ * @param {number} pv - The present value of the investment.
6
+ * @param {number} fv - The future value of the investment.
7
+ * @returns {number} the equivalent interest rate
8
+ * @throws {RangeError} if `nper` is less than or equal to zero
9
+ * @throws {RangeError} if `pv` is zero
10
+ *
11
+ * @example
12
+ * rri(96, 10000, 11000); // 0.00099331
13
+ */
14
+ export function rri(nper, pv, fv) {
15
+ if (nper <= 0) throw new RangeError("Number of periods must be positive");
16
+ if (pv === 0) throw new RangeError("Present value cannot be zero");
17
+ return Math.pow(fv / pv, 1 / nper) - 1;
18
+ }
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/vdb.js ADDED
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Calculates the depreciation of an asset for any period you specify, including
3
+ * partial periods, using the double-declining balance method or some other
4
+ * method you specify. VDB stands for variable declining balance.
5
+ *
6
+ * @param {number} cost - The initial cost of the asset.
7
+ * @param {number} salvage - The value at the end of the depreciation (sometimes
8
+ * called the salvage value of the asset). This value can be `0`.
9
+ * @param {number} life - The number of periods over which the asset is
10
+ * depreciated (sometimes called the useful life of the asset).
11
+ * @param {number} startPeriod - The starting period for which you want to
12
+ * calculate the depreciation. `startPeriod` must use the same units as `life`.
13
+ * @param {number} endPeriod - The ending period for which you want to calculate
14
+ * the depreciation. `endPeriod` must use the same units as `life`.
15
+ * @param {number} [factor=2] -The rate at which the balance declines. If
16
+ * `factor` is omitted, it is assumed to be `2` (the double-declining balance
17
+ * method). Change factor if you do not want to use the double-declining balance
18
+ * method.
19
+ * @param {boolean} [noSwitch=false] - Whether to switch to straight-line
20
+ * depreciation when depreciation is greater than the declining balance
21
+ * calculation. If `noSwitch` is `true`, this function does not switch to
22
+ * straigh-line depreciation even when the depreciation is greater than the
23
+ * declining balance calculation. If `noSwitch` is `false`, this function
24
+ * switches to straight-line depreciation when depreciation is greater than the
25
+ * declining balance calculation.
26
+ * @returns {number} the depreciation
27
+ *
28
+ * @example
29
+ * vdb(2400, 300, 10 * 365, 0, 1); // 1.31506849
30
+ */
31
+ export function vdb(
32
+ cost,
33
+ salvage,
34
+ life,
35
+ startPeriod,
36
+ endPeriod,
37
+ factor = 2,
38
+ noSwitch = false,
39
+ ) {
40
+ // Input validation
41
+ if (cost < 0) throw new RangeError("cost must be >= 0");
42
+ if (salvage < 0) throw new RangeError("salvage must be >= 0");
43
+ if (life <= 0) throw new RangeError("life must be > 0");
44
+ if (factor <= 0) throw new RangeError("factor must be > 0");
45
+ if (startPeriod < 0) throw new RangeError("startPeriod must be >= 0");
46
+ if (endPeriod < 0) throw new RangeError("endPeriod must be >= 0");
47
+ if (startPeriod > endPeriod)
48
+ throw new RangeError("startPeriod must be <= endPeriod");
49
+ if (salvage >= cost) return 0;
50
+
51
+ // Clamp periods to [0, life]
52
+ startPeriod = Math.max(0, Math.min(startPeriod, life));
53
+ endPeriod = Math.max(0, Math.min(endPeriod, life));
54
+ if (startPeriod === endPeriod) return 0;
55
+
56
+ // Excel returns 0 for a fractional interval wholly within the final period
57
+ // when it ends exactly at life (e.g. start=9.5, end=10 with life=10).
58
+ if (!noSwitch && endPeriod === life && startPeriod > life - 1) return 0;
59
+
60
+ let totalDep = 0;
61
+ let period = Math.floor(startPeriod * 1e9) / 1e9; // avoid floating point issues
62
+
63
+ while (period < endPeriod) {
64
+ // Calculate the portion of the period to depreciate
65
+ let next = Math.min(Math.floor(period + 1), endPeriod);
66
+ let periodLength = next - period;
67
+
68
+ // Calculate depreciation for this period
69
+ let book = cost;
70
+ let accDep = 0;
71
+
72
+ for (let i = 0; i < Math.floor(period); ++i) {
73
+ let d = (book * factor) / life;
74
+
75
+ if (!noSwitch) {
76
+ let sl = (cost - accDep - salvage) / (life - i);
77
+ if (sl > d) d = sl;
78
+ }
79
+
80
+ if (book - d < salvage) d = book - salvage;
81
+ accDep += d;
82
+ book -= d;
83
+ }
84
+
85
+ // For the current period (may be partial)
86
+ let d = (book * factor) / life;
87
+
88
+ if (!noSwitch) {
89
+ let sl = (cost - accDep - salvage) / (life - period);
90
+ if (sl > d) d = sl;
91
+ }
92
+
93
+ if (book - d < salvage) d = book - salvage;
94
+
95
+ let dep = d * periodLength;
96
+ totalDep += dep;
97
+ period = next;
98
+ }
99
+
100
+ return totalDep;
101
+ }