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.
- package/README.md +3 -1
- package/build/index.d.ts +10 -7
- package/build/index.js +8 -6
- package/build/lib/constants.d.ts +4 -0
- package/build/lib/constants.js +6 -0
- package/build/lib/{loan.d.ts → debt/loan.d.ts} +3 -1
- package/build/lib/{loan.js → debt/loan.js} +4 -2
- package/build/lib/{paymentTypes.d.ts → debt/paymentTypes.d.ts} +2 -2
- package/build/lib/{payments.d.ts → debt/payments.d.ts} +3 -3
- package/build/lib/{payments.js → debt/payments.js} +7 -7
- package/build/lib/errors.d.ts +3 -0
- package/build/lib/errors.js +7 -0
- package/build/lib/investment/contributionTypes.d.ts +31 -0
- package/build/lib/investment/contributionTypes.js +20 -0
- package/build/lib/investment/contributions.d.ts +35 -0
- package/build/lib/investment/contributions.js +105 -0
- package/build/lib/investment/instrument.d.ts +51 -0
- package/build/lib/investment/instrument.js +59 -0
- package/build/lib/shared/sorting.d.ts +34 -0
- package/build/lib/shared/sorting.js +32 -0
- package/coverage/clover.xml +247 -72
- package/coverage/coverage-final.json +11 -8
- package/coverage/index.html +58 -13
- package/coverage/src/index.html +5 -5
- package/coverage/src/index.ts.html +54 -12
- package/coverage/src/lib/constants.ts.html +28 -4
- package/coverage/src/lib/{helperFunctions.ts.html → debt/helperFunctions.ts.html} +10 -10
- package/coverage/src/lib/debt/index.html +161 -0
- package/coverage/src/lib/{loan.ts.html → debt/loan.ts.html} +19 -13
- package/coverage/src/lib/{paymentTypes.ts.html → debt/paymentTypes.ts.html} +12 -12
- package/coverage/src/lib/{payments.ts.html → debt/payments.ts.html} +32 -32
- package/coverage/src/lib/errors.ts.html +30 -6
- package/coverage/src/lib/index.html +11 -86
- package/coverage/src/lib/investment/contributionTypes.ts.html +187 -0
- package/coverage/src/lib/investment/contributions.ts.html +535 -0
- package/coverage/src/lib/investment/index.html +146 -0
- package/coverage/src/lib/investment/instrument.ts.html +361 -0
- package/coverage/src/lib/shared/index.html +116 -0
- package/coverage/src/lib/{sorting.ts.html → shared/sorting.ts.html} +32 -32
- package/package.json +4 -1
- package/src/index.ts +22 -8
- package/src/lib/constants.ts +8 -1
- package/src/lib/{loan.ts → debt/loan.ts} +4 -2
- package/src/lib/{paymentTypes.ts → debt/paymentTypes.ts} +2 -2
- package/src/lib/{payments.ts → debt/payments.ts} +19 -19
- package/src/lib/errors.ts +8 -0
- package/src/lib/investment/contributionTypes.ts +34 -0
- package/src/lib/investment/contributions.ts +150 -0
- package/src/lib/investment/instrument.ts +92 -0
- package/src/lib/shared/sorting.ts +41 -0
- package/build/lib/sorting.d.ts +0 -31
- package/build/lib/sorting.js +0 -32
- package/src/lib/sorting.ts +0 -41
- /package/build/lib/{helperFunctions.d.ts → debt/helperFunctions.d.ts} +0 -0
- /package/build/lib/{helperFunctions.js → debt/helperFunctions.js} +0 -0
- /package/build/lib/{paymentTypes.js → debt/paymentTypes.js} +0 -0
- /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
|
-
-
|
|
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 {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
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 {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
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';
|
package/build/lib/constants.d.ts
CHANGED
package/build/lib/constants.js
CHANGED
|
@@ -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
|
|
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 '
|
|
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
|
|
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
|
|
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:
|
|
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 {
|
|
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 {
|
|
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):
|
|
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 '
|
|
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((
|
|
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 {
|
|
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,
|
|
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
|
}
|
package/build/lib/errors.d.ts
CHANGED
package/build/lib/errors.js
CHANGED
|
@@ -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
|
+
}
|