npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

fundamental-js

v1.4.1

Published

Comprehensive financial calculator library for investing, loans, and personal finance. Includes EMI, SIP, SWP, amortization, CAGR, IRR, NPV, risk metrics, and more.

Readme

fundamental-js

A comprehensive, zero-dependency TypeScript financial calculator library for investing, loans, and personal finance.

All functions are pure, deterministic, and work in both Node.js and browser environments.

Live Demo — Interactive web app showcasing all calculators with visualizations.

Install

npm install fundamental-js

Quick Start

import {
  emi,
  sipFutureValue,
  stepUpSipFutureValue,
  swpPlan,
  xirr,
} from "fundamental-js";

// EMI for a ₹25L loan at 8.5% for 20 years
const loan = emi(2500000, 0.085, 240);
// { emi: 21698.69, totalPayment: 5207685.73, totalInterest: 2707685.73 }

// SIP: ₹25,000/month at 12% for 15 years
const sip = sipFutureValue({
  monthlyInvestment: 25000,
  annualRate: 0.12,
  months: 180,
});
// { futureValue: 12649741.45, totalInvested: 4500000, estimatedGains: 8149741.45 }

// Step-up SIP: ₹25,000/month, 10% annual step-up
const stepUp = stepUpSipFutureValue({
  monthlyInvestment: 25000,
  annualRate: 0.12,
  months: 180,
  stepUpPercentAnnual: 0.10,
});

// SWP: ₹1Cr corpus, ₹50K/month withdrawal at 8%, 6% inflation
const withdrawal = swpPlan({
  initialCorpus: 10000000,
  monthlyWithdrawal: 50000,
  annualRate: 0.08,
  months: 300,
  inflationRateAnnual: 0.06,
});

// XIRR: Irregular cashflows with dates
const result = xirr([
  { date: new Date("2020-01-01"), amount: -100000 },
  { date: new Date("2020-06-15"), amount: 5000 },
  { date: new Date("2021-01-01"), amount: 115000 },
]);
// ~0.1975 (19.75%)

Conventions

All rates and percentages are expressed as decimals throughout the library:

| Meaning | Value | NOT | |---------|-------|-----| | 12% annual return | 0.12 | 12 | | 8.5% interest rate | 0.085 | 8.5 | | 10% annual step-up | 0.10 | 10 | | 6% inflation | 0.06 | 6 |

Other conventions:

  • Period type: 0 = end-of-period (ordinary annuity), 1 = beginning-of-period (annuity due)
  • Day count: XNPV/XIRR default to ACT/365; pass "ACT/360" to override
  • Cash-flow sign: Outflows are negative, inflows are positive (Excel convention for PV/FV/PMT/NPER/RATE)

API Reference

Time Value of Money

Excel-compatible sign convention: cash you pay out is negative, cash you receive is positive.

| Function | Formula | Description | |----------|---------|-------------| | pv(rate, nper, pmt, fv?, type?) | PV = −[PMT·(1+r·type)·((1+r)ⁿ−1)/r + FV] / (1+r)ⁿ | Present Value | | fv(rate, nper, pmt, pv?, type?) | FV = −[PV·(1+r)ⁿ + PMT·(1+r·type)·((1+r)ⁿ−1)/r] | Future Value | | pmt(rate, nper, pv, fv?, type?) | PMT = −[PV·(1+r)ⁿ + FV]·r / [(1+r·type)·((1+r)ⁿ−1)] | Payment per period | | nper(rate, pmt, pv, fv?, type?) | NPER = ln[(PMT·(1+r·type) − FV·r) / (PMT·(1+r·type) + PV·r)] / ln(1+r) | Number of periods | | rate(nper, pmt, pv, fv?, type?, guess?) | Newton-Raphson iterative solver | Solve for periodic rate |

Parameters:

  • rate — periodic interest rate as decimal (e.g., monthly rate = annual / 12)
  • nper — total number of compounding periods
  • type0 = end of period (default), 1 = beginning of period

Cash-Flow Analysis

