name:
src/Formulas/Financial.ts
-rw-r--r--
35663
1import {
2 ArgsChecker
3} from "../Utilities/ArgsChecker";
4import {
5 TypeConverter,
6 checkForDevideByZero
7} from "../Utilities/TypeConverter";
8import {
9 NumError,
10 DivZeroError, ValueError
11} from "../Errors"
12import {
13 YEARFRAC
14} from "./Date";
15import {Filter} from "../Utilities/Filter";
16import {isDefined, isUndefined} from "../Utilities/MoreUtils";
17
18
19/**
20 * Calculates the depreciation of an asset for a specified period using the double-declining balance method.
21 * @param cost - The initial cost of the asset.
22 * @param salvage - The value of the asset at the end of depreciation.
23 * @param life - The number of periods over which the asset is depreciated.
24 * @param period - The single period within life for which to calculate depreciation.
25 * @param factor - [ OPTIONAL - 2 by default ] - The factor by which depreciation decreases.
26 * @returns {number} depreciation of an asset for a specified period
27 * @constructor
28 */
29let DDB = function (cost, salvage, life, period, factor?) : number {
30 ArgsChecker.checkLengthWithin(arguments, 4, 5, "DDB");
31 cost = TypeConverter.firstValueAsNumber(cost);
32 salvage = TypeConverter.firstValueAsNumber(salvage);
33 life = TypeConverter.firstValueAsNumber(life);
34 period = TypeConverter.firstValueAsNumber(period);
35 factor = factor === undefined ? 2 : TypeConverter.firstValueAsNumber(factor);
36
37 if (cost < 0) {
38 throw new NumError("Function DDB parameter 1 value is "
39 + cost + ". It should be greater than or equal to 0.");
40 }
41 if (salvage < 0) {
42 throw new NumError("Function DDB parameter 2 value is "
43 + salvage + ". It should be greater than or equal to 0.");
44 }
45 if (life < 0) {
46 throw new NumError("Function DDB parameter 3 value is "
47 + life + ". It should be greater than or equal to 0.");
48 }
49 if (period < 0) {
50 throw new NumError("Function DDB parameter 4 value is "
51 + period + ". It should be greater than or equal to 0.");
52 }
53 if (period > life) {
54 throw new NumError("Function DDB parameter 4 value is "
55 + life + ". It should be less than or equal to value of Function DB parameter 3 with "+ period +".");
56 }
57 if (salvage >= cost) {
58 return 0;
59 }
60
61 let total = 0;
62 let current = 0;
63 for (let i = 1; i <= period; i++) {
64 current = Math.min((cost - total) * (factor / checkForDevideByZero(life)), (cost - salvage - total));
65 total += current;
66 }
67 return current;
68};
69
70
71/**
72 * Calculates the depreciation of an asset for a specified period using the arithmetic declining balance method.
73 * @param cost - The initial cost of the asset.
74 * @param salvage - The value of the asset at the end of depreciation.
75 * @param life - The number of periods over which the asset is depreciated.
76 * @param period - The single period within life for which to calculate depreciation.
77 * @param month - [ OPTIONAL - 12 by default ] - The number of months in the first year of depreciation.
78 * @returns {number} depreciated value
79 * @constructor
80 */
81let DB = function (cost, salvage, life, period, month) : number {
82 ArgsChecker.checkLengthWithin(arguments, 4, 5, "DB");
83 cost = TypeConverter.firstValueAsNumber(cost);
84 salvage = TypeConverter.firstValueAsNumber(salvage);
85 life = TypeConverter.firstValueAsNumber(life);
86 period = TypeConverter.firstValueAsNumber(period);
87 month = month !== undefined ? Math.floor(TypeConverter.firstValueAsNumber(month)) : 12;
88 if (cost < 0) {
89 throw new NumError("Function DB parameter 1 value is "
90 + cost + ". It should be greater than or equal to 0.");
91 }
92 if (salvage < 0) {
93 throw new NumError("Function DB parameter 2 value is "
94 + salvage + ". It should be greater than or equal to 0.");
95 }
96 if (life < 0) {
97 throw new NumError("Function DB parameter 3 value is "
98 + life + ". It should be greater than or equal to 0.");
99 }
100 if (period < 0) {
101 throw new NumError("Function DB parameter 4 value is "
102 + period + ". It should be greater than or equal to 0.");
103 }
104 if (month > 12 || month < 1) {
105 throw new NumError("Function DB parameter 5 value is "
106 + month + ". Valid values are between 1 and 12 inclusive.");
107 }
108 if (period > life) {
109 throw new NumError("Function DB parameter 4 value is "
110 + life + ". It should be less than or equal to value of Function DB parameter 3 with "+ period +".");
111 }
112 if (salvage >= cost) {
113 return 0;
114 }
115 if (cost === 0 && salvage !== 0) {
116 throw new DivZeroError("Evaluation of function DB cause a divide by zero error.")
117 }
118 let rate = (1 - Math.pow(salvage / cost, 1 / life));
119 let initial = cost * rate * month / 12;
120 let total = initial;
121 let current = 0;
122 let ceiling = (period === life) ? life - 1 : period;
123 for (let i = 2; i <= ceiling; i++) {
124 current = (cost - total) * rate;
125 total += current;
126 }
127 if (period === 1) {
128 return initial;
129 } else if (period === life) {
130 return (cost - total) * rate;
131 } else {
132 return current;
133 }
134};
135
136/**
137 * Formats a number into the locale-specific currency format. WARNING: Currently the equivalent of TRUNC, since this
138 * returns numbers
139 * @param number - The value to be formatted.
140 * @param places - [ OPTIONAL - 2 by default ] - The number of decimal places to display.
141 * @returns {number} dollars
142 * @constructor
143 */
144let DOLLAR = function (number, places?) : number {
145 ArgsChecker.checkLengthWithin(arguments, 1, 2, "DOLLAR");
146 let v = TypeConverter.firstValueAsNumber(number);
147 places = places !== undefined ? TypeConverter.firstValueAsNumber(places) : 2;
148 let sign = (v > 0) ? 1 : -1;
149 let divisor = sign * (Math.floor(Math.abs(v) * Math.pow(10, places)));
150 let pow = Math.pow(10, places);
151 if (pow === 0 && divisor !== 0) {
152 throw new DivZeroError("Evaluation of function DOLLAR cause a divide by zero error.")
153 }
154 return divisor / pow;
155};
156
157
158/**
159 * Converts a price quotation given as a decimal fraction into a decimal value.
160 * @param fractionalPrice - The price quotation given using fractional decimal conventions.
161 * @param unit - The units of the fraction, e.g. 8 for 1/8ths or 32 for 1/32nds.
162 * @returns {number} decimal value.
163 * @constructor
164 */
165let DOLLARDE = function (fractionalPrice, unit) : number {
166 ArgsChecker.checkLength(arguments, 2, "DOLLARDE");
167 let dollar = TypeConverter.firstValueAsNumber(fractionalPrice);
168 let fraction = Math.floor(TypeConverter.firstValueAsNumber(unit));
169 if (fraction === 0) {
170 throw new DivZeroError("Function DOLLARDE parameter 2 cannot be zero.");
171 }
172 let result = parseInt(dollar.toString(), 10);
173 result += (dollar % 1) * Math.pow(10, Math.ceil(Math.log(fraction) / Math.LN10)) / fraction;
174 let power = Math.pow(10, Math.ceil(Math.log(fraction) / Math.LN2) + 1);
175 if (power === 0) {
176 throw new DivZeroError("Evaluation of function DOLLARDE cause a divide by zero error.")
177 }
178 result = Math.round(result * power) / power;
179 return result;
180};
181
182
183/**
184 * Converts a price quotation given as a decimal value into a decimal fraction.
185 * @param decimalPrice - The price quotation given as a decimal value.
186 * @param unit - The units of the desired fraction, e.g. 8 for 1/8ths or 32 for 1/32nds
187 * @returns {number} price quotation as decimal fraction.
188 * @constructor
189 */
190let DOLLARFR = function (decimalPrice, unit) : number {
191 ArgsChecker.checkLength(arguments, 2, "DOLLARFR");
192 decimalPrice = TypeConverter.firstValueAsNumber(decimalPrice);
193 unit = Math.floor(TypeConverter.firstValueAsNumber(unit));
194 if (unit === 0) {
195 throw new DivZeroError("Function DOLLARFR parameter 2 cannot be zero.");
196 }
197 let result = parseInt(decimalPrice.toString(), 10);
198 result += (decimalPrice % 1) * Math.pow(10, -Math.ceil(Math.log(unit) / Math.LN10)) * unit;
199 return result;
200};
201
202
203/**
204 * Calculates the annual effective interest rate given the nominal rate and number of compounding periods per year.
205 * @param nominalRate - The nominal interest rate per year.
206 * @param periodsPerYear - The number of compounding periods per year.
207 * @returns {number} annual effective interest rate
208 * @constructor
209 */
210let EFFECT = function (nominalRate, periodsPerYear) : number {
211 ArgsChecker.checkLength(arguments, 2, "EFFECT");
212 let rate = TypeConverter.firstValueAsNumber(nominalRate);
213 let periods = TypeConverter.firstValueAsNumber(periodsPerYear);
214 if (rate <= 0) {
215 throw new NumError("Function EFFECT parameter 1 value is " + rate + ". It should be greater than to 0");
216 }
217 if (periods < 1) {
218 throw new NumError("Function EFFECT parameter 2 value is " + periods + ". It should be greater than or equal to 1");
219 }
220 periods = Math.floor(periods);
221 return Math.pow(1 + rate / periods, periods) - 1;
222};
223
224
225/**
226 * Calculates the periodic payment for an annuity investment based on constant-amount periodic payments and a constant
227 * interest rate.
228 * @param rate - The interest rate.
229 * @param periods - The number of payments to be made.
230 * @param presentValue - The current value of the annuity.
231 * @param futureValue [ OPTIONAL ] - The future value remaining after the final payment has been made.
232 * @param endOrBeginning [ OPTIONAL - 0 by default ] - Whether payments are due at the end (0) or beginning (1) of each
233 * period.
234 * @returns {number}
235 * @constructor
236 */
237let PMT = function (rate, periods, presentValue, futureValue?, endOrBeginning?) : number {
238 ArgsChecker.checkLengthWithin(arguments, 3, 5, "PMT");
239 rate = TypeConverter.firstValueAsNumber(rate);
240 periods = TypeConverter.firstValueAsNumber(periods);
241 presentValue = TypeConverter.firstValueAsNumber(presentValue);
242 futureValue = futureValue ? TypeConverter.firstValueAsNumber(futureValue) : 0;
243 endOrBeginning = endOrBeginning ? TypeConverter.firstValueAsNumber(endOrBeginning) : 0;
244 let result;
245 if (rate === 0) {
246 result = (presentValue + futureValue) / periods;
247 } else {
248 let term = Math.pow(1 + rate, periods);
249 if (endOrBeginning) {
250 result = (futureValue * rate / (term - 1) + presentValue * rate / (1 - 1 / term)) / (1 + rate);
251 } else {
252 result = futureValue * rate / (term - 1) + presentValue * rate / (1 - 1 / term);
253 }
254 }
255 return -result;
256};
257
258
259/**
260 * Returns the future value of an investment based on periodic, constant payments and a constant interest rate.
261 * @param rate - The rate of periodic interest.
262 * @param periods - The total number of periods.
263 * @param payment - The annuity paid regularly per period
264 * @param value - [OPTIONAL] - The present cash value of an investment.
265 * @param type - [OPTIONAL] - Defines whether the payment is due at the beginning (1) or the end (0) of a period.
266 * @returns {number}
267 * @constructor
268 */
269let FV = function (rate, periods, payment, value?, type?) {
270 ArgsChecker.checkLengthWithin(arguments, 3, 5, "FV");
271 rate = TypeConverter.firstValueAsNumber(rate);
272 periods = TypeConverter.firstValueAsNumber(periods);
273 payment = TypeConverter.firstValueAsNumber(payment);
274 value = (typeof value === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(value);
275 type = (typeof type === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(type);
276 let result;
277 if (rate === 0) {
278 result = value + payment * periods;
279 } else {
280 let term = Math.pow(1 + rate, periods);
281 if (type === 0) {
282 result = value * term + payment * (term - 1) / rate;
283 } else {
284 result = value * term + payment * (1 + rate) * (term - 1.0) / rate;
285 }
286 }
287 return -result;
288};
289
290/**
291 * Calculates the cumulative principal paid over a range of payment periods for an investment based on constant-amount
292 * periodic payments and a constant interest rate.
293 * @param rate - The interest rate.
294 * @param numberOfPeriods - The number of payments to be made.
295 * @param presentValue - The current value of the annuity.
296 * @param firstPeriod - The number of the payment period to begin the cumulative calculation. must be greater
297 * than or equal to 1.
298 * @param lastPeriod - The number of the payment period to end the cumulative calculation, must be greater
299 * than first_period.
300 * @param endOrBeginning - Whether payments are due at the end (0) or beginning (1) of each period
301 * @returns {number} cumulative principal
302 * @constructor
303 */
304let CUMPRINC = function (rate, numberOfPeriods, presentValue, firstPeriod, lastPeriod, endOrBeginning) : number {
305 ArgsChecker.checkLength(arguments, 6, "CUMPRINC");
306 rate = TypeConverter.firstValueAsNumber(rate);
307 let periods = TypeConverter.firstValueAsNumber(numberOfPeriods);
308 let value = TypeConverter.firstValueAsNumber(presentValue);
309 let start = TypeConverter.firstValueAsNumber(firstPeriod);
310 if (start < 1) {
311 throw new NumError("Function CUMPRINC parameter 4 value is " + start + ". It should be greater than or equal to 1.");
312 }
313 let end = TypeConverter.firstValueAsNumber(lastPeriod);
314 if (end < 1) {
315 throw new NumError("Function CUMPRINC parameter 5 value is " + end + ". It should be greater than or equal to 1.");
316 }
317 if (end < start) {
318 throw new NumError("Function CUMPRINC parameter 5 value is " + end + ". It should be greater than or equal to " + start + ".");
319 }
320 let type = TypeConverter.firstValueAsBoolean(endOrBeginning);
321
322 let payment = PMT(rate, periods, value, 0, type);
323 let principal = 0;
324 if (start === 1) {
325 if (type) {
326 principal = payment;
327 } else {
328 principal = payment + value * rate;
329 }
330 start++;
331 }
332 for (let i = start; i <= end; i++) {
333 if (type) {
334 principal += payment - (FV(rate, i - 2, payment, value, 1) - payment) * rate;
335 } else {
336 principal += payment - FV(rate, i - 1, payment, value, 0) * rate;
337 }
338 }
339 return principal;
340};
341
342/**
343 * Calculates the cumulative interest over a range of payment periods for an investment based on constant-amount
344 * periodic payments and a constant interest rate.
345 * @param rate - The interest rate.
346 * @param numberOfPeriods - The number of payments to be made.
347 * @param presentValue - The current value of the annuity.
348 * @param firstPeriod - The number of the payment period to begin the cumulative calculation, must be greater
349 * than or equal to 1.
350 * @param lastPeriod - The number of the payment period to end the cumulative calculation, must be greater
351 * than first_period.
352 * @param endOrBeginning - Whether payments are due at the end (0) or beginning (1) of each period.
353 * @returns {number} cumulative interest
354 * @constructor
355 */
356let CUMIPMT = function (rate, numberOfPeriods, presentValue, firstPeriod, lastPeriod, endOrBeginning) : number {
357 ArgsChecker.checkLength(arguments, 6, "CUMIPMT");
358 rate = TypeConverter.firstValueAsNumber(rate);
359 let periods = TypeConverter.firstValueAsNumber(numberOfPeriods);
360 let value = TypeConverter.firstValueAsNumber(presentValue);
361 let start = TypeConverter.firstValueAsNumber(firstPeriod);
362 if (start < 1) {
363 throw new NumError("Function CUMPRINC parameter 4 value is " + start + ". It should be greater than or equal to 1.");
364 }
365 let end = TypeConverter.firstValueAsNumber(lastPeriod);
366 if (end < 1) {
367 throw new NumError("Function CUMPRINC parameter 5 value is " + end + ". It should be greater than or equal to 1.");
368 }
369 if (end < start) {
370 throw new NumError("Function CUMPRINC parameter 5 value is " + end + ". It should be greater than or equal to " + start + ".");
371 }
372 let type = TypeConverter.firstValueAsBoolean(endOrBeginning);
373
374 let payment = PMT(rate, periods, value, 0, type);
375 let interest = 0;
376 if (start === 1) {
377 if (!type) {
378 interest = -value;
379 start++;
380 } else {
381 start++;
382 }
383 }
384 for (let i = start; i <= end; i++) {
385 if (type) {
386 interest += FV(rate, i - 2, payment, value, 1) - payment;
387 } else {
388 interest += FV(rate, i - 1, payment, value, 0);
389 }
390 }
391 interest *= rate;
392 return interest;
393};
394
395/**
396 * Calculates the accrued interest of a security that has periodic payments.
397 * WARNING: This function has been implemented to specifications as outlined in Google Spreadsheets, LibreOffice, and
398 * OpenOffice. It functions much the same as MSExcel's ACCRINT, but there are several key differences. Below are links
399 * to illustrate the differences. Please see the source code for more information on differences. Links: https://quant.stackexchange.com/questions/7040/whats-the-algorithm-behind-excels-accrint, https://support.office.com/en-us/article/ACCRINT-function-fe45d089-6722-4fb3-9379-e1f911d8dc74, https://quant.stackexchange.com/questions/7040/whats-the-algorithm-behind-excels-accrint, https://support.google.com/docs/answer/3093200 .
400 * @param issue - The date the security was initially issued.
401 * @param firstPayment - The first date interest will be paid.
402 * @param settlement - The settlement date of the security, the date after issuance when the security is
403 * delivered to the buyer. Is the maturity date of the security if it is held until maturity rather than sold.
404 * @param rate - The annualized rate of interest.
405 * @param redemption - The redemption amount per 100 face value, or par.
406 * @param frequency - The number of coupon payments per year. For annual payments, frequency = 1; for
407 * semiannual, frequency = 2; for quarterly, frequency = 4.
408 * @param dayCountConvention - [ OPTIONAL - 0 by default ] - An indicator of what day count method to use.
409 * 0 or omitted = US (NASD) 30/360, 1 = Actual/actual, 2 = Actual/360, 3 = Actual/365, 4 = European 30/360.
410 * @returns {number}
411 * @constructor
412 * TODO: This function is based off of the open-source versions I was able to dig up online. We should implement a
413 * TODO: second version that is closer to what MSExcel does and is named something like `ACCRINT.MS`.
414 */
415let ACCRINT = function (issue, firstPayment, settlement, rate, redemption, frequency, dayCountConvention?) {
416 ArgsChecker.checkLengthWithin(arguments, 6, 7, "ACCRINT");
417 issue = TypeConverter.firstValueAsDateNumber(issue);
418 // "firstPayment" param is only here to check for errors for GS implementation.
419 // In MSE, there is a 7th (zero-indexed-6th) param that indicates the calculation-method to use, which indicates
420 // weather the total accrued interest starting at the first_intrest date, instead of the issue date.
421 firstPayment = TypeConverter.firstValueAsDateNumber(firstPayment);
422 if (firstPayment < 0) {
423 throw new NumError("Function ACCRINT parameter 2 value is " + firstPayment
424 + ". It should be greater than 0.");
425 }
426 settlement = TypeConverter.firstValueAsDateNumber(settlement);
427 if (issue > settlement) {
428 throw new NumError("Function ACCRINT parameter 1 (" + issue.toString()
429 + ") should be on or before Function ACCRINT parameter 3 (" + settlement.toString() + ").")
430 }
431 rate = TypeConverter.firstValueAsNumber(rate);
432 // redemption, aka "par"
433 redemption = TypeConverter.firstValueAsNumber(redemption);
434 // The frequency parameter also does not affect the resulting value of the formula in the GS implementation.
435 // In MSE, frequency is used to calculate a more accurate value, by breaking apart the year, and computing interest
436 // on an on-going basis. In this implementation, we use YEARFRAC to get a numerical value that encompasses the
437 // functionality of "frequency".
438 frequency = TypeConverter.firstValueAsNumber(frequency);
439 // dayCountConvention, aka "basis"
440 dayCountConvention = dayCountConvention !== undefined ? TypeConverter.firstValueAsNumber(dayCountConvention) : 1;
441 let factor = YEARFRAC(issue, settlement, dayCountConvention);
442 return redemption * rate * factor;
443};
444
445
446/**
447 * Returns the arithmetic-declining depreciation rate. Use this function to calculate the depreciation amount for one
448 * period of the total depreciation span of an object. Arithmetic declining depreciation reduces the depreciation amount
449 * from period to period by a fixed sum.
450 * @param cost - The initial cost of an asset.
451 * @param salvage - the value of an asset after depreciation.
452 * @param life - The period fixing the time span over which an asset is depreciated.
453 * @param period - The period for which the depreciation is to be calculated.
454 * @returns {number}
455 * @constructor
456 */
457let SYD = function (cost, salvage, life, period) {
458 ArgsChecker.checkLength(arguments, 4, "SYD");
459 cost = TypeConverter.firstValueAsNumber(cost);
460 salvage = TypeConverter.firstValueAsNumber(salvage);
461 life = TypeConverter.firstValueAsNumber(life);
462 period = TypeConverter.firstValueAsNumber(period);
463 // Return error if period is lower than 1 or greater than life
464 if (period > life) {
465 throw new NumError("Function SYD parameter 4 value is " + period +
466 ". It should be less than or equal to value of Function SYD parameter 3 with " + life + ".");
467 }
468 if (period < 1) {
469 throw new NumError("Function SYD parameter 4 value is " + period + ". It should be greater than 0.");
470 }
471 period = Math.floor(period);
472
473 return (cost - salvage) * (life - period + 1) * 2 / (life * (life + 1));
474};
475
476
477/**
478 * Returns the straight-line depreciation of an asset for one period. The amount of the depreciation is constant during
479 * the depreciation period.
480 * @param cost - The initial cost of the asset.
481 * @param salvage - The value of an asset at the end of the depreciation.
482 * @param life - The depreciation period determining the number of periods in the deprecation of the asset.
483 * @returns {number}
484 * @constructor
485 */
486let SLN = function (cost, salvage, life) {
487 ArgsChecker.checkLength(arguments, 3, "SYD");
488 cost = TypeConverter.firstValueAsNumber(cost);
489 salvage = TypeConverter.firstValueAsNumber(salvage);
490 life = TypeConverter.firstValueAsNumber(life);
491 if (life === 0) {
492 throw new DivZeroError("Function SLN parameter 3 cannot be zero.");
493 }
494 return (cost - salvage) / life;
495};
496
497
498/**
499 * Returns the net present value of an investment based on a series of periodic cash flows and a discount rate.
500 * @param rate - The discount rate for a period.
501 * @param values - The values representing deposits or withdrawals.
502 * @returns {number}
503 * @constructor
504 * TODO: This function can return results that are prone to floating point precision errors.
505 */
506let NPV = function (rate, ...values) {
507 ArgsChecker.checkAtLeastLength(arguments, 2, "NPV");
508 let range = Filter.flattenAndThrow(values).map(function (value) {
509 try {
510 return TypeConverter.valueToNumber(value);
511 } catch (e) {
512 throw new ValueError("Function NPV expects number values. But '" + value + "' is " + (typeof value)
513 + " and cannot be coerced to a number.")
514 }
515 });
516 let value = 0;
517 for (let i = 0; i < range.length; i++) {
518 value += range[i] / Math.pow(1 + rate, i);
519 }
520 return value;
521};
522
523/**
524 * Returns the number of payment for an investment. Number is based on constant-amount payments made periodically and a
525 * constant interest rate.
526 * @param rate - The interest rate.
527 * @param payment - The amount of each payment.
528 * @param present - THe current value.
529 * @param future - [OPTIONAL] - The future value remaining after the final payment has been made.
530 * @param type [OPTIONAL 0 by default] - 1 indicates payments are due at the beginning of each period. 0 indicates
531 * payments are due at the end of each period.
532 * @returns {number}
533 * @constructor
534 */
535let NPER = function (rate, payment, present, future?, type?) {
536 ArgsChecker.checkLengthWithin(arguments, 3, 5, "NPER");
537 rate = TypeConverter.firstValueAsNumber(rate);
538 payment = TypeConverter.firstValueAsNumber(payment);
539 present = TypeConverter.firstValueAsNumber(present);
540 type = (typeof type === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(type);
541 future = (typeof future === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(future);
542 let num = payment * (1 + rate * type) - future * rate;
543 let den = (present * rate + payment * (1 + rate * type));
544 if (den === 0) {
545 throw new DivZeroError("Evaluation of function NPER cause a divide by zero error.");
546 }
547 let div = Math.log(1 + rate);
548 let logNumDen = Math.log(num / den);
549 if (isNaN(logNumDen)) {
550 throw new NumError("Parameters given function NPER are not possible.");
551 }
552 return logNumDen / div;
553};
554
555/**
556 * Calculates the yearly nominal interest rate, given the effective rate and the number of compounding periods per year.
557 * @param rate - The effective interest rate.
558 * @param periods - The number of periodic interest payments per year.
559 * @returns {number}
560 * @constructor
561 */
562let NOMINAL = function (rate, periods) {
563 ArgsChecker.checkLength(arguments, 2, "NOMINAL");
564 rate = TypeConverter.firstValueAsNumber(rate);
565 periods = Math.round(TypeConverter.firstValueAsNumber(periods));
566 if (periods < 1) {
567 throw new NumError("Function NOMINAL parameter 2 value is " + periods
568 + ". It should be greater than or equal to 1.");
569 }
570 return (Math.pow(rate + 1, 1 / periods) - 1) * periods;
571};
572
573
574/**
575 * Calculates the modified internal rate of return of a series of investments.
576 * @param values - Range or values of payments. Ignores text values.
577 * @param financeRate - The rate of interest of the investments.
578 * @param reinvestRate - The rate of interest of the reinvestment.
579 * @returns {number}
580 * @constructor
581 * TODO: This relies on NPV and will therefore be prone to floating-point errors.
582 */
583let MIRR = function (values, financeRate, reinvestRate) {
584 ArgsChecker.checkLength(arguments, 3, "MIRR");
585 values = Filter.flattenAndThrow(values).filter(function (value) {
586 return (typeof value !== "string");
587 }).map(function (value) {
588 return TypeConverter.valueToNumber(value);
589 });
590 let n = values.length;
591
592 let payments = [];
593 let incomes = [];
594 for (let i = 0; i < n; i++) {
595 if (values[i] < 0) {
596 payments.push(values[i]);
597 } else {
598 incomes.push(values[i]);
599 }
600 }
601 if (incomes.length === 0 || payments.length === 0) {
602 throw new DivZeroError("For MIRR, the values must include positive and negative numbers.");
603 }
604
605 let num = -NPV(reinvestRate, incomes) * Math.pow(1 + reinvestRate, n - 1);
606 let den = NPV(financeRate, payments) * (1 + financeRate);
607 return Math.pow(num / den, 1 / (n - 1)) - 1;
608};
609
610
611/**
612 * Calculates the internal rate of return for an investment. The values represent cash flow values at regular intervals;
613 * at least one value must be negative (payments), and at least one value must be positive (income).
614 *
615 * Relevant StackOverflow discussion: https://stackoverflow.com/questions/15089151/javascript-irr-internal-rate-of-return-formula-accuracy
616 *
617 * @param values - Range containing values. Ignores text values.
618 * @param guess - [OPTIONAL] - The estimated value. Defaults to 0.01.
619 * @returns {number}
620 * @constructor
621 */
622let IRR = function (values, guess?) {
623 ArgsChecker.checkLengthWithin(arguments, 1, 2, "IRR");
624 values = Filter.flattenAndThrow(values).filter(function (value) {
625 return (typeof value !== "string");
626 }).map(function (value) {
627 return TypeConverter.valueToNumber(value);
628 });
629 guess = (guess === undefined) ? 0.1 : TypeConverter.firstValueAsNumber(guess);
630 let min = -1.0;
631 let max = 10.0;
632 let val;
633 let counter = 1;
634 const MAX_ITERATIONS = 500000;
635 do {
636 guess = (min + max) / 2;
637 val = 0;
638 for (let j = 0; j < values.length; j++) {
639 val += values[j] / Math.pow((1 + guess), j);
640 }
641 if (val > 0) {
642 min = guess;
643 }
644 else {
645 max = guess;
646 }
647 } while(Math.abs(val) > 0.000001 && ++counter < MAX_ITERATIONS);
648 return guess;
649};
650
651
652/**
653 * Calculates the periodic amortization for an investment with regular payments and a constant interest rate.
654 * @param rate - The periodic interest rate.
655 * @param period - The period for which the compound interest is calculated.
656 * @param periods - The total number of periods during which the annuity is paid.
657 * @param present - The present cash value in sequence of payments.
658 * @param future - [OPTIONAL] - The desired value (future value) at the end of the periods.
659 * @param type - [OPTIONAL] - Defines whether the payment is due at the beginning (1) or the end (0) of a period.
660 * @returns {number}
661 * @constructor
662 */
663let IPMT = function (rate, period, periods, present, future?, type?) {
664 ArgsChecker.checkLengthWithin(arguments, 4, 6, "IPMT");
665 rate = TypeConverter.firstValueAsNumber(rate);
666 period = TypeConverter.firstValueAsNumber(period);
667 periods = TypeConverter.firstValueAsNumber(periods);
668 present = TypeConverter.firstValueAsNumber(present);
669 future = (typeof future === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(future);
670 type = (typeof type === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(type);
671 let payment = PMT(rate, periods, present, future, type);
672 let interest;
673 if (period === 1) {
674 if (type === 1) {
675 interest = 0;
676 } else {
677 interest = -present;
678 }
679 } else {
680 if (type === 1) {
681 interest = FV(rate, period - 2, payment, present, 1) - payment;
682 } else {
683 interest = FV(rate, period - 1, payment, present, 0);
684 }
685 }
686 return interest * rate;
687};
688
689
690/**
691 * Returns for a given period the payment on the principal for an investment that is based on periodic and constant
692 * payments and a constant interest rate.
693 * @param rate - The periodic interest rate.
694 * @param period - The amortization period.
695 * @param periods - The total number of periods during which the annuity is paid.
696 * @param present - The present value in the sequence of payments.
697 * @param future - [OPTIONAL] - The desired future value. Defaults to 0.
698 * @param type - [OPTIONAL] - Indicates how the year is to be calculated. 0 indicates payments are due at end of
699 * period, 1 indicates payments are due at beginning of period. Defaults to 0.
700 * @returns {number}
701 * @constructor
702 */
703let PPMT = function (rate, period, periods, present, future?, type?) {
704 ArgsChecker.checkLengthWithin(arguments, 4, 6, "PPMT");
705 rate = TypeConverter.firstValueAsNumber(rate);
706 period = TypeConverter.firstValueAsNumber(period);
707 if (period < 1) {
708 throw new NumError("Function PPMT parameter 2 value is " + period + ", but should be greater than or equal to 1.");
709 }
710 periods = TypeConverter.firstValueAsNumber(periods);
711 if (periods <= 0) {
712 throw new NumError("Function PPMT parameter 3 value is " + periods + ", but should be greater than 0.");
713 }
714 present = TypeConverter.firstValueAsNumber(present);
715 future = (typeof future === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(future);
716 type = (typeof type === 'undefined') ? 0 : TypeConverter.firstValueAsNumber(type);
717 return PMT(rate, periods, present, future, type) - IPMT(rate, period, periods, present, future, type);
718};
719
720
721/**
722 * Calculates the accumulated value of the starting capital for a series of periodically varying interest rates.
723 * @param principal - The starting capital.
724 * @param rateSchedule - Range or Array that is a series of interest rates.
725 * @returns {number}
726 * @constructor
727 */
728let FVSCHEDULE = function (principal, rateSchedule) {
729 ArgsChecker.checkLength(arguments, 2, "FVSCHEDULE");
730 let future = TypeConverter.firstValueAsNumber(principal);
731 rateSchedule = Filter.flattenAndThrow(rateSchedule);
732
733 for (let i = 0; i < rateSchedule.length; i++) {
734 // Apply scheduled interest
735 future *= 1 + rateSchedule[i];
736 }
737 return future;
738};
739
740
741/**
742 * Returns the present value of an investment resulting from a series of regular payments.
743 * @param rate - The interest rate per period.
744 * @param periods - The total number of payment periods
745 * @param paymentPerPeriod - The regular payment made per period.
746 * @param future - [OPTIONAL defaults to 0] The future value remaining after the final installment has been made
747 * @param type - [OPTIONAL defaults to 0] Defines whether the payment is due at the beginning (1) or the end (0) of a
748 * period.
749 * @constructor
750 */
751let PV = function (rate, periods, paymentPerPeriod, future?, type?) {
752 ArgsChecker.checkLengthWithin(arguments, 3, 5, "PV");
753 rate = TypeConverter.firstValueAsNumber(rate);
754 if (rate < 0) {
755 throw new NumError("Function PV parameter 21value is " + rate + ", but should be greater than or equal to 0.");
756 }
757 periods = TypeConverter.firstValueAsNumber(periods);
758 paymentPerPeriod = TypeConverter.firstValueAsNumber(paymentPerPeriod);
759 future = isUndefined(future) ? 0 : TypeConverter.firstValueAsNumber(future);
760 type = isUndefined(type) ? 0 : TypeConverter.firstValueAsNumber(type);
761 if (rate === 0) {
762 return -paymentPerPeriod * periods - future;
763 } else {
764 return (((1 - Math.pow(1 + rate, periods)) / rate) * paymentPerPeriod * (1 + rate * type) - future) / Math.pow(1 + rate, periods);
765 }
766};
767
768
769/**
770 * Returns the constant interest rate per period of an annuity.
771 * @param periods - The total number of periods, during which payments are made (payment period).
772 * @param paymentPerPeriod - The constant payment (annuity) paid during each period.
773 * @param presentValue - The cash value in the sequence of payments
774 * @param futureValue - [OPTIONAL defaults to 0] The future value, which is reached at the end of the periodic payments.
775 * @param beginningOrEnd - [OPTIONAL defaults to 0] Defines whether the payment is due at the beginning (1) or the end
776 * (0) of a period.
777 * @param guessRate - [OPTIONAL] - Determines the estimated value of the interest with iterative
778 * calculation.
779 * @constructor
780 */
781let RATE = function (periods, paymentPerPeriod, presentValue, futureValue?, beginningOrEnd?, guessRate?) {
782 ArgsChecker.checkLengthWithin(arguments, 3, 6, "RATE");
783 periods = TypeConverter.firstValueAsNumber(periods);
784 if (periods < 1) {
785 throw new NumError("Function RATE parameter 1 value is" + periods + ", but it should be greater than 0.");
786 }
787 paymentPerPeriod = TypeConverter.firstValueAsNumber(paymentPerPeriod);
788 presentValue = TypeConverter.firstValueAsNumber(presentValue);
789 futureValue = isDefined(futureValue) ? TypeConverter.firstValueAsNumber(futureValue) : 0;
790 beginningOrEnd = isDefined(beginningOrEnd) ? TypeConverter.firstValueAsNumber(beginningOrEnd) : 0;
791 guessRate = isDefined(guessRate) ? TypeConverter.firstValueAsNumber(guessRate) : 0.1;
792
793 // Sets the limits for possible guesses to any
794 // number between 0% and 100%
795 let lowLimit = 0;
796 let highLimit = 1;
797 let guess = guessRate;
798
799 // Defines a tolerance of up to +/- 0.00005% of pmt, to accept
800 // the solution as valid.
801 let tolerance = Math.abs(0.00000005 * paymentPerPeriod);
802
803 // Tries at most 40 times to find a solution within the tolerance.
804 for (let i = 0; i < 40; i++) {
805 // Resets the balance to the original pv.
806 let balance = presentValue;
807
808 // Calculates the balance at the end of the loan, based
809 // on loan conditions.
810 for (let j = 0; j < periods; j++ ) {
811 if (beginningOrEnd == 0) {
812 // Interests applied before payment
813 balance = balance * (1 + guess) + paymentPerPeriod;
814 } else {
815 // Payments applied before insterests
816 balance = (balance + paymentPerPeriod) * (1 + guess);
817 }
818 }
819
820 // Returns the guess if balance is within tolerance. If not, adjusts
821 // the limits and starts with a new guess.
822 if (Math.abs(balance + futureValue) < tolerance) {
823 return guess;
824 } else if (balance + futureValue > 0) {
825 // Sets a new highLimit knowing that
826 // the current guess was too big.
827 highLimit = guess;
828 } else {
829 // Sets a new lowLimit knowing that
830 // the current guess was too small.
831 lowLimit = guess;
832 }
833
834 // Calculates the new guess.
835 guess = (highLimit + lowLimit) / 2;
836 }
837 throw new NumError("RATE attempted to complete but it was not able to.");
838};
839
840
841export {
842 ACCRINT,
843 CUMPRINC,
844 CUMIPMT,
845 DB,
846 DDB,
847 DOLLAR,
848 DOLLARDE,
849 DOLLARFR,
850 EFFECT,
851 PMT,
852 SYD,
853 SLN,
854 NPV,
855 NPER,
856 NOMINAL,
857 MIRR,
858 IRR,
859 IPMT,
860 FV,
861 PPMT,
862 FVSCHEDULE,
863 PV,
864 RATE
865}