@travishorn/financejs 1.0.0 → 1.10.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/src/sln.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Calculates the straight-line depreciation of an asset for one period.
3
+ *
4
+ * @param {number} cost - The initial cost of the asset.
5
+ * @param {number} salvage - The value at the end of the depreciation (sometimes
6
+ * called the salvage value of the asset).
7
+ * @param {number} life - The number of periods over which the asset is
8
+ * depreciated (sometimes called the useful life of the asset).
9
+ * @returns {number} the straight-line depreciation
10
+ *
11
+ * @example
12
+ * sln(30000, 7500, 10); // 2250
13
+ */
14
+ export function sln(cost, salvage, life) {
15
+ return (cost - salvage) / life;
16
+ }
package/src/syd.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Calculates the sum-of-years' digits depreciation of an asset for a specified
3
+ * period.
4
+ *
5
+ * Remarks:
6
+ * - The calculation is `((cost - salvage) * (life - per + 1) * 2) / (life)(life
7
+ * + 1)`.
8
+ *
9
+ * @param {number} cost - The initial cost of the asset.
10
+ * @param {number} salvage - The value at the end of the depreciation (sometimes
11
+ * called the salvage value of the asset).
12
+ * @param {number} life - The number of periods over which the asset is
13
+ * depreciated (sometimes called the useful life of the asset).
14
+ * @param {number} per - The period and must use the same units as `life`.
15
+ * @returns {number} the straight-line depreciation
16
+ *
17
+ * @example
18
+ * syd(30000, 7500, 10, 1); // 4090.91
19
+ */
20
+ export function syd(cost, salvage, life, per) {
21
+ return ((cost - salvage) * (life - per + 1) * 2) / (life * (life + 1));
22
+ }
package/src/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
+ }