| Function | Formula | Description | |----------|---------|-------------| | npv(rate, cashflows) | NPV = Σ CFᵢ / (1+r)ⁱ, i = 0…n | Net Present Value (textbook-style: CF₀ at face value) | | irr(cashflows, guess?) | Solves NPV = 0 | Internal Rate of Return | | xnpv(rate, cashflows, dayCount?) | XNPV = Σ CFᵢ / (1+r)^(dᵢ/365) | NPV with irregular dates | | xirr(cashflows, guess?, dayCount?) | Solves XNPV = 0 | IRR with irregular dates |

Note on NPV: This is the textbook NPV where cashflows[0] is at time 0 (not discounted). Excel's NPV() discounts from period 1; to replicate Excel in this library, do: npv(rate, [0, ...cashflows]) or cashflows[0] + npv(rate, cashflows.slice(1)).

IRR/XIRR validation: Both functions throw an Error if cash flows do not contain at least one positive and one negative value (no sign change means no valid rate exists).

Parameters:

  • cashflows for NPV/IRR: number[]
  • cashflows for XNPV/XIRR: { date: Date, amount: number }[]
  • dayCount"ACT/365" (default) or "ACT/360"

Returns Analysis

| Function | Formula | Description | |----------|---------|-------------| | absoluteReturn(beginValue, endValue) | (end − begin) / begin | Simple return as decimal | | cagr(beginValue, endValue, years) | (end / begin)^(1/years) − 1 | Compound Annual Growth Rate | | annualizedReturn(beginValue, endValue, days) | (end / begin)^(365/days) − 1 | Annualized return using ACT/365 | | trailingReturn(series, windowDays) | Simple return from nearest point to cutoff | Trailing return over a rolling window |

Parameters:

  • series for trailingReturn: { date: Date, value: number }[]
  • windowDays — lookback window in calendar days

SIP / Lumpsum / SWP

| Function | Description | |----------|-------------| | sipFutureValue(params) | SIP future value with optional annual step-up | | stepUpSipFutureValue(params) | SIP with required annual step-up (delegates to sipFutureValue) | | lumpsumFutureValue(principal, annualRate, years) | FV = P × (1 + r)^t | | swpPlan(params) | Systematic Withdrawal Plan with month-by-month schedule | | inflationAdjustedValue(value, inflationRate, years) | value / (1 + inflation)^years | | realReturn(nominalRate, inflationRate) | Fisher equation: (1 + nominal) / (1 + inflation) − 1 |

sipFutureValue params:

{
  monthlyInvestment: number,
  annualRate: number,          // 0.12 = 12%
  months: number,
  stepUpPercentAnnual?: number, // 0.10 = 10% annual increase
  investmentAt?: "begin" | "end"
}

SIP formula (end-of-period, no step-up): FV = P × [(1+r)ⁿ − 1] / r, where r = annualRate / 12

SIP formula (beginning-of-period): FV = P × (1+r) × [(1+r)ⁿ − 1] / r

With step-up: monthly investment increases by stepUpPercentAnnual every 12 months, computed iteratively.

Returns: { futureValue, totalInvested, estimatedGains }

swpPlan params:

{
  initialCorpus: number,
  monthlyWithdrawal: number,
  annualRate: number,           // 0.08 = 8%
  months: number,
  inflationRateAnnual?: number, // 0.06 = 6% (withdrawal increases annually)
  withdrawalAt?: "begin" | "end"
}

Returns: { endingCorpus, totalWithdrawn, schedule[] }

Goal Planning

| Function | Formula | Description | |----------|---------|-------------| | requiredMonthlyInvestmentForGoal(params) | Binary search over sipFutureValue | Required monthly SIP for a target amount | | requiredLumpsumForGoal(goal, annualRate, years) | goal / (1 + r)^years | Required lumpsum (present value of goal) |

requiredMonthlyInvestmentForGoal params:

{
  goalAmountFuture: number,
  annualRate: number,           // 0.12 = 12%
  months: number,
  stepUpPercentAnnual?: number, // 0.10 = 10%
  investmentAt?: "begin" | "end"
}

Returns: { requiredMonthlyInvestment, assumptions }

Loans

