import * as plugins from './calculation.plugins.js'; import { Calculator } from './calculation.classes.calculator.js'; export interface ICashFlow { amount: plugins.Decimal.Value; period?: number; } /** * Financial calculations class providing time value of money and investment analysis functions */ export class Financial extends Calculator { constructor() { super({ precision: 15 }); } /** * Calculate Present Value (PV) * @param futureValue The future value * @param rate The interest rate per period * @param periods The number of periods */ public presentValue( futureValue: plugins.Decimal.Value, rate: plugins.Decimal.Value, periods: plugins.Decimal.Value ): plugins.Decimal { const r = this.decimal(rate); const n = this.decimal(periods); const fv = this.decimal(futureValue); const denominator = this.power(this.add(1, r), n); return this.divide(fv, denominator); } /** * Calculate Future Value (FV) * @param presentValue The present value * @param rate The interest rate per period * @param periods The number of periods */ public futureValue( presentValue: plugins.Decimal.Value, rate: plugins.Decimal.Value, periods: plugins.Decimal.Value ): plugins.Decimal { const r = this.decimal(rate); const n = this.decimal(periods); const pv = this.decimal(presentValue); const multiplier = this.power(this.add(1, r), n); return this.multiply(pv, multiplier); } /** * Calculate Payment (PMT) for a loan * @param principal The loan principal * @param rate The interest rate per period * @param periods The number of periods */ public payment( principal: plugins.Decimal.Value, rate: plugins.Decimal.Value, periods: plugins.Decimal.Value ): plugins.Decimal { const r = this.decimal(rate); const n = this.decimal(periods); const p = this.decimal(principal); if (r.isZero()) { return this.divide(p, n); } const numerator = this.multiply(p, r); const denominator = this.subtract(1, this.power(this.add(1, r), this.multiply(-1, n))); return this.divide(numerator, denominator); } /** * Calculate Net Present Value (NPV) * @param rate The discount rate * @param cashFlows Array of cash flows (first element is initial investment, usually negative) */ public npv(rate: plugins.Decimal.Value, cashFlows: plugins.Decimal.Value[]): plugins.Decimal { const r = this.decimal(rate); return cashFlows.reduce((npv, cashFlow, period) => { const cf = this.decimal(cashFlow); const discountFactor = this.power(this.add(1, r), period); const presentValue = this.divide(cf, discountFactor); return this.add(npv, presentValue); }, this.decimal(0)); } /** * Calculate Internal Rate of Return (IRR) using Newton-Raphson method * @param cashFlows Array of cash flows * @param guess Initial guess for IRR (default: 0.1) * @param tolerance Convergence tolerance (default: 0.000001) * @param maxIterations Maximum iterations (default: 1000) */ public irr( cashFlows: plugins.Decimal.Value[], guess: plugins.Decimal.Value = 0.1, tolerance: plugins.Decimal.Value = 0.000001, maxIterations: number = 1000 ): plugins.Decimal { let rate = this.decimal(guess); const tol = this.decimal(tolerance); for (let i = 0; i < maxIterations; i++) { const npvValue = this.npv(rate, cashFlows); const npvDerivative = this.npvDerivative(rate, cashFlows); if (npvDerivative.isZero()) { throw new Error('IRR calculation failed: derivative is zero'); } const newRate = this.subtract(rate, this.divide(npvValue, npvDerivative)); if (this.abs(this.subtract(newRate, rate)).lessThan(tol)) { return newRate; } rate = newRate; } throw new Error('IRR calculation failed: maximum iterations reached'); } /** * Calculate the derivative of NPV with respect to rate (for IRR calculation) */ private npvDerivative(rate: plugins.Decimal.Value, cashFlows: plugins.Decimal.Value[]): plugins.Decimal { const r = this.decimal(rate); return cashFlows.reduce((derivative, cashFlow, period) => { if (period === 0) return derivative; const cf = this.decimal(cashFlow); const negPeriod = this.decimal(period).neg(); const onePlusR = this.add(1, r); const power = this.add(negPeriod, -1); const discountFactor = this.power(onePlusR, power); const termDerivative = this.multiply(this.multiply(negPeriod, cf), discountFactor); return this.add(derivative, termDerivative); }, this.decimal(0)); } /** * Calculate Modified Internal Rate of Return (MIRR) * @param cashFlows Array of cash flows * @param financeRate Rate for negative cash flows * @param reinvestRate Rate for positive cash flows */ public mirr( cashFlows: plugins.Decimal.Value[], financeRate: plugins.Decimal.Value, reinvestRate: plugins.Decimal.Value ): plugins.Decimal { const fRate = this.decimal(financeRate); const rRate = this.decimal(reinvestRate); const n = cashFlows.length - 1; let pvNegative = this.decimal(0); let fvPositive = this.decimal(0); cashFlows.forEach((cashFlow, period) => { const cf = this.decimal(cashFlow); if (cf.lessThan(0)) { pvNegative = this.add(pvNegative, this.presentValue(cf, fRate, period)); } else { fvPositive = this.add(fvPositive, this.futureValue(cf, rRate, n - period)); } }); if (pvNegative.isZero() || fvPositive.isZero()) { throw new Error('MIRR calculation failed: no negative or positive cash flows'); } const ratio = this.divide(fvPositive, this.abs(pvNegative)); const result = this.subtract(this.power(ratio, this.divide(1, n)), 1); return result; } /** * Calculate the number of periods for an investment to reach a target value * @param presentValue The present value * @param futureValue The future value * @param rate The interest rate per period */ public periods( presentValue: plugins.Decimal.Value, futureValue: plugins.Decimal.Value, rate: plugins.Decimal.Value ): plugins.Decimal { const pv = this.decimal(presentValue); const fv = this.decimal(futureValue); const r = this.decimal(rate); if (r.isZero()) { throw new Error('Cannot calculate periods with zero interest rate'); } const ratio = this.divide(fv, pv); const numerator = this.ln(ratio); const denominator = this.ln(this.add(1, r)); return this.divide(numerator, denominator); } /** * Calculate the interest rate given PV, FV, and periods * @param presentValue The present value * @param futureValue The future value * @param periods The number of periods */ public rate( presentValue: plugins.Decimal.Value, futureValue: plugins.Decimal.Value, periods: plugins.Decimal.Value ): plugins.Decimal { const pv = this.decimal(presentValue); const fv = this.decimal(futureValue); const n = this.decimal(periods); if (n.isZero()) { throw new Error('Cannot calculate rate with zero periods'); } const ratio = this.divide(fv, pv); const exponent = this.divide(1, n); const result = this.subtract(this.power(ratio, exponent), 1); return result; } /** * Calculate Extended Internal Rate of Return (XIRR) for irregular cash flows * @param cashFlows Array of cash flow objects with amounts and dates * @param dates Array of dates corresponding to cash flows * @param guess Initial guess for XIRR (default: 0.1) */ public xirr( cashFlows: plugins.Decimal.Value[], dates: Date[], guess: plugins.Decimal.Value = 0.1 ): plugins.Decimal { if (cashFlows.length !== dates.length) { throw new Error('Cash flows and dates arrays must have the same length'); } const startDate = dates[0]; const yearFractions = dates.map(date => { const daysDiff = (date.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24); return this.divide(daysDiff, 365); }); let rate = this.decimal(guess); const tolerance = this.decimal(0.000001); const maxIterations = 1000; for (let i = 0; i < maxIterations; i++) { const xnpv = this.xnpv(rate, cashFlows, yearFractions); const xnpvDerivative = this.xnpvDerivative(rate, cashFlows, yearFractions); if (xnpvDerivative.isZero()) { throw new Error('XIRR calculation failed: derivative is zero'); } const newRate = this.subtract(rate, this.divide(xnpv, xnpvDerivative)); if (this.abs(this.subtract(newRate, rate)).lessThan(tolerance)) { return newRate; } rate = newRate; } throw new Error('XIRR calculation failed: maximum iterations reached'); } /** * Calculate XNPV for irregular cash flows */ private xnpv( rate: plugins.Decimal.Value, cashFlows: plugins.Decimal.Value[], yearFractions: plugins.Decimal[] ): plugins.Decimal { const r = this.decimal(rate); return cashFlows.reduce((npv, cashFlow, index) => { const cf = this.decimal(cashFlow); const t = yearFractions[index]; const discountFactor = this.power(this.add(1, r), t); const presentValue = this.divide(cf, discountFactor); return this.add(npv, presentValue); }, this.decimal(0)); } /** * Calculate the derivative of XNPV */ private xnpvDerivative( rate: plugins.Decimal.Value, cashFlows: plugins.Decimal.Value[], yearFractions: plugins.Decimal[] ): plugins.Decimal { const r = this.decimal(rate); return cashFlows.reduce((derivative, cashFlow, index) => { const cf = this.decimal(cashFlow); const t = yearFractions[index]; const negT = this.multiply(-1, t); const discountFactor = this.power(this.add(1, r), this.subtract(negT, 1)); const termDerivative = this.multiply(this.multiply(negT, cf), discountFactor); return this.add(derivative, termDerivative); }, this.decimal(0)); } }