feat(core): initial release of financial calculation package with decimal precision
Some checks failed
CI Pipeline (nottags) / security (push) Successful in 17s
CI Pipeline (tags) / security (push) Successful in 17s
CI Pipeline (nottags) / test (push) Failing after 52s
CI Pipeline (tags) / test (push) Failing after 50s
CI Pipeline (tags) / release (push) Has been skipped
CI Pipeline (tags) / metadata (push) Has been skipped
Some checks failed
CI Pipeline (nottags) / security (push) Successful in 17s
CI Pipeline (tags) / security (push) Successful in 17s
CI Pipeline (nottags) / test (push) Failing after 52s
CI Pipeline (tags) / test (push) Failing after 50s
CI Pipeline (tags) / release (push) Has been skipped
CI Pipeline (tags) / metadata (push) Has been skipped
This commit is contained in:
297
ts/calculation.classes.interest.ts
Normal file
297
ts/calculation.classes.interest.ts
Normal file
@@ -0,0 +1,297 @@
|
||||
import * as plugins from './calculation.plugins.js';
|
||||
import { Calculator } from './calculation.classes.calculator.js';
|
||||
|
||||
export type CompoundingFrequency = 'annually' | 'semiannually' | 'quarterly' | 'monthly' | 'weekly' | 'daily' | 'continuous';
|
||||
|
||||
export interface IInterestOptions {
|
||||
principal: plugins.Decimal.Value;
|
||||
rate: plugins.Decimal.Value;
|
||||
time: plugins.Decimal.Value;
|
||||
compoundingFrequency?: CompoundingFrequency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interest calculations class providing various interest computation methods
|
||||
*/
|
||||
export class Interest extends Calculator {
|
||||
private readonly frequencyMap: Record<CompoundingFrequency, number> = {
|
||||
annually: 1,
|
||||
semiannually: 2,
|
||||
quarterly: 4,
|
||||
monthly: 12,
|
||||
weekly: 52,
|
||||
daily: 365,
|
||||
continuous: 0
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super({ precision: 15 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate simple interest
|
||||
* @param principal The principal amount
|
||||
* @param rate The annual interest rate (as decimal, e.g., 0.05 for 5%)
|
||||
* @param time The time period in years
|
||||
*/
|
||||
public simple(
|
||||
principal: plugins.Decimal.Value,
|
||||
rate: plugins.Decimal.Value,
|
||||
time: plugins.Decimal.Value
|
||||
): plugins.Decimal {
|
||||
const p = this.decimal(principal);
|
||||
const r = this.decimal(rate);
|
||||
const t = this.decimal(time);
|
||||
|
||||
return this.multiply(p, r, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate simple interest amount (principal + interest)
|
||||
*/
|
||||
public simpleAmount(
|
||||
principal: plugins.Decimal.Value,
|
||||
rate: plugins.Decimal.Value,
|
||||
time: plugins.Decimal.Value
|
||||
): plugins.Decimal {
|
||||
const p = this.decimal(principal);
|
||||
const interest = this.simple(principal, rate, time);
|
||||
|
||||
return this.add(p, interest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate compound interest
|
||||
* @param principal The principal amount
|
||||
* @param rate The annual interest rate (as decimal)
|
||||
* @param time The time period in years
|
||||
* @param frequency The compounding frequency (default: annually)
|
||||
*/
|
||||
public compound(
|
||||
principal: plugins.Decimal.Value,
|
||||
rate: plugins.Decimal.Value,
|
||||
time: plugins.Decimal.Value,
|
||||
frequency: CompoundingFrequency = 'annually'
|
||||
): plugins.Decimal {
|
||||
const p = this.decimal(principal);
|
||||
const r = this.decimal(rate);
|
||||
const t = this.decimal(time);
|
||||
|
||||
if (frequency === 'continuous') {
|
||||
return this.continuousCompound(principal, rate, time);
|
||||
}
|
||||
|
||||
const n = this.decimal(this.frequencyMap[frequency]);
|
||||
const amount = this.compoundAmount(principal, rate, time, frequency);
|
||||
|
||||
return this.subtract(amount, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate compound interest amount (principal + interest)
|
||||
*/
|
||||
public compoundAmount(
|
||||
principal: plugins.Decimal.Value,
|
||||
rate: plugins.Decimal.Value,
|
||||
time: plugins.Decimal.Value,
|
||||
frequency: CompoundingFrequency = 'annually'
|
||||
): plugins.Decimal {
|
||||
const p = this.decimal(principal);
|
||||
const r = this.decimal(rate);
|
||||
const t = this.decimal(time);
|
||||
|
||||
if (frequency === 'continuous') {
|
||||
return this.continuousCompoundAmount(principal, rate, time);
|
||||
}
|
||||
|
||||
const n = this.decimal(this.frequencyMap[frequency]);
|
||||
const ratePerPeriod = this.divide(r, n);
|
||||
const periods = this.multiply(n, t);
|
||||
const multiplier = this.power(this.add(1, ratePerPeriod), periods);
|
||||
|
||||
return this.multiply(p, multiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate continuous compound interest
|
||||
*/
|
||||
private continuousCompound(
|
||||
principal: plugins.Decimal.Value,
|
||||
rate: plugins.Decimal.Value,
|
||||
time: plugins.Decimal.Value
|
||||
): plugins.Decimal {
|
||||
const p = this.decimal(principal);
|
||||
const amount = this.continuousCompoundAmount(principal, rate, time);
|
||||
|
||||
return this.subtract(amount, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate continuous compound interest amount
|
||||
*/
|
||||
private continuousCompoundAmount(
|
||||
principal: plugins.Decimal.Value,
|
||||
rate: plugins.Decimal.Value,
|
||||
time: plugins.Decimal.Value
|
||||
): plugins.Decimal {
|
||||
const p = this.decimal(principal);
|
||||
const r = this.decimal(rate);
|
||||
const t = this.decimal(time);
|
||||
|
||||
const exponent = this.multiply(r, t);
|
||||
const multiplier = this.exp(exponent);
|
||||
|
||||
return this.multiply(p, multiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate effective annual rate (EAR) from nominal rate
|
||||
* @param nominalRate The nominal annual interest rate
|
||||
* @param frequency The compounding frequency
|
||||
*/
|
||||
public effectiveAnnualRate(
|
||||
nominalRate: plugins.Decimal.Value,
|
||||
frequency: CompoundingFrequency = 'annually'
|
||||
): plugins.Decimal {
|
||||
const r = this.decimal(nominalRate);
|
||||
|
||||
if (frequency === 'continuous') {
|
||||
return this.subtract(this.exp(r), 1);
|
||||
}
|
||||
|
||||
const n = this.decimal(this.frequencyMap[frequency]);
|
||||
const ratePerPeriod = this.divide(r, n);
|
||||
const onePlusRate = this.add(1, ratePerPeriod);
|
||||
const compounded = this.power(onePlusRate, n);
|
||||
|
||||
return this.subtract(compounded, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate nominal rate from effective annual rate
|
||||
* @param effectiveRate The effective annual rate
|
||||
* @param frequency The compounding frequency
|
||||
*/
|
||||
public nominalRate(
|
||||
effectiveRate: plugins.Decimal.Value,
|
||||
frequency: CompoundingFrequency = 'annually'
|
||||
): plugins.Decimal {
|
||||
const ear = this.decimal(effectiveRate);
|
||||
|
||||
if (frequency === 'continuous') {
|
||||
return this.ln(this.add(1, ear));
|
||||
}
|
||||
|
||||
const n = this.decimal(this.frequencyMap[frequency]);
|
||||
const onePlusEar = this.add(1, ear);
|
||||
const exponent = this.divide(1, n);
|
||||
const nthRoot = this.power(onePlusEar, exponent);
|
||||
const ratePerPeriod = this.subtract(nthRoot, 1);
|
||||
|
||||
return this.multiply(ratePerPeriod, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the real interest rate adjusted for inflation
|
||||
* @param nominalRate The nominal interest rate
|
||||
* @param inflationRate The inflation rate
|
||||
*/
|
||||
public realRate(
|
||||
nominalRate: plugins.Decimal.Value,
|
||||
inflationRate: plugins.Decimal.Value
|
||||
): plugins.Decimal {
|
||||
const r = this.decimal(nominalRate);
|
||||
const i = this.decimal(inflationRate);
|
||||
|
||||
const numerator = this.subtract(r, i);
|
||||
const denominator = this.add(1, i);
|
||||
|
||||
return this.divide(numerator, denominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the time required to double an investment
|
||||
* @param rate The interest rate
|
||||
* @param frequency The compounding frequency
|
||||
*/
|
||||
public doubleTime(
|
||||
rate: plugins.Decimal.Value,
|
||||
frequency: CompoundingFrequency = 'annually'
|
||||
): plugins.Decimal {
|
||||
const r = this.decimal(rate);
|
||||
|
||||
if (r.isZero()) {
|
||||
throw new Error('Cannot calculate doubling time with zero interest rate');
|
||||
}
|
||||
|
||||
if (frequency === 'continuous') {
|
||||
return this.divide(this.ln(2), r);
|
||||
}
|
||||
|
||||
const n = this.decimal(this.frequencyMap[frequency]);
|
||||
const ratePerPeriod = this.divide(r, n);
|
||||
const numerator = this.ln(2);
|
||||
const denominator = this.multiply(n, this.ln(this.add(1, ratePerPeriod)));
|
||||
|
||||
return this.divide(numerator, denominator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the Rule of 72 approximation for doubling time
|
||||
* @param rate The annual interest rate (as percentage, e.g., 5 for 5%)
|
||||
*/
|
||||
public ruleOf72(rate: plugins.Decimal.Value): plugins.Decimal {
|
||||
const r = this.decimal(rate);
|
||||
|
||||
if (r.isZero()) {
|
||||
throw new Error('Cannot calculate Rule of 72 with zero interest rate');
|
||||
}
|
||||
|
||||
return this.divide(72, r);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate periodic interest rate from annual rate
|
||||
* @param annualRate The annual interest rate
|
||||
* @param frequency The compounding frequency
|
||||
*/
|
||||
public periodicRate(
|
||||
annualRate: plugins.Decimal.Value,
|
||||
frequency: CompoundingFrequency
|
||||
): plugins.Decimal {
|
||||
if (frequency === 'continuous') {
|
||||
throw new Error('Periodic rate not applicable for continuous compounding');
|
||||
}
|
||||
|
||||
const r = this.decimal(annualRate);
|
||||
const n = this.decimal(this.frequencyMap[frequency]);
|
||||
|
||||
return this.divide(r, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate annual percentage yield (APY)
|
||||
* Same as effective annual rate but commonly used in banking
|
||||
*/
|
||||
public apy(
|
||||
nominalRate: plugins.Decimal.Value,
|
||||
frequency: CompoundingFrequency = 'annually'
|
||||
): plugins.Decimal {
|
||||
return this.effectiveAnnualRate(nominalRate, frequency);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate interest on interest (compound interest minus simple interest)
|
||||
*/
|
||||
public interestOnInterest(
|
||||
principal: plugins.Decimal.Value,
|
||||
rate: plugins.Decimal.Value,
|
||||
time: plugins.Decimal.Value,
|
||||
frequency: CompoundingFrequency = 'annually'
|
||||
): plugins.Decimal {
|
||||
const simpleInt = this.simple(principal, rate, time);
|
||||
const compoundInt = this.compound(principal, rate, time, frequency);
|
||||
|
||||
return this.subtract(compoundInt, simpleInt);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user