moneyfunx 1.4.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +3 -1
  2. package/build/index.d.ts +10 -7
  3. package/build/index.js +8 -6
  4. package/build/lib/constants.d.ts +4 -0
  5. package/build/lib/constants.js +6 -0
  6. package/build/lib/{loan.d.ts → debt/loan.d.ts} +3 -1
  7. package/build/lib/{loan.js → debt/loan.js} +4 -2
  8. package/build/lib/{paymentTypes.d.ts → debt/paymentTypes.d.ts} +2 -2
  9. package/build/lib/{payments.d.ts → debt/payments.d.ts} +3 -3
  10. package/build/lib/{payments.js → debt/payments.js} +7 -7
  11. package/build/lib/errors.d.ts +3 -0
  12. package/build/lib/errors.js +7 -0
  13. package/build/lib/investment/contributionTypes.d.ts +31 -0
  14. package/build/lib/investment/contributionTypes.js +20 -0
  15. package/build/lib/investment/contributions.d.ts +35 -0
  16. package/build/lib/investment/contributions.js +105 -0
  17. package/build/lib/investment/instrument.d.ts +51 -0
  18. package/build/lib/investment/instrument.js +59 -0
  19. package/build/lib/shared/sorting.d.ts +34 -0
  20. package/build/lib/shared/sorting.js +32 -0
  21. package/coverage/clover.xml +247 -72
  22. package/coverage/coverage-final.json +11 -8
  23. package/coverage/index.html +58 -13
  24. package/coverage/src/index.html +5 -5
  25. package/coverage/src/index.ts.html +54 -12
  26. package/coverage/src/lib/constants.ts.html +28 -4
  27. package/coverage/src/lib/{helperFunctions.ts.html → debt/helperFunctions.ts.html} +10 -10
  28. package/coverage/src/lib/debt/index.html +161 -0
  29. package/coverage/src/lib/{loan.ts.html → debt/loan.ts.html} +19 -13
  30. package/coverage/src/lib/{paymentTypes.ts.html → debt/paymentTypes.ts.html} +12 -12
  31. package/coverage/src/lib/{payments.ts.html → debt/payments.ts.html} +32 -32
  32. package/coverage/src/lib/errors.ts.html +30 -6
  33. package/coverage/src/lib/index.html +11 -86
  34. package/coverage/src/lib/investment/contributionTypes.ts.html +187 -0
  35. package/coverage/src/lib/investment/contributions.ts.html +535 -0
  36. package/coverage/src/lib/investment/index.html +146 -0
  37. package/coverage/src/lib/investment/instrument.ts.html +361 -0
  38. package/coverage/src/lib/shared/index.html +116 -0
  39. package/coverage/src/lib/{sorting.ts.html → shared/sorting.ts.html} +32 -32
  40. package/package.json +4 -1
  41. package/src/index.ts +22 -8
  42. package/src/lib/constants.ts +8 -1
  43. package/src/lib/{loan.ts → debt/loan.ts} +4 -2
  44. package/src/lib/{paymentTypes.ts → debt/paymentTypes.ts} +2 -2
  45. package/src/lib/{payments.ts → debt/payments.ts} +19 -19
  46. package/src/lib/errors.ts +8 -0
  47. package/src/lib/investment/contributionTypes.ts +34 -0
  48. package/src/lib/investment/contributions.ts +150 -0
  49. package/src/lib/investment/instrument.ts +92 -0
  50. package/src/lib/shared/sorting.ts +41 -0
  51. package/build/lib/sorting.d.ts +0 -31
  52. package/build/lib/sorting.js +0 -32
  53. package/src/lib/sorting.ts +0 -41
  54. /package/build/lib/{helperFunctions.d.ts → debt/helperFunctions.d.ts} +0 -0
  55. /package/build/lib/{helperFunctions.js → debt/helperFunctions.js} +0 -0
  56. /package/build/lib/{paymentTypes.js → debt/paymentTypes.js} +0 -0
  57. /package/src/lib/{helperFunctions.ts → debt/helperFunctions.ts} +0 -0
package/README.md CHANGED
@@ -4,4 +4,6 @@
4
4
  MoneyFunx is a small library of functions for financial computations, with a focus on personal finance
5
5
 
6
6
  ## Upcoming features
