@travishorn/financejs 1.18.0 → 1.19.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 CHANGED
@@ -113,7 +113,7 @@ Tiers 1-3 are complete.
113
113
  - **Tier 3:** ✓rri, ✓pduration, ✓vdb, ✓fvschedule, ✓dollarde, ✓dollarfr, ✓ispmt
114
114
  - **Tier 4:** ✓yield, ✓price, duration, mduration, disc, intrate, received,
115
115
  pricedisc, pricemat, yielddisc, yieldmat
116
- - **Tier 5:** all others
116
+ - **Tier 5:** ✓coupdaybs, ✓coupdays, ✓coupdaysnc, all others
117
117
 
118
118
  ## License
119
119
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travishorn/financejs",
3
- "version": "1.18.0",
3
+ "version": "1.19.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",
@@ -0,0 +1,74 @@
1
+ import {
2
+ actualDays,
3
+ days360Eu,
4
+ days360Us,
5
+ getCouponBounds,
6
+ toUtcDate,
7
+ } from "./util.js";
8
+
9
+ /**
10
+ * Returns the number of days from the beginning of the coupon period to the
11
+ * settlement date.
12
+ *
13
+ * Remarks:
14
+ * - `settlement`, `maturity`, `frequency`, and `basis` are truncated to
15
+ * integers.
16
+ * - If `settlement` or `maturity` is not a valid date, 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`, an error is thrown.
20
+ * - If `settlement` >= `maturity`, an error is thrown.
21
+ *
22
+ * @param {Date} settlement - The security's settlement date.
23
+ * @param {Date} maturity - The security's maturity date.
24
+ * @param {1|2|4} frequency - The number of coupon payments per year. For annual
25
+ * payments, frequency = `1`; for semiannual, frequency = `2`; for quarterly,
26
+ * frequency = `4`.
27
+ * @param {0|1|2|3|4} [basis=0] - The type of day count basis to use. `0` or
28
+ * omitted = US (NASD 30/360), `1` = actual/actual, `2` = actual/360, `3` =
29
+ * actual/365, `4` = European 30/360.
30
+ * @returns {number} The number of days from the beginning of the coupon period
31
+ * to the settlement date.
32
+ *
33
+ * @example
34
+ * coupdaybs(new Date("2011-01-25"), new Date("2011-11-15"), 2, 1); // 71
35
+ */
36
+ export function coupdaybs(settlement, maturity, frequency, basis = 0) {
37
+ const settlementDate = toUtcDate(settlement);
38
+ const maturityDate = toUtcDate(maturity);
39
+
40
+ frequency = /** @type {1|2|4} */ (Math.trunc(frequency));
41
+ const basisNumber = Math.trunc(basis ?? 0);
42
+
43
+ if (![1, 2, 4].includes(frequency)) {
44
+ throw new RangeError("Invalid frequency.");
45
+ }
46
+
47
+ if (basisNumber < 0 || basisNumber > 4) {
48
+ throw new RangeError("Invalid basis.");
49
+ }
50
+
51
+ /** @type {0|1|2|3|4} */
52
+ const normalizedBasis = /** @type {0|1|2|3|4} */ (basisNumber);
53
+
54
+ if (settlementDate >= maturityDate) {
55
+ throw new RangeError("Settlement must be before maturity.");
56
+ }
57
+
58
+ const monthsPerCoupon = 12 / frequency;
59
+ const { previousCouponDate } = getCouponBounds(
60
+ settlementDate,
61
+ maturityDate,
62
+ monthsPerCoupon,
63
+ );
64
+
65
+ if (normalizedBasis === 0) {
66
+ return days360Us(previousCouponDate, settlementDate);
67
+ }
68
+
69
+ if (normalizedBasis === 4) {
70
+ return days360Eu(previousCouponDate, settlementDate);
71
+ }
72
+
73
+ return actualDays(previousCouponDate, settlementDate);
74
+ }
@@ -0,0 +1,68 @@
1
+ import { actualDays, getCouponBounds, toUtcDate } from "./util.js";
2
+
3
+ /**
4
+ * Returns the number of days in the coupon period that contains the settlement
5
+ * date.
6
+ *
7
+ * Remarks:
8
+ * - `settlement`, `maturity`, `frequency`, and `basis` are truncated to
9
+ * integers.
10
+ * - If `settlement` or `maturity` is not a valid date, an error is thrown.
11
+ * - If `frequency` is any number other than `1`, `2`, or `4`, an error is
12
+ * thrown.
13
+ * - If `basis` < `0` or if `basis` > `4`, an error is thrown.
14
+ * - If `settlement` >= `maturity`, an error is thrown.
15
+ *
16
+ * @param {Date} settlement - The security's settlement date.
17
+ * @param {Date} maturity - The security's maturity date.
18
+ * @param {1|2|4} frequency - The number of coupon payments per year. For annual
19
+ * payments, frequency = `1`; for semiannual, frequency = `2`; for quarterly,
20
+ * frequency = `4`.
21
+ * @param {0|1|2|3|4} [basis=0] - The type of day count basis to use. `0` or
22
+ * omitted = US (NASD 30/360), `1` = actual/actual, `2` = actual/360, `3` =
23
+ * actual/365, `4` = European 30/360.
24
+ * @returns {number} The number of days in the coupon period that contains the
25
+ * settlement date.
26
+ *
27
+ * @example
28
+ * coupdays(new Date("2011-01-25"), new Date("2011-11-15"), 2, 1); // 184
29
+ */
30
+ export function coupdays(settlement, maturity, frequency, basis = 0) {
31
+ const settlementDate = toUtcDate(settlement);
32
+ const maturityDate = toUtcDate(maturity);
33
+
34
+ frequency = /** @type {1|2|4} */ (Math.trunc(frequency));
35
+ const basisNumber = Math.trunc(basis ?? 0);
36
+
37
+ if (![1, 2, 4].includes(frequency)) {
38
+ throw new RangeError("Invalid frequency.");
39
+ }
40
+
41
+ if (basisNumber < 0 || basisNumber > 4) {
42
+ throw new RangeError("Invalid basis.");
43
+ }
44
+
45
+ /** @type {0|1|2|3|4} */
46
+ const normalizedBasis = /** @type {0|1|2|3|4} */ (basisNumber);
47
+
48
+ if (settlementDate >= maturityDate) {
49
+ throw new RangeError("Settlement must be before maturity.");
50
+ }
51
+
52
+ const monthsPerCoupon = 12 / frequency;
53
+ const { previousCouponDate, nextCouponDate } = getCouponBounds(
54
+ settlementDate,
55
+ maturityDate,
56
+ monthsPerCoupon,
57
+ );
58
+
59
+ if (normalizedBasis === 0 || normalizedBasis === 2 || normalizedBasis === 4) {
60
+ return 360 / frequency;
61
+ }
62
+
63
+ if (normalizedBasis === 3) {
64
+ return 365 / frequency;
65
+ }
66
+
67
+ return actualDays(previousCouponDate, nextCouponDate);
68
+ }
@@ -0,0 +1,73 @@
1
+ import {
2
+ actualDays,
3
+ days360Eu,
4
+ days360Us,
5
+ getCouponBounds,
6
+ toUtcDate,
7
+ } from "./util.js";
8
+
9
+ /**
10
+ * Returns the number of days from the settlement date to the next coupon date.
11
+ *
12
+ * Remarks:
13
+ * - `settlement`, `maturity`, `frequency`, and `basis` are truncated to
14
+ * integers.
15
+ * - If `settlement` or `maturity` is not a valid date, an error is thrown.
16
+ * - If `frequency` is any number other than `1`, `2`, or `4`, an error is
17
+ * thrown.
18
+ * - If `basis` < `0` or if `basis` > `4`, an error is thrown.
19
+ * - If `settlement` >= `maturity`, an error is thrown.
20
+ *
21
+ * @param {Date} settlement - The security's settlement date.
22
+ * @param {Date} maturity - The security's maturity date.
23
+ * @param {1|2|4} frequency - The number of coupon payments per year. For annual
24
+ * payments, frequency = `1`; for semiannual, frequency = `2`; for quarterly,
25
+ * frequency = `4`.
26
+ * @param {0|1|2|3|4} [basis=0] - The type of day count basis to use. `0` or
27
+ * omitted = US (NASD 30/360), `1` = actual/actual, `2` = actual/360, `3` =
28
+ * actual/365, `4` = European 30/360.
29
+ * @returns {number} The number of days from the settlement date to the next
30
+ * coupon date.
31
+ *
32
+ * @example
33
+ * coupdaysnc(new Date("2011-01-25"), new Date("2011-11-15"), 2, 1); // 113
34
+ */
35
+ export function coupdaysnc(settlement, maturity, frequency, basis = 0) {
36
+ const settlementDate = toUtcDate(settlement);
37
+ const maturityDate = toUtcDate(maturity);
38
+
39
+ frequency = /** @type {1|2|4} */ (Math.trunc(frequency));
40
+ const basisNumber = Math.trunc(basis ?? 0);
41
+
42
+ if (![1, 2, 4].includes(frequency)) {
43
+ throw new RangeError("Invalid frequency.");
44
+ }
45
+
46
+ if (basisNumber < 0 || basisNumber > 4) {
47
+ throw new RangeError("Invalid basis.");
48
+ }
49
+
50
+ /** @type {0|1|2|3|4} */
51
+ const normalizedBasis = /** @type {0|1|2|3|4} */ (basisNumber);
52
+
53
+ if (settlementDate >= maturityDate) {
54
+ throw new RangeError("Settlement must be before maturity.");
55
+ }
56
+
57
+ const monthsPerCoupon = 12 / frequency;
58
+ const { nextCouponDate } = getCouponBounds(
59
+ settlementDate,
60
+ maturityDate,
61
+ monthsPerCoupon,
62
+ );
63
+
64
+ if (normalizedBasis === 0) {
65
+ return days360Us(settlementDate, nextCouponDate);
66
+ }
67
+
68
+ if (normalizedBasis === 4) {
69
+ return days360Eu(settlementDate, nextCouponDate);
70
+ }
71
+
72
+ return actualDays(settlementDate, nextCouponDate);
73
+ }
package/src/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ export { coupdaybs } from "./coupdaybs.js";
2
+ export { coupdays } from "./coupdays.js";
3
+ export { coupdaysnc } from "./coupdaysnc.js";
1
4
  export { cumipmt } from "./cumipmt.js";
