@travishorn/financejs 1.10.0 → 1.17.1
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 +7 -142
- package/package.json +3 -1
- package/src/dollarde.js +47 -0
- package/src/dollarfr.js +40 -0
- package/src/fvschedule.js +41 -0
- package/src/index.js +8 -0
- package/src/ispmt.js +53 -0
- package/src/pduration.js +19 -0
- package/src/rri.js +18 -0
- package/src/vdb.js +101 -0
- package/src/yield_.js +385 -0
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,145 +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
|
-
| `cost` | The initial cost of the asset. |
|
|
59
|
-
| `dates` | A schedule of payment dates that corresponds to the cash flow payments. The first payment date indicates the beginning of the schedule of payments. All other dates must be later than this date, but they may occur in any order. |
|
|
60
|
-
| `effectRate` | The effective interest rate. |
|
|
61
|
-
| `endPeriod` | The last period in the calculation. |
|
|
62
|
-
| `factor` | The rate at which the balance declines. |
|
|
63
|
-
| `financeRate` | The interest rate you pay on the money used in the cash flows. |
|
|
64
|
-
| `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. |
|
|
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
|
-
| `life` | The number of periods over which the asset is depreciated (sometimes called the useful life of the asset). |
|
|
67
|
-
| `month` | The number of months in the first year. |
|
|
68
|
-
| `nominalRate` | The nominal interest rate. |
|
|
69
|
-
| `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`. |
|
|
70
|
-
| `npery` | The number of compounding periods per year. |
|
|
71
|
-
| `per` | The period for which you want to find the interest and must be in the range `1` to `nper`. |
|
|
72
|
-
| `period` | The period for which you want to calculate the depreciation. Period must use the same units as `life`. |
|
|
73
|
-
| `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`. |
|
|
74
|
-
| `pv` | The present value, or the lump-sum amount that a series of future payments is worth right now. |
|
|
75
|
-
| `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. |
|
|
76
|
-
| `reinvestRate` | The interest rate you receive on the cash flows as you reinvest them. |
|
|
77
|
-
| `salvage` | The value at the end of the depreciation (sometimes called the salvage value of the asset). |
|
|
78
|
-
| `startPeriod` | The first period in the calculation. Payment periods are numbered beginning with 1. |
|
|
79
|
-
| `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. |
|
|
80
|
-
| `values` | Array of cash flows, where each entry represents a payment (negative) or income (positive) at a regular interval. |
|
|
81
|
-
|
|
82
|
-
### `cumipmt(rate, nper, pv, startPeriod, endPeriod, type)`
|
|
83
|
-
|
|
84
|
-
Calculates the the cumulative interest paid on a loan between a start period and
|
|
85
|
-
an end period.
|
|
86
|
-
|
|
87
|
-
### `cumprinc(rate, nper, pv, startPeriod, endPeriod, type)`
|
|
88
|
-
|
|
89
|
-
Calculates the cumulative principal paid on a loan between a start period and an
|
|
90
|
-
end period.
|
|
91
|
-
|
|
92
|
-
### `db(cost, salvage, life, period, month = 12)`
|
|
93
|
-
|
|
94
|
-
Calculates the depreciation of an asset for a specified period using the
|
|
95
|
-
fixed-declining balance method.
|
|
96
|
-
|
|
97
|
-
### `ddb(cost, salvage, life, period, factor = 2)`
|
|
98
|
-
|
|
99
|
-
Calculates the depreciation of an asset for a specified period using the
|
|
100
|
-
double-declining balance method or some other method you specify.
|
|
101
|
-
|
|
102
|
-
### `effect(nominalRate, npery)`
|
|
103
|
-
|
|
104
|
-
Calculates the effective annual interest rate, given the nominal annual interest
|
|
105
|
-
rate and the number of compounding periods per year.
|
|
106
|
-
|
|
107
|
-
### `fv(rate, nper, pmt, pv, type = 0)`
|
|
108
|
-
|
|
109
|
-
Calculates the future value of an investment based on a constant interest rate.
|
|
110
|
-
You can use FV with either periodic, constant payments, or a single lump sum
|
|
111
|
-
payment.
|
|
112
|
-
|
|
113
|
-
### `ipmt(rate, per, nper, pv, fv = 0, type = 0)`
|
|
114
|
-
|
|
115
|
-
Returns the interest payment for a given period for an investment based on
|
|
116
|
-
periodic, constant payments and a constant interest rate.
|
|
117
|
-
|
|
118
|
-
### `irr(values, guess = 0.1)`
|
|
119
|
-
|
|
120
|
-
Calculates the internal rate of return for a series of cash flows represented by
|
|
121
|
-
the numbers in `values`. These cash flows do not have to be even, as they would
|
|
122
|
-
be for an annuity. However, the cash flows must occur at regular intervals, such
|
|
123
|
-
as monthly or annually. The internal rate of return is the interest rate
|
|
124
|
-
received for an investment consisting of payments (negative values) and income
|
|
125
|
-
(positive values) that occur at regular periods.
|
|
126
|
-
|
|
127
|
-
### `mirr(values, financeRate, reinvestRate)`
|
|
128
|
-
|
|
129
|
-
Calculates the modified internal rate of return for a series of periodic cash
|
|
130
|
-
flows. Considers both the cost of the investment and the interest received on
|
|
131
|
-
reinvestment of cash.
|
|
132
|
-
|
|
133
|
-
### `nominal(effectRate, npery)`
|
|
134
|
-
|
|
135
|
-
Calculates the nominal annual interest rate, given the effective rate and the
|
|
136
|
-
number of compounding periods per year.
|
|
137
|
-
|
|
138
|
-
### `nper(rate, pmt, pv, fv = 0, type = 0)`
|
|
139
|
-
|
|
140
|
-
Calculates the number of periods for an investment based on periodic, constant
|
|
141
|
-
payments and a constant interest rate.
|
|
142
|
-
|
|
143
|
-
### `npv(rate, ...values)`
|
|
144
|
-
|
|
145
|
-
Calculates the net present value of an investment by using a discount rate and a
|
|
146
|
-
series of future payments (negative values) and income (positive values).
|
|
147
|
-
|
|
148
|
-
### `pmt(rate, nper, pv, fv = 0, type = 0)`
|
|
149
|
-
|
|
150
|
-
Calculates the payment for a loan based on constant payments and a constant
|
|
151
|
-
interest rate.
|
|
152
|
-
|
|
153
|
-
### `ppmt(rate, per, nper, pv, fv = 0, type = 0)`
|
|
154
|
-
|
|
155
|
-
Calculates the payment on the principal for a given period for an investment
|
|
156
|
-
based on periodic, constant payments and a constant interest rate.
|
|
157
|
-
|
|
158
|
-
### `pv(rate, nper, pmt, fv = 0, type = 0)`
|
|
159
|
-
|
|
160
|
-
Calculates the present value of a loan or an investment, based on a constant
|
|
161
|
-
interest rate. You can use PV with either periodic, constant payments (such as a
|
|
162
|
-
mortgage or other loan), or a future value that's your investment goal.
|
|
163
|
-
|
|
164
|
-
### `rate(nper, pmt, pv, fv = 0, type = 0, guess = 0.1)`
|
|
165
|
-
|
|
166
|
-
Calculates the interest rate per period of an annuity. The rate is calculated by
|
|
167
|
-
iteration and can have zero or more solutions. If the successive results of this
|
|
168
|
-
function do not converge to within 0.0000001 after 128 iterations, a RangeError
|
|
169
|
-
is thrown.
|
|
170
|
-
|
|
171
|
-
### `sln(cost, salvage, life)`
|
|
172
|
-
|
|
173
|
-
Calculates the straight-line depreciation of an asset for one period.
|
|
174
|
-
|
|
175
|
-
### `syd(cost, salvage, life, per)`
|
|
176
|
-
|
|
177
|
-
Calculates the sum-of-years' digits depreciation of an asset for a specified
|
|
178
|
-
period.
|
|
179
|
-
|
|
180
|
-
### `xirr(values, dates, guess = 0.1)`
|
|
181
|
-
|
|
182
|
-
Calculates the internal rate of return for a schedule of cash flows that is
|
|
183
|
-
not necessarily periodic. To calculate the internal rate of return for a
|
|
184
|
-
series of periodic cash flows, use the `irr()` function.
|
|
185
|
-
|
|
186
|
-
### `xnpv(rate, values, dates)`
|
|
187
|
-
|
|
188
|
-
Calculates the net present value for a schedule of cash flows that is not
|
|
189
|
-
necessarily periodic.
|
|
190
|
-
|
|
191
55
|
## Error behavior
|
|
192
56
|
|
|
193
57
|
All functions will either return a number, or throw `RangeError` for invalid
|
|
@@ -240,13 +104,14 @@ to 8 decimal places), while using a modern JavaScript module API.
|
|
|
240
104
|
|
|
241
105
|
I want to add more [Excel financial
|
|
242
106
|
functions](https://support.microsoft.com/en-us/office/financial-functions-reference-5658d81e-6035-4f24-89c1-fbf124c2b1d8)
|
|
243
|
-
to the project. Since there are over 50 functions,
|
|
107
|
+
to the project. Since there are over 50 functions, broke them into "tiers."
|
|
108
|
+
Tiers 1-3 are complete.
|
|
244
109
|
|
|
245
110
|
- **Tier 1:** ✓pmt, ✓pv, ✓fv, ✓npv, ✓irr, ✓rate, ✓nper, ✓xnpv, ✓xirr
|
|
246
111
|
- **Tier 2:** ✓ipmt, ✓ppmt, ✓cumipmt, ✓cumprinc, ✓sln, ✓db, ✓ddb, ✓effect,
|
|
247
112
|
✓nominal, ✓syd, ✓mirr
|
|
248
|
-
- **Tier 3:** rri, pduration, vdb, fvschedule, dollarde, dollarfr, ispmt
|
|
249
|
-
- **Tier 4:** yield, price, duration, mduration, disc, intrate, received,
|
|
113
|
+
- **Tier 3:** ✓rri, ✓pduration, ✓vdb, ✓fvschedule, ✓dollarde, ✓dollarfr, ✓ispmt
|
|
114
|
+
- **Tier 4:** ✓yield, price, duration, mduration, disc, intrate, received,
|
|
250
115
|
pricedisc, pricemat, yielddisc, yieldmat
|
|
251
116
|
- **Tier 5:** all others
|
|
252
117
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travishorn/financejs",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.1",
|
|
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/dollarde.js
ADDED
|
@@ -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
|
+
}
|
package/src/dollarfr.js
ADDED
|
@@ -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
|
+
}
|
|
@@ -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
|
@@ -2,19 +2,27 @@ export { cumipmt } from "./cumipmt.js";
|
|
|
2
2
|
export { cumprinc } from "./cumprinc.js";
|
|
3
3
|
export { db } from "./db.js";
|
|
4
4
|
export { ddb } from "./ddb.js";
|
|
5
|
+
export { dollarde } from "./dollarde.js";
|
|
6
|
+
export { dollarfr } from "./dollarfr.js";
|
|
5
7
|
export { effect } from "./effect.js";
|
|
6
8
|
export { fv } from "./fv.js";
|
|
9
|
+
export { fvschedule } from "./fvschedule.js";
|
|
7
10
|
export { ipmt } from "./ipmt.js";
|
|
8
11
|
export { irr } from "./irr.js";
|
|
12
|
+
export { ispmt } from "./ispmt.js";
|
|
9
13
|
export { mirr } from "./mirr.js";
|
|
10
14
|
export { nominal } from "./nominal.js";
|
|
11
15
|
export { nper } from "./nper.js";
|
|
12
16
|
export { npv } from "./npv.js";
|
|
17
|
+
export { pduration } from "./pduration.js";
|
|
13
18
|
export { pmt } from "./pmt.js";
|
|
14
19
|
export { ppmt } from "./ppmt.js";
|
|
15
20
|
export { pv } from "./pv.js";
|
|
16
21
|
export { rate } from "./rate.js";
|
|
22
|
+
export { rri } from "./rri.js";
|
|
17
23
|
export { sln } from "./sln.js";
|
|
18
24
|
export { syd } from "./syd.js";
|
|
25
|
+
export { vdb } from "./vdb.js";
|
|
19
26
|
export { xirr } from "./xirr.js";
|
|
20
27
|
export { xnpv } from "./xnpv.js";
|
|
28
|
+
export { yield_ } from "./yield_.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/pduration.js
ADDED
|
@@ -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/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
|
+
}
|
package/src/yield_.js
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the yield on a security that pays periodic interest. Use to
|
|
3
|
+
* calculate bond yield.
|
|
4
|
+
*
|
|
5
|
+
* Remarks:
|
|
6
|
+
* - The settlement date is the date a buyer purchases a coupon, such as a bond.
|
|
7
|
+
* The maturity date is the date when a coupon expires. For example, suppose a
|
|
8
|
+
* 30-year bond is issued on January 1, 2008, and is purchased by a buyer six
|
|
9
|
+
* months later. The issue date would be January 1, 2008, the settlement date
|
|
10
|
+
* would be July 1, 2008, and the maturity date would be January 1, 2038,
|
|
11
|
+
* which is 30 years after the January 1, 2008, issue date.
|
|
12
|
+
* - `settlement`, `maturity`, `frequency`, and `basis` are truncated to
|
|
13
|
+
* integers.
|
|
14
|
+
* - If `settlement` or `maturity` is not a valid date, an error is thrown.
|
|
15
|
+
* - If `rate` < `0`, an error is thrown.
|
|
16
|
+
* - If `pr` ≤ `0` or if `redemption` ≤ `0`, an error is thrown.
|
|
17
|
+
* - If `frequency` is any number other than `1`, `2`, or `4`, an error is
|
|
18
|
+
* thrown.
|
|
19
|
+
* - If `basis` < `0` or if `basis` > `4` and error is thrown.
|
|
20
|
+
* - If `settlement` ≥ `maturity`, an error is thrown.
|
|
21
|
+
* - If there is one coupon period or less until redemption, the yield is
|
|
22
|
+
* calculated as follows: `yield = ((redemption/100 + rate/frequency) -
|
|
23
|
+
* (par/100 + (A/E * rate/frequency))) / (par/100 + (A/E * rate/frequency)) *
|
|
24
|
+
* (frequency * E) / DSR`, where:
|
|
25
|
+
* - `A` = number of days from the beginning of the coupon period to the
|
|
26
|
+
* settlement date (accrued days).
|
|
27
|
+
* - `DSR` = number of days from the settlement date to the redemption date.
|
|
28
|
+
* - `E` = number of days in the coupon period.
|
|
29
|
+
* - If there is more than one coupon period until redemption, the yield is
|
|
30
|
+
* calculated through a hundred iterations. The resolution uses the Newton
|
|
31
|
+
* method. The yield is changed until the estimated price given the yield is
|
|
32
|
+
* close to price.
|
|
33
|
+
*
|
|
34
|
+
* @param {Date} settlement - The security's settlement date. The security
|
|
35
|
+
* settlement date is the date after the issue date when the security is traded
|
|
36
|
+
* to the buyer.
|
|
37
|
+
* @param {Date} maturity - The security's maturity date. The maturity date is
|
|
38
|
+
* the date when the security expires.
|
|
39
|
+
* @param {number} rate - The security's annual coupon rate.
|
|
40
|
+
* @param {number} pr - The security's price per $100 face value.
|
|
41
|
+
* @param {number} redemption - The security's redemption value per $100 face
|
|
42
|
+
* value.
|
|
43
|
+
* @param {1|2|4} frequency - The number of coupon payments per year. For
|
|
44
|
+
* annual payments, frequency = `1`; for semiannual, frequency = `2`; for
|
|
45
|
+
* quarterly, frequency = `4`.
|
|
46
|
+
* @param {0|1|2|3|4} [basis=0] - The type of day count basis to use. `0` or
|
|
47
|
+
* omitted = US (NASD 30/360), `1` = actual/actual, `2` = actual/360, `3` =
|
|
48
|
+
* actual/365, `4` = European 30/360.
|
|
49
|
+
* @returns {number} The yield
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* yield_(new Date("2008-02-15"), new Date("2016-11-15"), 0.0575, 95.04287, 100, 2, 0); // 0.06500001
|
|
53
|
+
*/
|
|
54
|
+
export function yield_(
|
|
55
|
+
settlement,
|
|
56
|
+
maturity,
|
|
57
|
+
rate,
|
|
58
|
+
pr,
|
|
59
|
+
redemption,
|
|
60
|
+
frequency,
|
|
61
|
+
basis = 0,
|
|
62
|
+
) {
|
|
63
|
+
const settlementDate = toUtcDate(settlement);
|
|
64
|
+
const maturityDate = toUtcDate(maturity);
|
|
65
|
+
|
|
66
|
+
frequency = /** @type {1|2|4} */ (Math.trunc(frequency));
|
|
67
|
+
const basisNumber = Math.trunc(basis ?? 0);
|
|
68
|
+
|
|
69
|
+
if (rate < 0) {
|
|
70
|
+
throw new RangeError("Invalid rate.");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (pr <= 0 || redemption <= 0) {
|
|
74
|
+
throw new RangeError("Price and redemption must be greater than zero.");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (![1, 2, 4].includes(frequency)) {
|
|
78
|
+
throw new RangeError("Invalid frequency.");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (basisNumber < 0 || basisNumber > 4) {
|
|
82
|
+
throw new RangeError("Invalid basis.");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** @type {0|1|2|3|4} */
|
|
86
|
+
const normalizedBasis = /** @type {0|1|2|3|4} */ (basisNumber);
|
|
87
|
+
|
|
88
|
+
if (settlementDate >= maturityDate) {
|
|
89
|
+
throw new RangeError("Settlement must be before maturity.");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const monthsPerCoupon = 12 / frequency;
|
|
93
|
+
const { previousCouponDate, nextCouponDate } = getCouponBounds(
|
|
94
|
+
settlementDate,
|
|
95
|
+
maturityDate,
|
|
96
|
+
monthsPerCoupon,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const a = coupdaybs(previousCouponDate, settlementDate, normalizedBasis);
|
|
100
|
+
let dsc = coupdaysnc(settlementDate, nextCouponDate, normalizedBasis);
|
|
101
|
+
const e = coupdays(
|
|
102
|
+
previousCouponDate,
|
|
103
|
+
nextCouponDate,
|
|
104
|
+
frequency,
|
|
105
|
+
normalizedBasis,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (normalizedBasis === 3) {
|
|
109
|
+
dsc = e - a;
|
|
110
|
+
}
|
|
111
|
+
const n = couponsRemaining(nextCouponDate, maturityDate, monthsPerCoupon);
|
|
112
|
+
const coupon = (100 * rate) / frequency;
|
|
113
|
+
|
|
114
|
+
if (n <= 1) {
|
|
115
|
+
const numerator =
|
|
116
|
+
redemption / 100 +
|
|
117
|
+
rate / frequency -
|
|
118
|
+
(pr / 100 + (a / e) * (rate / frequency));
|
|
119
|
+
const denominator = pr / 100 + (a / e) * (rate / frequency);
|
|
120
|
+
const result = (numerator / denominator) * ((frequency * e) / dsc);
|
|
121
|
+
return normalizeNegativeZero(result);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** @param {number} candidateYield */
|
|
125
|
+
const priceFromYield = (candidateYield) => {
|
|
126
|
+
const base = 1 + candidateYield / frequency;
|
|
127
|
+
const firstExponent = dsc / e;
|
|
128
|
+
|
|
129
|
+
let presentValue = 0;
|
|
130
|
+
for (let k = 1; k <= n; k += 1) {
|
|
131
|
+
presentValue += coupon / Math.pow(base, k - 1 + firstExponent);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
presentValue += redemption / Math.pow(base, n - 1 + firstExponent);
|
|
135
|
+
presentValue -= (coupon * a) / e;
|
|
136
|
+
|
|
137
|
+
return presentValue;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const epsilon = 1e-11;
|
|
141
|
+
const maxIterations = 100;
|
|
142
|
+
const minYield = -frequency + 1e-10;
|
|
143
|
+
const step = 0.01;
|
|
144
|
+
|
|
145
|
+
let y0 = Math.max(rate, minYield + step);
|
|
146
|
+
let f0 = priceFromYield(y0) - pr;
|
|
147
|
+
let y1 = y0 + (f0 > 0 ? step : -step);
|
|
148
|
+
y1 = Math.max(y1, minYield);
|
|
149
|
+
let f1 = priceFromYield(y1) - pr;
|
|
150
|
+
|
|
151
|
+
for (let iteration = 0; iteration < maxIterations; iteration += 1) {
|
|
152
|
+
if (Math.abs(f1) < epsilon) {
|
|
153
|
+
return normalizeNegativeZero(y1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (f1 === f0) {
|
|
157
|
+
y0 = Math.max(y0 + step, minYield);
|
|
158
|
+
f0 = priceFromYield(y0) - pr;
|
|
159
|
+
if (f1 === f0) {
|
|
160
|
+
throw new RangeError(
|
|
161
|
+
"Cannot calculate YIELD with the provided values.",
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let y2 = y1 - ((y1 - y0) * f1) / (f1 - f0);
|
|
167
|
+
y2 = Math.max(y2, minYield);
|
|
168
|
+
const f2 = priceFromYield(y2) - pr;
|
|
169
|
+
|
|
170
|
+
if (Math.abs(f2) < epsilon) {
|
|
171
|
+
return normalizeNegativeZero(y2);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
y0 = y1;
|
|
175
|
+
f0 = f1;
|
|
176
|
+
y1 = y2;
|
|
177
|
+
f1 = f2;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
throw new RangeError("Maximum iterations exceeded while calculating YIELD.");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** @param {Date} date */
|
|
184
|
+
function lastDayOfMonthUtc(date) {
|
|
185
|
+
return new Date(
|
|
186
|
+
Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 0),
|
|
187
|
+
).getUTCDate();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Month arithmetic that preserves end-of-month behavior for coupon schedules.
|
|
192
|
+
*
|
|
193
|
+
* @param {Date} date
|
|
194
|
+
* @param {number} months
|
|
195
|
+
*/
|
|
196
|
+
function addMonthsUtc(date, months) {
|
|
197
|
+
const year = date.getUTCFullYear();
|
|
198
|
+
const month = date.getUTCMonth();
|
|
199
|
+
const day = date.getUTCDate();
|
|
200
|
+
const isEndOfMonth = day === lastDayOfMonthUtc(date);
|
|
201
|
+
|
|
202
|
+
const monthIndex = month + months;
|
|
203
|
+
const newYear = year + Math.floor(monthIndex / 12);
|
|
204
|
+
const newMonth = ((monthIndex % 12) + 12) % 12;
|
|
205
|
+
const monthEndDay = new Date(Date.UTC(newYear, newMonth + 1, 0)).getUTCDate();
|
|
206
|
+
const newDay = isEndOfMonth ? monthEndDay : Math.min(day, monthEndDay);
|
|
207
|
+
|
|
208
|
+
return new Date(Date.UTC(newYear, newMonth, newDay));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @param {Date} settlementDate
|
|
213
|
+
* @param {Date} maturityDate
|
|
214
|
+
* @param {number} monthsPerCoupon
|
|
215
|
+
*/
|
|
216
|
+
function getCouponBounds(settlementDate, maturityDate, monthsPerCoupon) {
|
|
217
|
+
let nextCouponDate = new Date(maturityDate.getTime());
|
|
218
|
+
let previousCouponDate = addMonthsUtc(nextCouponDate, -monthsPerCoupon);
|
|
219
|
+
|
|
220
|
+
while (settlementDate < previousCouponDate) {
|
|
221
|
+
nextCouponDate = previousCouponDate;
|
|
222
|
+
previousCouponDate = addMonthsUtc(nextCouponDate, -monthsPerCoupon);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Excel treats settlement on a coupon date as the start of the next period.
|
|
226
|
+
if (settlementDate >= nextCouponDate) {
|
|
227
|
+
previousCouponDate = nextCouponDate;
|
|
228
|
+
nextCouponDate = addMonthsUtc(nextCouponDate, monthsPerCoupon);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return { previousCouponDate, nextCouponDate };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @param {Date} nextCouponDate
|
|
236
|
+
* @param {Date} maturityDate
|
|
237
|
+
* @param {number} monthsPerCoupon
|
|
238
|
+
*/
|
|
239
|
+
function couponsRemaining(nextCouponDate, maturityDate, monthsPerCoupon) {
|
|
240
|
+
let n = 1;
|
|
241
|
+
let current = new Date(nextCouponDate.getTime());
|
|
242
|
+
|
|
243
|
+
while (current < maturityDate) {
|
|
244
|
+
current = addMonthsUtc(current, monthsPerCoupon);
|
|
245
|
+
n += 1;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return n;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param {Date} settlementDate
|
|
253
|
+
* @param {Date} nextCouponDate
|
|
254
|
+
* @param {0|1|2|3|4} basis
|
|
255
|
+
*/
|
|
256
|
+
function coupdaysnc(settlementDate, nextCouponDate, basis) {
|
|
257
|
+
if (basis === 0) {
|
|
258
|
+
return days360Us(settlementDate, nextCouponDate);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (basis === 4) {
|
|
262
|
+
return days360Eu(settlementDate, nextCouponDate);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return actualDays(settlementDate, nextCouponDate);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* @param {Date} previousCouponDate
|
|
270
|
+
* @param {Date} settlementDate
|
|
271
|
+
* @param {0|1|2|3|4} basis
|
|
272
|
+
*/
|
|
273
|
+
function coupdaybs(previousCouponDate, settlementDate, basis) {
|
|
274
|
+
if (basis === 0) {
|
|
275
|
+
return days360Us(previousCouponDate, settlementDate);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (basis === 4) {
|
|
279
|
+
return days360Eu(previousCouponDate, settlementDate);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return actualDays(previousCouponDate, settlementDate);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @param {Date} previousCouponDate
|
|
287
|
+
* @param {Date} nextCouponDate
|
|
288
|
+
* @param {number} frequency
|
|
289
|
+
* @param {0|1|2|3|4} basis
|
|
290
|
+
*/
|
|
291
|
+
function coupdays(previousCouponDate, nextCouponDate, frequency, basis) {
|
|
292
|
+
if (basis === 0 || basis === 4) {
|
|
293
|
+
return 360 / frequency;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (basis === 3) {
|
|
297
|
+
return 365 / frequency;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return actualDays(previousCouponDate, nextCouponDate);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Excel/NASD 30/360 day count.
|
|
305
|
+
*
|
|
306
|
+
* @param {Date} start
|
|
307
|
+
* @param {Date} end
|
|
308
|
+
*/
|
|
309
|
+
function days360Us(start, end) {
|
|
310
|
+
let d1 = start.getUTCDate();
|
|
311
|
+
let d2 = end.getUTCDate();
|
|
312
|
+
const m1 = start.getUTCMonth() + 1;
|
|
313
|
+
const m2 = end.getUTCMonth() + 1;
|
|
314
|
+
const y1 = start.getUTCFullYear();
|
|
315
|
+
const y2 = end.getUTCFullYear();
|
|
316
|
+
|
|
317
|
+
const startIsMonthEnd = d1 === lastDayOfMonthUtc(start);
|
|
318
|
+
const endIsMonthEnd = d2 === lastDayOfMonthUtc(end);
|
|
319
|
+
|
|
320
|
+
if (m1 === 2 && startIsMonthEnd) {
|
|
321
|
+
d1 = 30;
|
|
322
|
+
}
|
|
323
|
+
if (m2 === 2 && endIsMonthEnd && m1 === 2 && startIsMonthEnd) {
|
|
324
|
+
d2 = 30;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (d2 === 31 && d1 >= 30) {
|
|
328
|
+
d2 = 30;
|
|
329
|
+
}
|
|
330
|
+
if (d1 === 31) {
|
|
331
|
+
d1 = 30;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* European 30/360 day count.
|
|
339
|
+
*
|
|
340
|
+
* @param {Date} start
|
|
341
|
+
* @param {Date} end
|
|
342
|
+
*/
|
|
343
|
+
function days360Eu(start, end) {
|
|
344
|
+
let d1 = start.getUTCDate();
|
|
345
|
+
let d2 = end.getUTCDate();
|
|
346
|
+
|
|
347
|
+
if (d1 === 31) {
|
|
348
|
+
d1 = 30;
|
|
349
|
+
}
|
|
350
|
+
if (d2 === 31) {
|
|
351
|
+
d2 = 30;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const m1 = start.getUTCMonth() + 1;
|
|
355
|
+
const m2 = end.getUTCMonth() + 1;
|
|
356
|
+
const y1 = start.getUTCFullYear();
|
|
357
|
+
const y2 = end.getUTCFullYear();
|
|
358
|
+
|
|
359
|
+
return 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* @param {Date} start
|
|
364
|
+
* @param {Date} end
|
|
365
|
+
*/
|
|
366
|
+
function actualDays(start, end) {
|
|
367
|
+
const msPerDay = 24 * 60 * 60 * 1000;
|
|
368
|
+
return (end.getTime() - start.getTime()) / msPerDay;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** @param {Date} value */
|
|
372
|
+
function toUtcDate(value) {
|
|
373
|
+
if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
|
|
374
|
+
throw new RangeError("Settlement and maturity must be valid Date objects.");
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return new Date(
|
|
378
|
+
Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()),
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** @param {number} value */
|
|
383
|
+
function normalizeNegativeZero(value) {
|
|
384
|
+
return Object.is(value, -0) ? 0 : value;
|
|
385
|
+
}
|