Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolves Add annual adjustments for amountPerAnnum #30 #31

Merged
merged 3 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ For example, if you invest $1,000 today at a 7% annual interest rate, how much w
#### Compound Interest

- [x] 1. calculate compound interest of a lump sum over time
- [x] 2. calculate compound interest with additional contributions
- [x] 2. calculate compound interest with additional contributions and annual contribution adjustments
- [x] 3. calculate compound interest with interest only payments towards the principal borrowed
- [x] 4. calculate compound interest with repayments towards the principal

Expand Down Expand Up @@ -74,14 +74,15 @@ const lumpSum = compoundInterestPerPeriod({
paymentsPerAnnum: 12 // displays monthly interest balance
});

// calculate a lump sum over 2 years with additional contributions of 500 per month
// calculate a lump sum over 2 years with additional contributions of 500 per month with 2% adjustment every year
const additionalContributions = compoundInterestPerPeriod({
type: "contribution",
principal: 500,
rate: 3.4,
years: 2,
paymentsPerAnnum: 12,
amountPerAnnum: 6_000,
contributionPerAnnumChange: 2,
accrualOfPaymentsPerAnnum: true
});

Expand Down Expand Up @@ -126,7 +127,8 @@ const repayment = compoundInterestPerPeriod({
###### Contribution Options

- `amountPerAnnum: number` The amount of contributions per annum (eg 6_000 for 500 per month)
- `accrualOfPaymentsPerAnnum: number` If provided payments accrue interest per annum; Otherwise interest is only accrued on the principal payment.
- `accrualOfPaymentsPerAnnum: boolean` If provided payments accrue interest per annum; Otherwise interest is only accrued on the principal payment.
- `contributionPerAnnumChange: number` Changes of annual contribution in percents (to adjust contribution according inflation rates, good for long investments)

###### Debt Repayment Options

Expand Down
26 changes: 26 additions & 0 deletions calc/compoundInterest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -592,4 +592,30 @@ describe("compoundInterestPerPeriod", () => {
});
});
});

describe("contributionPerAnnumChange", () => {
it("when contributionPerAnnumChange is supplied it increases annual additional contribution amountPerAnnum", () => {
const options: IOptions = {
type: "contribution",
principal: 250_000,
rate: 7.8,
years: 25,
paymentsPerAnnum: 1,
amountPerAnnum: 12_000,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice thanks for the update! Last request from me and I will get it merged in 😃

With the test we want to test the expected output of amountPerAnnum is correct, so ideally we would want to test it changes to the correct amount after the first year. You can use currentPositionInYears to get the balance for a specific year.

You can do something like this:

const firstYearOptions = { ...initialOptions, currentPositionInYears: 1 };
const secondYearOptions = { ...initialOptions, currentPositionInYears: 2 };

const firstYearResult = compoundInterestPerPeriod(firstYearOptions);
const secondYearResult = compoundInterestPerPeriod(secondYearOptions);

Side note: I think I just spotted a previous bug with accrualOfPaymentsPerAnnum: false where the end balance is not updating correctly. I am going to raise an issue and a pr for this.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually I just realised we don't actually return amountPerAnnum to check the output, should we return this amount so we can see what the final contributions would be further down the line?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am going to approve this and we can add this as an additional feature

contributionPerAnnumChange: 3,
currentPositionInYears: 1,
accrualOfPaymentsPerAnnum: true
};
const result = compoundInterestPerPeriod(options);
expect(result).toMatchObject(
expect.objectContaining({
currentBalance: 282436,
totalInterest: 2144895.2679852117,
endBalance: 2832406.46,
accrualOfPaymentsPerAnnum: true,
investmentType: "contribution"
})
);
});
});
});
31 changes: 30 additions & 1 deletion calc/compoundInterest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,15 @@ export const calcTotalInvestment = (options: IOptions, investmentType: Investmen
const { principal, years, paymentsPerAnnum = 1 } = options;

if (investmentType === "contribution" && "amountPerAnnum" in options) {
const { amountPerAnnum = 0 } = options;
const { amountPerAnnum = 0, contributionPerAnnumChange = 0 } = options;
// Adjust annual contributions if contributionPerAnnumChange rate provided
if (contributionPerAnnumChange > 0) {
let repaymentWithAnnualChange = amountPerAnnum;
for (let i = 1; i < years; i++) {
repaymentWithAnnualChange += (repaymentWithAnnualChange * contributionPerAnnumChange) / 100 + amountPerAnnum;
}
return principal * years + repaymentWithAnnualChange;
}
return principal + amountPerAnnum * years;
}

Expand Down Expand Up @@ -94,6 +102,18 @@ export const calcInterestPayments = (principal: number, interestRate: number, pa
* amountPerAnnum: 6_000,
* accrualOfPaymentsPerAnnum: true
* });
* @example
* // calculate a lump sum over 2 years with additional contributions of 500 per month with 2% adjustment every year
* const additionalContributions = compoundInterestPerPeriod({
* type: "contribution",
* principal: 500,
* rate: 3.4,
* years: 2,
* paymentsPerAnnum: 12,
* amountPerAnnum: 6_000,
* contributionPerAnnumChange: 2,
* accrualOfPaymentsPerAnnum: true
* });
*
* @example
* // example interest only payment that compounds at 4% per annum
Expand All @@ -115,6 +135,7 @@ export const compoundInterestPerPeriod = (options: IOptions): CompoundInterestRe
const { type: investmentType, principal, years, paymentsPerAnnum = 1, currentPositionInYears } = options;

let amountPerAnnum = 0;
let contributionPerAnnumChange;
let accrualOfPaymentsPerAnnum = false;

if ("amountPerAnnum" in options && options.amountPerAnnum && options.amountPerAnnum > 0) {
Expand All @@ -125,6 +146,10 @@ export const compoundInterestPerPeriod = (options: IOptions): CompoundInterestRe
accrualOfPaymentsPerAnnum = options.accrualOfPaymentsPerAnnum;
}

if ("contributionPerAnnumChange" in options && options.contributionPerAnnumChange !== undefined) {
contributionPerAnnumChange = options.contributionPerAnnumChange;
}

// if rate is provided as a percentage, convert to decimal
if (rate >= 1) {
rate = rate / 100;
Expand Down Expand Up @@ -170,6 +195,10 @@ export const compoundInterestPerPeriod = (options: IOptions): CompoundInterestRe

for (let p = 0; p < paymentsPerAnnum; p++) {
if (accrualOfPaymentsPerAnnum) {
// Adjust contributions only from the 2nd year
if (i >= 1 && contributionPerAnnumChange) {
amountPerAnnum = amountPerAnnum * (1 + contributionPerAnnumChange / 100);
}
const newBalanceWithAccrual = prevBalance + amountPerAnnum / paymentsPerAnnum;
const interest = newBalanceWithAccrual * ratePerPeriod;
prevBalance = prevBalance + interest + amountPerAnnum / paymentsPerAnnum;
Expand Down
1 change: 1 addition & 0 deletions types/calculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export type ContributionOptions = {
type: "contribution";
paymentsPerAnnum?: number;
amountPerAnnum?: number;
contributionPerAnnumChange?: number;
accrualOfPaymentsPerAnnum?: boolean;
};

Expand Down