2
5
  export { cumprinc } from "./cumprinc.js";
3
6
  export { db } from "./db.js";
package/src/price.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import {
2
- coupdaybs,
3
- coupdays,
4
- coupdaysnc,
2
+ actualDays,
5
3
  couponsRemaining,
6
4
  getCouponBounds,
7
5
  normalizeZero,
8
6
  toUtcDate,
9
7
  } from "./util.js";
8
+ import { coupdaybs } from "./coupdaybs.js";
9
+ import { coupdays } from "./coupdays.js";
10
+ import { coupdaysnc } from "./coupdaysnc.js";
10
11
 
11
12
  /**
12
13
  * Calculates the price per $100 face value of a security that pays periodic
@@ -106,14 +107,18 @@ export function price(
106
107
  monthsPerCoupon,
107
108
  );
108
109
 
109
- const a = coupdaybs(previousCouponDate, settlementDate, normalizedBasis);
110
- let dsc = coupdaysnc(settlementDate, nextCouponDate, normalizedBasis);
111
- const e = coupdays(
112
- previousCouponDate,
113
- nextCouponDate,
110
+ const a = coupdaybs(settlementDate, maturityDate, frequency, normalizedBasis);
111
+ let dsc = coupdaysnc(
112
+ settlementDate,
113
+ maturityDate,
114
114
  frequency,
115
115
  normalizedBasis,
116
116
  );
117
+ let e = coupdays(settlementDate, maturityDate, frequency, normalizedBasis);
118
+
119
+ if (normalizedBasis === 2) {
120
+ e = actualDays(previousCouponDate, nextCouponDate);
121
+ }
117
122
 
118
123
  if (normalizedBasis === 3) {
119
124
  dsc = e - a;
package/src/util.js CHANGED
@@ -204,96 +204,6 @@ export function couponsRemaining(
204
204
  return n;
205
205
  }
206
206
 
207
- /**
208
- * Computes days from settlement to next coupon date (`DSC`) by day-count basis.
209
- *
210
- * Basis mapping:
211
- * - `0`: US (NASD) 30/360
212
- * - `1`: Actual/actual
213
- * - `2`: Actual/360
214
- * - `3`: Actual/365
215
- * - `4`: European 30/360
216
- *
217
- * @param {Date} settlementDate - Settlement date.
218
- * @param {Date} nextCouponDate - Next coupon date.
219
- * @param {0|1|2|3|4} basis - Day-count basis code.
220
- * @returns {number} Days between settlement and next coupon under `basis`.
221
- *
222
- * @example
223
- * coupdaysnc(new Date("2024-01-15"), new Date("2024-04-15"), 0);
224
- */
225
- export function coupdaysnc(settlementDate, nextCouponDate, basis) {
226
- if (basis === 0) {
227
- return days360Us(settlementDate, nextCouponDate);
228
- }
229
-
230
- if (basis === 4) {
231
- return days360Eu(settlementDate, nextCouponDate);
232
- }
233
-
234
- return actualDays(settlementDate, nextCouponDate);
235
- }
236
-
237
- /**
238
- * Computes days from previous coupon date to settlement (`A`) by day-count
239
- * basis.
240
- *
241
- * Basis mapping:
242
- * - `0`: US (NASD) 30/360
243
- * - `1`: Actual/actual
244
- * - `2`: Actual/360
245
- * - `3`: Actual/365
246
- * - `4`: European 30/360
247
- *
248
- * @param {Date} previousCouponDate - Coupon date immediately before settlement.
249
- * @param {Date} settlementDate - Settlement date.
250
- * @param {0|1|2|3|4} basis - Day-count basis code.
251
- * @returns {number} Days between previous coupon and settlement under `basis`.
252
- *
253
- * @example
254
- * coupdaybs(new Date("2023-10-15"), new Date("2024-01-15"), 1);
255
- */
256
- export function coupdaybs(previousCouponDate, settlementDate, basis) {
257
- if (basis === 0) {
258
- return days360Us(previousCouponDate, settlementDate);
259
- }
260
-
261
- if (basis === 4) {
262
- return days360Eu(previousCouponDate, settlementDate);
263
- }
264
-
265
- return actualDays(previousCouponDate, settlementDate);
266
- }
267
-
268
- /**
269
- * Computes total days in the coupon period (`E`) by basis and payment
270
- * frequency.
271
- *
272
- * For 30/360 bases (`0` and `4`), period length is fixed at `360/frequency`.
273
- * For basis `3` (Actual/365), period length is fixed at `365/frequency`.
274
- * Otherwise, calendar days between coupon boundaries are used.
275
- *
276
- * @param {Date} previousCouponDate - Coupon date before settlement.
277
- * @param {Date} nextCouponDate - Coupon date after settlement.
278
- * @param {number} frequency - Coupon payments per year.
279
- * @param {0|1|2|3|4} basis - Day-count basis code.
280
- * @returns {number} Coupon period length in days.
281
- *
282
- * @example
283
- * coupdays(new Date("2024-01-01"), new Date("2024-07-01"), 2, 3); // 182.5
284
- */
285
- export function coupdays(previousCouponDate, nextCouponDate, frequency, basis) {
286
- if (basis === 0 || basis === 4) {
287
- return 360 / frequency;
288
- }
289
-
290
- if (basis === 3) {
291
- return 365 / frequency;
292
- }
293
-
294
- return actualDays(previousCouponDate, nextCouponDate);
295
- }
296
-
297
207
  /**
298
208
  * Converts a `Date` to a UTC-midnight date-only representation.
299
209
  *
package/src/yield_.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import {
2
- coupdaybs,
3
- coupdays,
4
- coupdaysnc,
2
+ actualDays,
5
3
  couponsRemaining,
6
4
  getCouponBounds,
7
5
  normalizeZero,
8
6
  toUtcDate,
9
7
  } from "./util.js";
8
+ import { coupdaybs } from "./coupdaybs.js";
9
+ import { coupdays } from "./coupdays.js";
10
+ import { coupdaysnc } from "./coupdaysnc.js";
10
11
 
11
12
  /**
12
13
  * Calculates the yield on a security that pays periodic interest. Use to
@@ -106,14 +107,18 @@ export function yield_(
106
107
  monthsPerCoupon,
107
108
  );
108
109
 
109
- const a = coupdaybs(previousCouponDate, settlementDate, normalizedBasis);
110
- let dsc = coupdaysnc(settlementDate, nextCouponDate, normalizedBasis);
111
- const e = coupdays(
112
- previousCouponDate,
113
- nextCouponDate,
110
+ const a = coupdaybs(settlementDate, maturityDate, frequency, normalizedBasis);
111
+ let dsc = coupdaysnc(
112
+ settlementDate,
113
+ maturityDate,
114
114
  frequency,
115
115
  normalizedBasis,
116
116
  );
117
+ let e = coupdays(settlementDate, maturityDate, frequency, normalizedBasis);
118
+
119
+ if (normalizedBasis === 2) {
120
+ e = actualDays(previousCouponDate, nextCouponDate);
121
+ }
117
122
 
118
123
  if (normalizedBasis === 3) {
119
124
  dsc = e - a;