7
- - Refinancing calcuations
7
+ - Investment library
8
+ - drawdown/retirement analysis across instruments
9
+ - "How long can I afford to live on $X (gross|net) per year?"
package/build/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
- export { TOTALS } from './lib/constants';
2
- export { PaymentTooLowError } from './lib/errors';
3
- export { calculateMinPayment, numPaymentsToZero, principalRemaining, interestPaid, } from './lib/helperFunctions';
4
- export { ILoan, Loan } from './lib/loan';
5
- export { determineExtraPayment, amortizePayments, payLoans, } from './lib/payments';
6
- export { AmortizationRecord, LoansPaymentSchedule, LoanPrincipals, PaymentSchedule, } from './lib/paymentTypes';
7
- export { snowball, avalanche, sortLoans } from './lib/sorting';
1
+ export { MAX_DURATION_YEARS, TOTALS, } from './lib/constants';
2
+ export { NegativeContributionError, PaymentTooLowError } from './lib/errors';
3
+ export { calculateMinPayment, numPaymentsToZero, principalRemaining, interestPaid, } from './lib/debt/helperFunctions';
4
+ export { amortizeContributions, contributeInstruments, determineExtraContribution, } from './lib/investment/contributions';
5
+ export { ContributionRecord, ContributionSchedule, InstrumentsContributionSchedule, } from './lib/investment/contributionTypes';
6
+ export { IInstrument, Instrument } from './lib/investment/instrument';
7
+ export { ILoan, Loan } from './lib/debt/loan';
8
+ export { determineExtraPayment, amortizePayments, payLoans, } from './lib/debt/payments';
9
+ export { PaymentRecord, LoansPaymentSchedule, LoanPrincipals, PaymentSchedule, } from './lib/debt/paymentTypes';
10
+ export { snowball, avalanche, sortWith } from './lib/shared/sorting';
package/build/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  // src/index
2
- export { TOTALS } from './lib/constants';
3
- export { PaymentTooLowError } from './lib/errors';
4
- export { calculateMinPayment, numPaymentsToZero, principalRemaining, interestPaid, } from './lib/helperFunctions';
5
- export { Loan } from './lib/loan';
6
- export { determineExtraPayment, amortizePayments, payLoans, } from './lib/payments';
7
- export { snowball, avalanche, sortLoans } from './lib/sorting';
2
+ export { MAX_DURATION_YEARS, TOTALS, } from './lib/constants';
3
+ export { NegativeContributionError, PaymentTooLowError } from './lib/errors';
4
+ export { calculateMinPayment, numPaymentsToZero, principalRemaining, interestPaid, } from './lib/debt/helperFunctions';
5
+ export { amortizeContributions, contributeInstruments, determineExtraContribution, } from './lib/investment/contributions';
6
+ export { Instrument } from './lib/investment/instrument';
7
+ export { Loan } from './lib/debt/loan';
8
+ export { determineExtraPayment, amortizePayments, payLoans, } from './lib/debt/payments';
9
+ export { snowball, avalanche, sortWith } from './lib/shared/sorting';
@@ -1 +1,5 @@
1
1
  export declare const TOTALS = "totals";
2
+ export declare const MAX_DURATION_YEARS = 110;
3
+ export declare const MAX_YIELD_FACTOR = 1.4;
4
+ export declare const MIN_GROWTH_FACTOR = 1;
5
+ export declare const MIN_YIELD_FACTOR = 0.45;
@@ -1 +1,7 @@
1
+ // shared constants
1
2
  export const TOTALS = 'totals';
