feat: core financial engine (PMT, amortization, simulation calculations)

This commit is contained in:
2026-02-22 20:00:28 +01:00
parent 8f9674730d
commit 87370d8fad
5 changed files with 700 additions and 0 deletions

54
src/lib/financial.ts Normal file
View File

@@ -0,0 +1,54 @@
/**
* Core financial functions replicating Excel's PMT, IPMT, PPMT.
* All functions return positive values (Excel returns negative for payments).
*/
/**
* PMT — Fixed periodic payment for a loan.
* Equivalent to Excel: -PMT(rate, nper, pv)
*/
export function pmt(rate: number, nper: number, pv: number): number {
if (pv <= 0 || nper <= 0) return 0;
if (rate === 0) return pv / nper;
const factor = Math.pow(1 + rate, nper);
return (pv * rate * factor) / (factor - 1);
}
/**
* IPMT — Interest portion of a specific payment period.
* Equivalent to Excel: -IPMT(rate, per, nper, pv)
* @param per 1-based period number
*/
export function ipmt(rate: number, per: number, nper: number, pv: number): number {
if (rate === 0 || pv <= 0 || nper <= 0) return 0;
const bal = remainingBalance(rate, per - 1, nper, pv);
return bal * rate;
}
/**
* PPMT — Principal portion of a specific payment period.
* Equivalent to Excel: -PPMT(rate, per, nper, pv)
* @param per 1-based period number
*/
export function ppmt(rate: number, per: number, nper: number, pv: number): number {
if (pv <= 0 || nper <= 0) return 0;
return pmt(rate, nper, pv) - ipmt(rate, per, nper, pv);
}
/**
* Remaining balance after `per` payments.
* @param per Number of payments already made (0 = initial balance)
*/
export function remainingBalance(rate: number, per: number, nper: number, pv: number): number {
if (pv <= 0 || nper <= 0) return 0;
if (per <= 0) return pv;
if (per >= nper) return 0;
if (rate === 0) {
return pv - pmt(rate, nper, pv) * per;
}
const payment = pmt(rate, nper, pv);
const factor = Math.pow(1 + rate, per);
return pv * factor - payment * (factor - 1) / rate;
}