@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 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.7331680465
33
+ // 2325.733168046526
34
34
 
35
35
  const periodicRate = rate(60, 500, -25000);
36
- // 0.00618341316125388
36
+ // 0.006183413161254404
37
37
 
38
38
  const internalRate = irr([-1500, 500, 500, 500, 500]);
39
- // 0.125898324962364
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` | 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 |
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
- ### `pv(rate, nper, pmt, fv = 0, type = 0)`
68
+ ### `fv(rate, nper, pmt, pv, type = 0)`
69
69
 
70
- Returns the present value of an investment, or the total amount that a series of
71
- future payments is worth now.
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
- ### `fv(rate, nper, pmt, pv, type = 0)`
74
+ ### `ipmt(rate, per, nper, pv, fv = 0, type = 0)`
74
75
 
75
- Returns the future value of an investment based on periodic, equal, payments and
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
- ### `pmt(rate, nper, pv, fv = 0, type = 0)`
79
+ ### `irr(values, guess = 0.1)`
79
80
 
80
- Calculates the payment for a loan based on a constant stream of equal payments
81
- and a constant interest rate.
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
- Number of periods.
90
+ Calculates the number of periods for an investment based on periodic, constant
91
+ payments and a constant interest rate.
86
92
 
87
- ### `ipmt(rate, per, nper, pv, fv = 0, type = 0)`
93
+ ### `npv(rate, ...values)`
88
94
 
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.
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
- ### `ppmt(rate, per, nper, pv, fv = 0, type = 0)`
98
+ ### `pmt(rate, nper, pv, fv = 0, type = 0)`
93
99
 
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.
100
+ Calculates the payment for a loan based on constant payments and a constant
101
+ interest rate.
96
102
 
97
- ### `rate(nper, pmt, pv, fv = 0, type = 0, guess = 0.1)`
103
+ ### `ppmt(rate, per, nper, pv, fv = 0, type = 0)`
98
104
 
99
- Returns the interest rate per period for a loan or investment (iterative solve).
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
- ### `npv(rate, ...values)`
108
+ ### `pv(rate, nper, pmt, fv = 0, type = 0)`
102
109
 
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).
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
- ### `irr(values, guess = 0.1)`
114
+ ### `rate(nper, pmt, pv, fv = 0, type = 0, guess = 0.1)`
108
115
 
109
- Returns the internal rate of return for a series of cash flows.
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
- A couple of items to note about this formula:
121
+ ### `xirr(values, dates, guess = 0.1)`
112
122
 
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.
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
- Example usage:
127
+ ### `xnpv(rate, values, dates)`
118
128
 
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
- ```
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.0.0",
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
- "test": "vitest --run",
24
- "lint": "eslint .",
23
+ "format": "prettier --write .",
24
+ "lint:format": "prettier --check .",
25
25
  "lint:types": "tsc --noEmit",
26
- "format": "prettier --write ."
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 or loan.
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
- * @param {number} pv - The present value.
8
- * @param {0|1} [type=0] - Payment timing: 0 = end of period, 1 = beginning of period.
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
- (pmt / rate) * paymentTimingFactor * (compoundFactor - 1)
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
- * Calculates the interest portion of a payment for a specific period.
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 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.
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, futureValue = 0, type = 0) {
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, futureValue, type);
47
+ const periodicPayment = pmt(rate, nper, pv, fv, type);
27
48
  const adjustedPresentValue = type !== 0 ? pv + periodicPayment : pv;
28
- const periodFutureValue = fv(
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
- * Evaluates present value for an IRR iteration guess.
2
+ * Computes the net present value (NPV) of a series of cash flows at a given
3
+ * discount rate.
3
4
  *
4
- * @param {number[]} values - Cash flow values.
5
- * @param {number} [guess=0.1] - Rate guess.
6
- * @returns {number} Present value at the supplied guess.
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 - Cash flow values where negatives are investments and positives are returns.
30
- * @param {number} [guess=0.1] - Initial guess for the IRR iteration.
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 converge.
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/loan.
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 - 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.
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
- Math.log(growthFactor)
53
+ Math.log(growthFactor),
40
54
  );
41
55
  }
package/src/npv.js CHANGED
@@ -1,11 +1,25 @@
1
1
  /**
2
- * Evaluates net present value across a bounded portion of a values array.
2
+ * Calculates the net present value (NPV) of a subset of cash flows at a
3
+ * specified discount rate.
3
4
  *
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.
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 a series of cash flows.
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
- * @param {number} rate - Discount rate per period.
27
- * @param {...number} values - Cash flow values.
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 periodic payment for a loan or investment.
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 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.
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
- rate
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 principal portion of a payment for a specific period.
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 - 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.
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 series of regular payments and a constant interest rate.
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
- * @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.
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
- compoundFactor
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 RATE equation for a candidate rate.
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
- * @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.
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 using iterative approximation.
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
- * @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.
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
+ }