| Function | Formula | Description | |----------|---------|-------------| | emi(principal, annualRate, months) | EMI = P × r × (1+r)ⁿ / [(1+r)ⁿ − 1] | Equated Monthly Installment | | amortizationSchedule(params) | Month-by-month principal/interest split | Full loan schedule with extra payments | | prepaymentImpact(params) | Compares original vs prepaid loan | Interest saved and tenure reduction |

EMI formula: r = annualRate / 12. When annualRate = 0, EMI = principal / months.

Returns: { emi, totalPayment, totalInterest }

amortizationSchedule params:

{
  principal: number,
  annualRate: number,
  months: number,
  extraPaymentMonthly?: number,
  extraPayments?: { month: number, amount: number }[]
}

Returns: { emi, schedule[], totalInterest, totalPaid, payoffMonth }

prepaymentImpact params:

{
  principal: number,
  annualRate: number,
  months: number,
  prepayments: { month: number, amount: number }[],
  mode: "reduceTenure" | "reduceEmi"
}
  • reduceTenure — keeps EMI constant, pays off loan early
  • reduceEmi — keeps tenure constant, recalculates lower EMI after each prepayment

Returns: { original, new, savings: { interestSaved, monthsSaved } }

Risk Metrics

| Function | Formula | Description | |----------|---------|-------------| | volatility(returns, periodsPerYear?) | σ = s(returns) × √periodsPerYear | Annualized volatility (sample std dev) | | sharpe(returns, riskFreeRate?, periodsPerYear?) | (R̄·T − Rf) / σ | Sharpe Ratio (Sharpe 1994) | | sortino(returns, riskFreeRate?, periodsPerYear?) | (R̄·T − Rf) / DD | Sortino Ratio (Sortino & Price 1994) | | maxDrawdown(values) | max((peak − trough) / peak) | Maximum peak-to-trough decline |

Volatility: Uses sample standard deviation (N−1 denominator) × √periodsPerYear for annualization.

Sharpe Ratio: Arithmetic annualization of mean return. SR = (meanReturn × periodsPerYear − riskFreeRateAnnual) / volatility

Sortino Ratio: Downside deviation uses the full sample size N as denominator (per Sortino & Price 1994), not just the count of negative returns: DD = √(Σ min(rᵢ − MAR, 0)² / N) × √periodsPerYear

Returns Infinity when no downside returns exist (zero downside risk).

Parameters:

  • returns — array of periodic returns as decimals (e.g., daily: 0.01 = 1%)
  • values — array of portfolio values (for maxDrawdown)
  • periodsPerYear — defaults to 252 (daily trading days)
  • riskFreeRate — annual risk-free rate as decimal

Portfolio Helpers

| Function | Description | |----------|-------------| | weightedReturn(returns, weights) | Weighted average return: Σ(rᵢ × wᵢ) | | rebalance(targetWeights, currentValues) | Calculate trades to reach target allocation |

rebalance returns: { trades, newValues, total } — positive trade = buy, negative = sell

Utilities

| Function | Description | |----------|-------------| | safeNum(value, fallback?) | Returns fallback if value is NaN/Infinity/null/undefined |

Migration from v1.2.x

Breaking change in v1.3.0: stepUpPercentAnnual and inflationRateAnnual (in SWP) now follow the same decimal convention as all other rates. Previously they expected whole-number percentages (e.g., 10 for 10%); now they expect decimals (e.g., 0.10 for 10%).

- sipFutureValue({ monthlyInvestment: 25000, annualRate: 0.12, months: 180, stepUpPercentAnnual: 10 })
+ sipFutureValue({ monthlyInvestment: 25000, annualRate: 0.12, months: 180, stepUpPercentAnnual: 0.10 })

- swpPlan({ initialCorpus: 10000000, monthlyWithdrawal: 50000, annualRate: 0.08, months: 300, inflationRateAnnual: 6 })
+ swpPlan({ initialCorpus: 10000000, monthlyWithdrawal: 50000, annualRate: 0.08, months: 300, inflationRateAnnual: 0.06 })

Error Handling

Most functions return safe defaults (0, empty arrays) for invalid inputs, making them safe for reactive UIs. Exceptions:

  • irr() and xirr() throw an Error if cash flows don't contain at least one positive and one negative value
  • sortino() returns Infinity when there are no downside returns

License

MIT