3
+ // investment constants
4
+ export const MAX_DURATION_YEARS = 110;
5
+ export const MAX_YIELD_FACTOR = 1.4;
6
+ export const MIN_GROWTH_FACTOR = 1;
7
+ export const MIN_YIELD_FACTOR = 0.45;
@@ -41,11 +41,12 @@ export declare class Loan implements ILoan {
41
41
  * @constructor
42
42
  * @param {number} principal The amount borrowed
43
43
  * @param {number} annualRate The yearly rate the loan accrues interest at
44
- * @param {number} periodsPerYear The number of times the interest is accrued in a year
44
+ * @param {number} periodsPerYear The number of times the interest accrues in a year
45
45
  * @param {number} termInYears The number of years the loan is repaid over
46
46
  * @param {number} name The name for the loan
47
47
  * @param {number} currentBalance (Optional) The current balance of the loan, if different from the principal
48
48
  * @param {number} fees (Optional) The fees on the loan
49
+ * @returns {Loan}
49
50
  */
50
51
  constructor(principal: number, annualRate: number, periodsPerYear: number, termInYears: number, name: string, currentBalance?: number, fees?: number);
51
52
  /**
@@ -53,6 +54,7 @@ export declare class Loan implements ILoan {
53
54
  * Throws a PaymentTooLowError if the payment amount is less than the loan's minimum payment
54
55
  *
55
56
  * @param {number} payment The amount to pay the loan with
57
+ * @throws {errors.PaymentTooLowError} Throws an error when the payment to a Loan is less than the Loan's minimum payment
56
58
  * @returns {number} The validated payment amount
57
59
  */
58
60
  validatePayment(payment?: number): number;
@@ -9,18 +9,19 @@
9
9
  * This library contains functions used to in personal financial analysis
10
10
  *
11
11
  */
12
- import * as errors from './errors';
12
+ import * as errors from '../errors';
13
13
  import * as helpers from './helperFunctions';
14
14
  export class Loan {
15
15
  /**
16
16
  * @constructor
17
17
  * @param {number} principal The amount borrowed
18
18
  * @param {number} annualRate The yearly rate the loan accrues interest at
19
- * @param {number} periodsPerYear The number of times the interest is accrued in a year
19
+ * @param {number} periodsPerYear The number of times the interest accrues in a year
20
20
  * @param {number} termInYears The number of years the loan is repaid over
21
21
  * @param {number} name The name for the loan
22
22
  * @param {number} currentBalance (Optional) The current balance of the loan, if different from the principal
23
23
  * @param {number} fees (Optional) The fees on the loan
24
+ * @returns {Loan}
24
25
  */
25
26
  constructor(principal, annualRate, periodsPerYear, termInYears, name, currentBalance, fees) {
26
27
  this.id = String(Math.floor(Math.random() * Date.now()));
@@ -40,6 +41,7 @@ export class Loan {
40
41
  * Throws a PaymentTooLowError if the payment amount is less than the loan's minimum payment
41
42
  *
42
43
  * @param {number} payment The amount to pay the loan with
44
+ * @throws {errors.PaymentTooLowError} Throws an error when the payment to a Loan is less than the Loan's minimum payment
43
45
  * @returns {number} The validated payment amount
44
46
  */
45
47
  validatePayment(payment = this.minPayment) {
@@ -1,4 +1,4 @@
1
- export type AmortizationRecord = {
1
+ export type PaymentRecord = {
2
2
  period: number;
3
3
  principal: number;
4
4
  interest: number;
@@ -7,7 +7,7 @@ export type AmortizationRecord = {
7
7
  export type PaymentSchedule = {
8
8
  lifetimeInterest: number;
9
9
  lifetimePrincipal: number;
10
- amortizationSchedule: AmortizationRecord[];
10
+ amortizationSchedule: PaymentRecord[];
11
11
  };
12
12
  export type LoansPaymentSchedule = Record<string, PaymentSchedule>;
13
13
  export type LoanPrincipals = Record<string, number>;
@@ -4,7 +4,7 @@
4
4
  *
5
5
  */
6
6
  import type { ILoan, Loan } from './loan';
7
- import type { AmortizationRecord, LoansPaymentSchedule } from './paymentTypes';
7
+ import type { PaymentRecord, LoansPaymentSchedule } from './paymentTypes';
8
8
  /**
9
9
  *
10
10
  * Calculates the extra amount in a payment after all loans' minimum payments are met
@@ -36,9 +36,9 @@ export declare function determineCarryover(loan: Loan, loanPayment: number, loan
36
36
  * @param {number} numPayments The number of periods to make payments to the loan
37
37
  * @param {number} startPeriod An initial offset of periods to 'fast-forward' the state of the loan to prior to calculation of each period
38
38
  * @param {number} carryover An additional amount to pay towards a loan, used when a residual amount is available from paying off the previous loan this period
39
- * @returns {AmortizationRecord[]} The amortization schdule for the number of payments of payment made to the loan from the provided start period
39
+ * @returns {PaymentRecord[]} The amortization schedule for the number of payments of payment made to the loan from the provided start period
40
40
  */
41
- export declare function amortizePayments(loan: Loan, principal: number, payment: number, numPayments: number, startPeriod?: number, carryover?: number): AmortizationRecord[];
41
+ export declare function amortizePayments(loan: Loan, principal: number, payment: number | null, numPayments: number | null, startPeriod?: number, carryover?: number): PaymentRecord[];
42
42
  /**
43
43
  *
44
44
  * Calculates a wealth of information about paying of a set of loans with a total payment amount
@@ -3,7 +3,7 @@
3
3
  * This file contains functions for computing detailed information on paying loans
4
4
  *
5
5
  */
6
- import * as errors from './errors';
6
+ import * as errors from '../errors';
7
7
  /**
8
8
  *
9
9
  * Calculates the extra amount in a payment after all loans' minimum payments are met
@@ -14,7 +14,7 @@ import * as errors from './errors';
14
14
  * @returns {number} The extra amount of payment
15
15
  */
16
16
  export function determineExtraPayment(loans, payment) {
17
- const totalMinPayment = loans.reduce((previousValue, currentValue) => previousValue + currentValue.minPayment, 0);
17
+ const totalMinPayment = loans.reduce((accumulator, loan) => accumulator + loan.minPayment, 0);
18
18
  // hack to get around floating precision adjustments
19
19
  if (parseInt((100 * totalMinPayment).toFixed()) > parseInt((100 * payment).toFixed())) {
20
20
  throw new errors.PaymentTooLowError(`Payment amount of ${payment} must be greater than ${totalMinPayment}`);
@@ -49,7 +49,7 @@ export function determineCarryover(loan, loanPayment, loanFinalPayment, reduceMi
49
49
  * @param {number} numPayments The number of periods to make payments to the loan
50
50
  * @param {number} startPeriod An initial offset of periods to 'fast-forward' the state of the loan to prior to calculation of each period
51
51
  * @param {number} carryover An additional amount to pay towards a loan, used when a residual amount is available from paying off the previous loan this period
52
- * @returns {AmortizationRecord[]} The amortization schdule for the number of payments of payment made to the loan from the provided start period
52
+ * @returns {PaymentRecord[]} The amortization schedule for the number of payments of payment made to the loan from the provided start period
53
53
  */
54
54
  export function amortizePayments(loan, principal, payment, numPayments, startPeriod = 0, carryover = 0) {
55
55
  if (payment === null) {
@@ -59,8 +59,8 @@ export function amortizePayments(loan, principal, payment, numPayments, startPer
59
59
  if (numPayments === null) {
60
60
  numPayments = loan.numPaymentsToZero(payment);
61
61
  }
62
- let principalRemaining = principal;
63
62
  const amortizationSchedule = [];
63
+ let principalRemaining = principal;
64
64
  for (let period = 0; period < numPayments; period++) {
65
65
  const interestThisPeriod = loan.accrueInterest(principalRemaining);
66
66
  const principalThisPeriod = Math.min((period === numPayments - 1
@@ -94,7 +94,7 @@ export function payLoans(loans, payment, reduceMinimum = false) {
94
94
  paymentSchedule[loan.id] = {
95
95
  lifetimeInterest: 0,
96
96
  lifetimePrincipal: loan.currentBalance,
97
- amortizationSchedule: []
97
+ amortizationSchedule: [],
98
98
  };
99
99
  loanPrincipalsRemaining[loan.id] = loan.currentBalance;
100
100
  });
@@ -149,7 +149,7 @@ export function payLoans(loans, payment, reduceMinimum = false) {
149
149
  periodsElapsed += periodsToPay;
150
150
  }
151
151
  for (const loan of loans) {
152
- const loanLifetimeInterest = (paymentSchedule[loan.id].amortizationSchedule.reduce((lifetimeInterest, curval) => lifetimeInterest + curval.interest, 0));
152
+ const loanLifetimeInterest = (paymentSchedule[loan.id].amortizationSchedule.reduce((lifetimeInterest, record) => lifetimeInterest + record.interest, 0));
153
153
  paymentSchedule[loan.id].lifetimeInterest = loanLifetimeInterest;
154
154
  paymentSchedule[loan.id].lifetimePrincipal = loan.currentBalance;
155
155
  totalLifetimeInterest += loanLifetimeInterest;
@@ -158,7 +158,7 @@ export function payLoans(loans, payment, reduceMinimum = false) {
158
158
  paymentSchedule.totals = {
159
159
  lifetimeInterest: totalLifetimeInterest,
160
160
  lifetimePrincipal: totalLifetimePrincipal,
161
- amortizationSchedule: totalAmortizationSchedule
161
+ amortizationSchedule: totalAmortizationSchedule,
162
162
  };
163
163
  return paymentSchedule;
164
164
  }
@@ -1,3 +1,6 @@
1
1
  export declare class PaymentTooLowError extends Error {
2
2
  constructor(message: string);
3
3
  }
4
+ export declare class NegativeContributionError extends Error {
5
+ constructor(message: string);
6
+ }
@@ -5,3 +5,10 @@ export class PaymentTooLowError extends Error {
5
5
  this.name = 'PaymentTooLowError';
6
6
  }
7
7
  }
8
+ export class NegativeContributionError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ Object.setPrototypeOf(this, NegativeContributionError.prototype);
12
+ this.name = 'NegativeContributionError';
13
+ }
14
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ *
3
+ * concept of "maximum contribution" (maxContribution)
4
+ * analogous to a "minimum payment" (minPayment) for a Loan
5
+ *
6
+ *
7
+ *
8
+ * current UI is
9
+ * table
10
+ * row-> year
11
+ * column -> totalAnnualContribution
12
+ * cell -> Wealth
13
+ *
14
+ *
15
+ * Want to produce similar to payments.ts:
16
+ * determineExtraPayment [carryover after all MAX contributions are fulfilled]
17
+ * amortizeContributions ** investigate if possible **
18
+ * contributeInvestments [main a la payLoans]
19
+ */
20
+ export type ContributionRecord = {
21
+ period: number;
22
+ contribution: number;
23
+ growth: number;
24
+ currentBalance: number;
25
+ };
26
+ export type ContributionSchedule = {
27
+ lifetimeGrowth: number;
28
+ lifetimeContribution: number;
29
+ amortizationSchedule: ContributionRecord[];
30
+ };
31
+ export type InstrumentsContributionSchedule = Record<string, ContributionSchedule>;
@@ -0,0 +1,20 @@
1
+ /**
2
+ *
3
+ * concept of "maximum contribution" (maxContribution)
4
+ * analogous to a "minimum payment" (minPayment) for a Loan
5
+ *
6
+ *
7
+ *
8
+ * current UI is
9
+ * table
10
+ * row-> year
11
+ * column -> totalAnnualContribution
12
+ * cell -> Wealth
13
+ *
14
+ *
15
+ * Want to produce similar to payments.ts:
16
+ * determineExtraPayment [carryover after all MAX contributions are fulfilled]
17
+ * amortizeContributions ** investigate if possible **
18
+ * contributeInvestments [main a la payLoans]
19
+ */
20
+ export {};
@@ -0,0 +1,35 @@
1
+ /**
2
+ *
3
+ * This file containts functions for computing detailed information on contributing to investments
4
+ *
5
+ */
6
+ import type { Instrument } from './instrument';
7
+ import { ContributionRecord, InstrumentsContributionSchedule } from './contributionTypes';
8
+ /**
9
+ *
10
+ * @param {IInstrument[]} instruments The instruments to allocate maximum contributions
11
+ * @param {number} contribution The amount to contribute across all instruments
12
+ * @returns {number} The extra amount of contribution
13
+ */
14
+ export declare function determineExtraContribution(instruments: Instrument[], contribution: number): number;
15
+ /**
16
+ *
17
+ * Calculates the amortization schedule for an instrument with a contribution
18
+ *
19
+ * @param {Instrument} instrument The instrument to amortize contributions for
20
+ * @param {number} initialBalance The amount invested
21
+ * @param {number} contribution The amount to contribute to the instrument's balance each period
22
+ * @param {number} numContributions The number of periods to make contributions to the instrument
23
+ * @param {boolean} accrueBeforeContribution A flag for ordering operations of accrual (A) and contribution (C)
24
+ * true: A -> C
25
+ * false: C -> A
26
+ * @returns {ContributionRecord[]} The amortization schedule for the number of contributions of contribution made to the instrument
27
+ */
28
+ export declare function amortizeContributions(instrument: Instrument, initialBalance: number, contribution: number | null, numContributions: number, accrueBeforeContribution?: boolean): ContributionRecord[];
29
+ /**
30
+ *
31
+ * @param instruments
32
+ * @param contribution
33
+ * @returns
34
+ */
35
+ export declare function contributeInstruments(instruments: Instrument[], contribution: number, yearsToContribute: number, accrueBeforeContribution?: boolean): InstrumentsContributionSchedule;
@@ -0,0 +1,105 @@
1
+ /**
2
+ *
3
+ * This file containts functions for computing detailed information on contributing to investments
4
+ *
5
+ */
6
+ import * as constants from '../constants';
7
+ /**
8
+ *
9
+ * @param {IInstrument[]} instruments The instruments to allocate maximum contributions
10
+ * @param {number} contribution The amount to contribute across all instruments
11
+ * @returns {number} The extra amount of contribution
12
+ */
13
+ export function determineExtraContribution(instruments, contribution) {
14
+ const totalMaxPayment = instruments.reduce((accumulator, instrument) => accumulator + instrument.periodicContribution(), 0);
15
+ return Math.max(contribution - totalMaxPayment, 0);
16
+ }
17
+ /**
18
+ *
19
+ * Calculates the amortization schedule for an instrument with a contribution
20
+ *
21
+ * @param {Instrument} instrument The instrument to amortize contributions for
22
+ * @param {number} initialBalance The amount invested
23
+ * @param {number} contribution The amount to contribute to the instrument's balance each period
24
+ * @param {number} numContributions The number of periods to make contributions to the instrument
25
+ * @param {boolean} accrueBeforeContribution A flag for ordering operations of accrual (A) and contribution (C)
26
+ * true: A -> C
27
+ * false: C -> A
28
+ * @returns {ContributionRecord[]} The amortization schedule for the number of contributions of contribution made to the instrument
29
+ */
30
+ export function amortizeContributions(instrument, initialBalance, contribution, numContributions, accrueBeforeContribution = true) {
31
+ if (contribution === null) {
32
+ contribution = instrument.annualLimit / instrument.periodsPerYear;
33
+ }
34
+ const amortizationSchedule = [];
35
+ let ytd = 0;
36
+ let currentBalance = initialBalance;
37
+ numContributions = Math.min(numContributions, constants.MAX_DURATION_YEARS * instrument.periodsPerYear);
38
+ for (let period = 0; period < numContributions; period++) {
39
+ const contributionThisPeriod = instrument.validateContribution(contribution, ytd);
40
+ let interestThisPeriod;
41
+ if (accrueBeforeContribution) {
42
+ interestThisPeriod = instrument.accrueInterest(currentBalance);
43
+ currentBalance += contributionThisPeriod + interestThisPeriod;
44
+ }
45
+ else {
46
+ currentBalance += contributionThisPeriod;
47
+ interestThisPeriod = instrument.accrueInterest(currentBalance);
48
+ currentBalance += interestThisPeriod;
49
+ }
50
+ amortizationSchedule.push({
51
+ period: period + 1,
52
+ contribution: contributionThisPeriod,
53
+ growth: interestThisPeriod,
54
+ currentBalance,
55
+ });
56
+ ytd = (period + 1) % instrument.periodsPerYear ? (ytd + contributionThisPeriod) : 0;
57
+ }
58
+ return amortizationSchedule;
59
+ }
60
+ /**
61
+ *
62
+ * @param instruments
63
+ * @param contribution
64
+ * @returns
65
+ */
66
+ export function contributeInstruments(instruments, contribution, yearsToContribute, accrueBeforeContribution = true) {
67
+ let monthlyContribution = contribution;
68
+ const contributionSchedule = {};
69
+ let totalLifetimeContribution = 0;
70
+ let totalLifetimeGrowth = 0;
71
+ let totalAmortizationSchedule = [];
72
+ for (const instrument of instruments) {
73
+ const instrumentContributions = amortizeContributions(instrument, instrument.currentBalance, null, yearsToContribute * instrument.periodsPerYear, accrueBeforeContribution);
74
+ const instrumentLifetimeContribution = instrumentContributions.reduce((lifetimeContribution, record) => lifetimeContribution + record.contribution, 0);
75
+ const instrumentLifetimeGrowth = instrumentContributions.reduce((lifetimeGrowth, record) => lifetimeGrowth + record.growth, 0);
76
+ contributionSchedule[instrument.id] = {
77
+ lifetimeContribution: instrument.currentBalance + instrumentLifetimeContribution,
78
+ lifetimeGrowth: instrumentLifetimeGrowth,
79
+ amortizationSchedule: instrumentContributions,
80
+ };
81
+ totalLifetimeContribution += (instrument.currentBalance + instrumentLifetimeContribution);
82
+ totalLifetimeGrowth += instrumentLifetimeGrowth;
83
+ // ternary handles base case of an empty list
84
+ // naively stole this from payments.ts
85
+ // need to create a new algo to combine
86
+ totalAmortizationSchedule = totalAmortizationSchedule.length ? (totalAmortizationSchedule.map((element) => {
87
+ const matchedInnerElement = instrumentContributions.find((innerElement) => innerElement.period === element.period);
88
+ return (matchedInnerElement != null)
89
+ ? {
90
+ period: element.period,
91
+ contribution: element.contribution + matchedInnerElement.contribution,
92
+ growth: element.growth + matchedInnerElement.growth,
93
+ currentBalance: element.currentBalance +
94
+ matchedInnerElement.currentBalance
95
+ }
96
+ : element;
97
+ })) : instrumentContributions;
98
+ }
99
+ contributionSchedule.totals = {
100
+ lifetimeContribution: totalLifetimeContribution,
101
+ lifetimeGrowth: totalLifetimeGrowth,
102
+ amortizationSchedule: totalAmortizationSchedule,
103
+ };
104
+ return contributionSchedule;
105
+ }
@@ -0,0 +1,51 @@
1
+ export interface IInstrument {
2
+ id: string;
3
+ name: string;
4
+ currentBalance: number;
5
+ annualRate: number;
6
+ periodsPerYear: number;
7
+ periodicRate: number;
8
+ annualLimit: number;
9
+ }
10
+ export declare class Instrument implements IInstrument {
11
+ id: string;
12
+ name: string;
13
+ currentBalance: number;
14
+ annualRate: number;
15
+ periodsPerYear: number;
16
+ periodicRate: number;
17
+ annualLimit: number;
18
+ /**
19
+ *
20
+ * @constructor
21
+ * @param {number} currentBalance The current balance of the instrument
22
+ * @param {Function} annualRate The yearly rate the instrument accrues interest at (simplest case is a closure returning a constant)
23
+ * @param {number} periodsPerYear The number of times the interest accrues in a year
24
+ * @param {string} name The name for the instrument
25
+ * @param {Function} annualLimit (Optional) The maximum amount of money contributable to the instrument in a single year (simplest case is a closure returning a constant)
26
+ * @returns {Instrument}
27
+ */
28
+ constructor(currentBalance: number, annualRate: number, periodsPerYear: number, name: string, annualLimit?: number);
29
+ /**
30
+ *
31
+ * @param {number} contribution The amount to contribute to the instrument
32
+ * @param {number} yearToDate The total amount contributed year-to-date on the instrument
33
+ * @throws {errors.NegativeContributionError} Throws an error when the contribution is less than zero
34
+ * @returns {number} The validated contribution amount
35
+ */
36
+ validateContribution(contribution: number, yearToDate: number): number;
37
+ /**
38
+ * Helper to calculate the periodic contribution for an instrument
39
+ * If the instrument does not have an annual limit, returns 0
40
+ *
41
+ * @returns {number} the amortized "max" contribution for a period
42
+ */
43
+ periodicContribution(): number;
44
+ /**
45
+ *
46
+ * Calculates the amount of interest accrued in a period on a provided principal
47
+ * @param {number} principal The amunt of money owed on an instrument
48
+ * @returns {number} The amount of interest accrued in one period
49
+ */
50
+ accrueInterest(principal?: number): number;
51
+ }
@@ -0,0 +1,59 @@
1
+ import * as errors from "../errors";
2
+ export class Instrument {
3
+ /**
4
+ *
5
+ * @constructor
6
+ * @param {number} currentBalance The current balance of the instrument
7
+ * @param {Function} annualRate The yearly rate the instrument accrues interest at (simplest case is a closure returning a constant)
8
+ * @param {number} periodsPerYear The number of times the interest accrues in a year
9
+ * @param {string} name The name for the instrument
10
+ * @param {Function} annualLimit (Optional) The maximum amount of money contributable to the instrument in a single year (simplest case is a closure returning a constant)
11
+ * @returns {Instrument}
12
+ */
13
+ constructor(currentBalance, annualRate, periodsPerYear, name, annualLimit = 0) {
14
+ this.id = String(Math.floor(Math.random() * Date.now()));
15
+ this.name = name;
16
+ this.currentBalance = currentBalance;
17
+ this.annualRate = annualRate;
18
+ this.periodsPerYear = periodsPerYear;
19
+ this.periodicRate = this.annualRate / this.periodsPerYear;
20
+ this.annualLimit = annualLimit;
21
+ }
22
+ /**
23
+ *
24
+ * @param {number} contribution The amount to contribute to the instrument
25
+ * @param {number} yearToDate The total amount contributed year-to-date on the instrument
26
+ * @throws {errors.NegativeContributionError} Throws an error when the contribution is less than zero
27
+ * @returns {number} The validated contribution amount
28
+ */
29
+ validateContribution(contribution, yearToDate) {
30
+ if (contribution < 0) {
31
+ throw new errors.NegativeContributionError(`contribution of ${contribution} must be greater than/equal to zero`);
32
+ }
33
+ if (this.annualLimit) {
34
+ return Math.min(Math.max(this.annualLimit - yearToDate, 0), contribution);
35
+ }
36
+ return contribution;
37
+ }
38
+ /**
39
+ * Helper to calculate the periodic contribution for an instrument
40
+ * If the instrument does not have an annual limit, returns 0
41
+ *
42
+ * @returns {number} the amortized "max" contribution for a period
43
+ */
44
+ periodicContribution() {
45
+ if (this.annualLimit) {
46
+ return this.annualLimit / this.periodsPerYear;
47
+ }
48
+ return 0;
49
+ }
50
+ /**
51
+ *
52
+ * Calculates the amount of interest accrued in a period on a provided principal
53
+ * @param {number} principal The amunt of money owed on an instrument
54
+ * @returns {number} The amount of interest accrued in one period
55
+ */
56
+ accrueInterest(principal = this.currentBalance) {
57
+ return principal * this.periodicRate;
58
+ }
59
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ *
3
+ * This file contains functions for sorting Arrays of ILoans or IInstruments on certain attributes
4
+ *
5
+ */
6
+ type HasRateAndBalance = {
7
+ annualRate: number;
8
+ currentBalance: number;
9
+ };
10
+ type avalanche = (obj1: HasRateAndBalance, obj2: HasRateAndBalance) => number;
11
+ type snowball = (obj1: HasRateAndBalance, obj2: HasRateAndBalance) => number;
12
+ type sortFunction = avalanche | snowball;
13
+ /**
14
+ * Sorts descending by interest rate
15
+ * @param {HasRateAndBalance} obj1
16
+ * @param {HasRateAndBalance} obj2
17
+ * @returns {number} the order in which to sort the objects in descending interestRate
18
+ */
19
+ export declare function avalanche(obj1: HasRateAndBalance, obj2: HasRateAndBalance): number;
20
+ /**
21
+ * Sorts ascending by principal
22
+ * @param {HasRateAndBalance} obj1
23
+ * @param {HasRateAndBalanceILoan} obj2
24
+ * @returns {number} the order in which to sort the objects in ascending currentBalance
25
+ */
26
+ export declare function snowball(obj1: HasRateAndBalance, obj2: HasRateAndBalance): number;
27
+ /**
28
+ * Sorts a collection (<HasRateAndBalalnce>) using the provided sortFunction
29
+ * @param {HasRateAndBalance[]} sortable The collection to sort
30
+ * @param {function} sortFunction The algorithm to sort the collection with
31
+ * @returns The sorted collection
32
+ */
33
+ export declare function sortWith(sortable: HasRateAndBalance[], sortFunction: sortFunction): HasRateAndBalance[];
34
+ export {};
@@ -0,0 +1,32 @@
1
+ /**
2
+ *
3
+ * This file contains functions for sorting Arrays of ILoans or IInstruments on certain attributes
4
+ *
5
+ */
6
+ /**
7
+ * Sorts descending by interest rate
8
+ * @param {HasRateAndBalance} obj1
9
+ * @param {HasRateAndBalance} obj2
10
+ * @returns {number} the order in which to sort the objects in descending interestRate
11
+ */
12
+ export function avalanche(obj1, obj2) {
13
+ return obj2.annualRate - obj1.annualRate;
14
+ }
15
+ /**
16
+ * Sorts ascending by principal
17
+ * @param {HasRateAndBalance} obj1
18
+ * @param {HasRateAndBalanceILoan} obj2
19
+ * @returns {number} the order in which to sort the objects in ascending currentBalance
20
+ */
21
+ export function snowball(obj1, obj2) {
22
+ return obj1.currentBalance - obj2.currentBalance;
23
+ }
24
+ /**
25
+ * Sorts a collection (<HasRateAndBalalnce>) using the provided sortFunction
26
+ * @param {HasRateAndBalance[]} sortable The collection to sort
27
+ * @param {function} sortFunction The algorithm to sort the collection with
28
+ * @returns The sorted collection
29
+ */
30
+ export function sortWith(sortable, sortFunction) {
31
+ return sortable.sort(sortFunction);
32
+ }