
Time Value Of Money
- 403 installs
- 161 repo stars
- Updated July 18, 2026
- joellewis/finance_skills
time-value-of-money is a Claude Code finance skill that calculates PV, FV, NPV, IRR, annuities, perpetuities, and loan amortization for developers who need discounted-cash-flow math in pricing, fundraising, or fintech fe
About
time-value-of-money is a core finance_skills plugin skill (276 installs) that teaches present value, future value, NPV, IRR, annuities, perpetuities, and loan amortization with worked examples and a stdlib-only Python reference script. The SKILL.md documents 12 key formulas covering discrete and continuous compounding, ordinary and annuity-due payments, growing annuities, Gordon growth perpetuities, and Newton-Raphson IRR solving across six compounding frequencies from annual to continuous. A bundled scripts/time_value_of_money.py exposes 11 functions plus an AmortizationSchedule class, runnable with uv run and self-verifying against a $300,000 mortgage payment of $1,896.20 and a five-year project NPV of $17,378.78 at 10%. Developers reach for time-value-of-money when discounting SaaS cash flows, comparing capex alternatives, modeling subscription unit economics, or generating amortization tables inside fintech backends.
- NPV and IRR framing
- discount rate selection
- amortization schedules
- subscription payback
- scenario sensitivity
Time Value Of Money by the numbers
- 403 all-time installs (skills.sh)
- +16 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #249 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/joellewis/finance_skills --skill time-value-of-moneyAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 403 |
|---|---|
| repo stars | ★ 161 |
| Last updated | July 18, 2026 |
| Repository | joellewis/finance_skills ↗ |
How do you calculate NPV and IRR from cash flows?
Model NPV, IRR, discount rates, loan amortization, and investment comparisons when evaluating pricing, fundraising, capex, or subscription unit economics.
Who is it for?
Developers building fintech, billing, or internal finance tools who need verified TVM formulas instead of re-deriving discount math from scratch.
Skip if: Developers who only need portfolio money-weighted return (MWR) metrics, which the related return-calculations skill covers instead.
When should I use this skill?
A developer asks to discount cash flows, compare investments with different timing, compute loan payments, or build an amortization table.
What you get
Discounted cash-flow valuations, IRR hurdle-rate checks, loan payment amounts, and period-by-period amortization schedule tables.
- NPV and IRR valuations
- loan amortization schedules
- discounted cash-flow models
By the numbers
- 276 installs on the Skillselion catalog
- 11 Python functions plus AmortizationSchedule class in the reference script
- 12 key formulas documented in SKILL.md with 2 worked numerical examples
Files
Time Value of Money
Core Concepts
Future Value (FV)
The value of a present sum after earning interest for n periods at rate r per period.
$$FV = PV \times (1 + r)^n$$
Future value grows exponentially with time, which is the mathematical basis of compound interest.
Present Value (PV)
The current worth of a future sum, discounted back at rate r for n periods. This is the inverse of future value.
$$PV = \frac{FV}{(1 + r)^n}$$
Present value is the cornerstone of all valuation: a dollar today is worth more than a dollar tomorrow because of the opportunity cost of capital.
Compounding Conventions
Interest can compound at different frequencies. The nominal annual rate r_nom compounded m times per year produces different effective yields.
Discrete compounding (m times per year):
$$FV = PV \times \left(1 + \frac{r_{nom}}{m}\right)^{m \times t}$$
Continuous compounding:
$$FV = PV \times e^{r \times t}$$
Effective Annual Rate (EAR):
$$EAR = \left(1 + \frac{r_{nom}}{m}\right)^m - 1$$
For continuous compounding: EAR = e^(r_nom) - 1
Common frequencies:
| Frequency | m |
|---|---|
| Annual | 1 |
| Semi-annual | 2 |
| Quarterly | 4 |
| Monthly | 12 |
| Daily | 365 |
| Continuous | infinity |
Ordinary Annuity
A series of equal payments made at the end of each period for n periods.
Present Value:
$$PV = PMT \times \frac{1 - (1 + r)^{-n}}{r}$$
Future Value:
$$FV = PMT \times \frac{(1 + r)^n - 1}{r}$$
Annuity Due
A series of equal payments made at the beginning of each period. Each cash flow is one period closer than in an ordinary annuity, so values are scaled by (1 + r).
Present Value:
$$PV = PMT \times \frac{1 - (1 + r)^{-n}}{r} \times (1 + r)$$
Future Value:
$$FV = PMT \times \frac{(1 + r)^n - 1}{r} \times (1 + r)$$
Growing Annuity
A finite series of payments that grow at a constant rate g per period, where g != r.
Present Value:
$$PV = \frac{PMT}{r - g} \times \left[1 - \left(\frac{1 + g}{1 + r}\right)^n\right]$$
This is widely used in equity valuation (e.g., multi-stage dividend discount models) and salary/pension projections.
Perpetuity
An infinite stream of equal payments.
$$PV = \frac{PMT}{r}$$
Growing Perpetuity
An infinite stream of payments growing at constant rate g, where g < r for convergence.
$$PV = \frac{PMT}{r - g}$$
This is the Gordon Growth Model when applied to dividends.
Net Present Value (NPV)
The sum of all discounted cash flows, including the initial investment. A positive NPV indicates value creation.
$$NPV = \sum_{t=0}^{T} \frac{CF_t}{(1 + r)^t}$$
Typically, CF_0 is a negative outflow (initial investment), and subsequent CF_t are inflows.
Internal Rate of Return (IRR)
The discount rate r that makes the NPV of all cash flows exactly zero.
$$0 = \sum_{t=0}^{T} \frac{CF_t}{(1 + r)^t}$$
IRR is solved numerically (Newton-Raphson or bisection) since there is no closed-form solution for general cash flow streams. For conventional cash flows (one sign change), a unique IRR exists.
Amortization
Each payment on an amortizing loan is split into an interest component and a principal component:
- Interest portion:
Interest_t = Balance_{t-1} * r - Principal portion:
Principal_t = PMT - Interest_t - Remaining balance:
Balance_t = Balance_{t-1} - Principal_t
Over time, the interest portion decreases and the principal portion increases.
Key Formulas
| Formula | Expression | Use Case |
|---|---|---|
| Future Value | FV = PV * (1 + r)^n | Compound a lump sum forward |
| Present Value | PV = FV / (1 + r)^n | Discount a future lump sum |
| EAR | (1 + r_nom/m)^m - 1 | Compare rates across compounding frequencies |
| Continuous FV | FV = PV * e^(r*t) | Continuous compounding |
| Ordinary Annuity PV | PMT * [1 - (1+r)^(-n)] / r | Loan payments, lease valuation |
| Annuity Due PV | PMT * [1 - (1+r)^(-n)] / r * (1+r) | Rent, insurance (paid in advance) |
| Growing Annuity PV | PMT/(r-g) * [1 - ((1+g)/(1+r))^n] | Salary streams, growing dividends |
| Perpetuity PV | PMT / r | Preferred stock, consol bonds |
| Growing Perpetuity PV | PMT / (r - g) | Gordon Growth Model |
| NPV | sum(CF_t / (1+r)^t) | Project/investment evaluation |
| IRR | solve: sum(CF_t / (1+r)^t) = 0 | Return metric for uneven cash flows |
Worked Examples
Example 1: Monthly Mortgage Payment
Given: A $300,000 mortgage at a 6.5% annual interest rate, fixed for 30 years, with monthly payments (ordinary annuity).
Calculate: The monthly payment amount.
Solution:
First, convert the annual rate to a monthly rate and years to months:
r_monthly = 0.065 / 12 = 0.00541667
n = 30 * 12 = 360 monthsUsing the ordinary annuity present value formula, solve for PMT:
PV = PMT * [1 - (1 + r)^(-n)] / r
300,000 = PMT * [1 - (1.00541667)^(-360)] / 0.00541667Compute the annuity factor:
(1.00541667)^360 = 6.99179
(1.00541667)^(-360) = 0.143010
1 - 0.143010 = 0.856990
0.856990 / 0.00541667 = 158.2108Solve for PMT:
PMT = 300,000 / 158.2108 = $1,896.20The monthly mortgage payment is $1,896.20.
Over 30 years, total payments = 360 * $1,896.20 = $682,632, meaning total interest paid is $682,632 - $300,000 = $382,632.
Example 2: NPV of a Project with Uneven Cash Flows
Given: A project requires an initial investment of $50,000 and produces the following cash flows:
- Year 1: $12,000
- Year 2: $15,000
- Year 3: $18,000
- Year 4: $22,000
- Year 5: $25,000
The required rate of return (discount rate) is 10%.
Calculate: The NPV and whether the project should be accepted.
Solution:
Discount each cash flow to present value:
PV(CF_0) = -50,000 / (1.10)^0 = -50,000.00
PV(CF_1) = 12,000 / (1.10)^1 = 10,909.09
PV(CF_2) = 15,000 / (1.10)^2 = 12,396.69
PV(CF_3) = 18,000 / (1.10)^3 = 13,523.67
PV(CF_4) = 22,000 / (1.10)^4 = 15,026.30
PV(CF_5) = 25,000 / (1.10)^5 = 15,523.03Sum all present values:
NPV = -50,000.00 + 10,909.09 + 12,396.69 + 13,523.67 + 15,026.30 + 15,523.03
NPV = +$17,378.78Since NPV is positive ($17,378.78), the project creates value and should be accepted. It earns more than the 10% required rate of return.
To find the IRR, we would solve for the rate where NPV = 0. Numerically, the IRR for this cash flow stream is approximately 21.2% (21.18%), well above the 10% hurdle rate.
Common Pitfalls
- Mismatching rate and period frequency: if payments are monthly, the discount rate must be a monthly rate. Divide the annual nominal rate by 12, do not take the 12th root of
(1 + annual rate)unless converting from EAR. - Forgetting the sign convention for cash flows in IRR: outflows (investments) must be negative and inflows (returns) positive, or vice versa, but the convention must be consistent. Incorrect signs produce meaningless IRR results.
- Confusing nominal vs effective rates: a 12% nominal rate compounded monthly produces an EAR of 12.68%, not 12%. Always clarify the compounding basis.
- Off-by-one errors in annuity due vs ordinary annuity: an annuity due shifts all payments one period earlier. Forgetting the
(1 + r)adjustment factor will undervalue annuity-due streams. - Multiple IRR solutions with non-conventional cash flows: when cash flows change sign more than once (e.g., initial outflow, inflows, then a large terminal outflow), Descartes' rule allows up to as many positive real IRR solutions as there are sign changes. In such cases, use NPV profiling or the Modified IRR (MIRR) instead.
Running the Script
scripts/time_value_of_money.py implements every formula above as standalone functions (present_value, future_value, npv, irr, annuity_pv, annuity_fv, growing_annuity_pv, perpetuity_pv, fisher_rate, continuous_compounding) plus an AmortizationSchedule class.
- Run:
uv run scripts/time_value_of_money.py(PEP 723 inline metadata; stdlib-only, no third-party dependencies), or simplypython3 scripts/time_value_of_money.py. - Bare invocation (or
--verify) prints a demo of all functions and asserts the worked-example values above (Example 1 mortgage payment = $1,896.20; Example 2 NPV = $17,378.78 and IRR = 21.18%), exiting nonzero on any mismatch. --helplists the available functions and import usage.- For programmatic use, import rather than run:
from time_value_of_money import npv, irr, AmortizationSchedule.
Cross-References
- return-calculations (core plugin, Layer 0): CAGR is a special case of compound growth; portfolio MWR uses the same NPV=0 framework and lives there
- statistics-fundamentals (core plugin, Layer 0): Discount rate estimation often relies on regression (CAPM beta) and distributional assumptions
# /// script
# dependencies = []
# requires-python = ">=3.11"
# ///
"""
Time Value of Money - Layer 0 (Mathematical Foundations)
A comprehensive reference implementation for present value, future value,
NPV, IRR, annuities, perpetuities, amortization schedules, and compounding
conventions.
Usage:
uv run time_value_of_money.py # demo + verification (default)
python time_value_of_money.py --verify # same as bare invocation
python time_value_of_money.py --help # list available functions
Dependencies:
none (standard library only)
"""
import argparse
import math
import sys
# ---------------------------------------------------------------------------
# Core TVM Functions
# ---------------------------------------------------------------------------
def present_value(future_value: float, rate: float, periods: float) -> float:
"""Compute the present value of a future cash flow.
PV = FV / (1 + r)^n
Args:
future_value: The future cash flow amount.
rate: Discount rate per period (as a decimal, e.g., 0.05 for 5%).
periods: Number of compounding periods.
Returns:
The present value.
"""
return future_value / (1.0 + rate) ** periods
def future_value(present_val: float, rate: float, periods: float) -> float:
"""Compute the future value of a present amount.
FV = PV * (1 + r)^n
Args:
present_val: The current value / principal.
rate: Interest rate per period (as a decimal).
periods: Number of compounding periods.
Returns:
The future value.
"""
return present_val * (1.0 + rate) ** periods
def npv(rate: float, cash_flows: list[float]) -> float:
"""Compute the Net Present Value of a series of cash flows.
NPV = sum( CF_t / (1 + r)^t ) for t = 0, 1, 2, ...
Args:
rate: Discount rate per period (as a decimal).
cash_flows: List of cash flows starting at t=0. Negative values
represent outflows (investments), positive values represent
inflows.
Returns:
The net present value.
"""
total = 0.0
for t, cf in enumerate(cash_flows):
total += cf / (1.0 + rate) ** t
return total
def irr(cash_flows: list[float], guess: float = 0.1) -> float:
"""Compute the Internal Rate of Return using Newton's method.
Finds the rate r such that NPV(r) = 0.
Args:
cash_flows: List of cash flows starting at t=0. Typically the first
value is negative (initial investment) and subsequent values
are positive (returns).
guess: Initial guess for the rate. Defaults to 0.1 (10%).
Returns:
The IRR as a decimal.
Raises:
RuntimeError: If Newton's method fails to converge.
"""
rate = guess
max_iterations = 1000
tolerance = 1e-10
for _ in range(max_iterations):
npv_val = 0.0
npv_deriv = 0.0
for t, cf in enumerate(cash_flows):
discount = (1.0 + rate) ** t
npv_val += cf / discount
if t > 0:
npv_deriv -= t * cf / ((1.0 + rate) ** (t + 1))
if abs(npv_val) < tolerance:
return rate
if abs(npv_deriv) < 1e-15:
raise RuntimeError(
"Newton's method derivative near zero; try a different guess."
)
rate = rate - npv_val / npv_deriv
raise RuntimeError(
f"Newton's method did not converge after {max_iterations} iterations."
)
def annuity_pv(
payment: float,
rate: float,
periods: int,
due: bool = False,
) -> float:
"""Compute the present value of an annuity.
Ordinary annuity (due=False):
PV = PMT * [1 - (1 + r)^(-n)] / r
Annuity due (due=True):
PV = PMT * [1 - (1 + r)^(-n)] / r * (1 + r)
Args:
payment: The periodic payment amount.
rate: Interest rate per period (as a decimal).
periods: Total number of payment periods.
due: If True, payments occur at the beginning of each period
(annuity due). Defaults to False (ordinary annuity).
Returns:
The present value of the annuity.
"""
if rate == 0:
return payment * periods * (1.0 + rate if due else 1.0)
pv = payment * (1.0 - (1.0 + rate) ** (-periods)) / rate
if due:
pv *= (1.0 + rate)
return pv
def annuity_fv(
payment: float,
rate: float,
periods: int,
due: bool = False,
) -> float:
"""Compute the future value of an annuity.
Ordinary annuity (due=False):
FV = PMT * [(1 + r)^n - 1] / r
Annuity due (due=True):
FV = PMT * [(1 + r)^n - 1] / r * (1 + r)
Args:
payment: The periodic payment amount.
rate: Interest rate per period (as a decimal).
periods: Total number of payment periods.
due: If True, payments occur at the beginning of each period.
Defaults to False.
Returns:
The future value of the annuity.
"""
if rate == 0:
return payment * periods * (1.0 + rate if due else 1.0)
fv = payment * ((1.0 + rate) ** periods - 1.0) / rate
if due:
fv *= (1.0 + rate)
return fv
def growing_annuity_pv(
payment: float,
rate: float,
growth_rate: float,
periods: int,
) -> float:
"""Compute the present value of a growing annuity.
PV = PMT / (r - g) * [1 - ((1 + g) / (1 + r))^n]
Args:
payment: The first period's payment amount.
rate: Discount rate per period (as a decimal).
growth_rate: Growth rate of payments per period (as a decimal).
periods: Total number of payment periods.
Returns:
The present value of the growing annuity. When rate equals
growth_rate the standard formula is undefined (division by zero),
and the limit formula PV = PMT * n / (1 + r) is used instead.
"""
if abs(rate - growth_rate) < 1e-12:
# When r == g, PV = PMT * n / (1 + r)
return payment * periods / (1.0 + rate)
return (
payment
/ (rate - growth_rate)
* (1.0 - ((1.0 + growth_rate) / (1.0 + rate)) ** periods)
)
def perpetuity_pv(
payment: float,
rate: float,
growth_rate: float = 0.0,
) -> float:
"""Compute the present value of a perpetuity.
Constant perpetuity: PV = PMT / r
Growing perpetuity: PV = PMT / (r - g), requires r > g
Args:
payment: The periodic payment amount (first payment for growing).
rate: Discount rate per period (as a decimal).
growth_rate: Growth rate of payments (as a decimal). Defaults to 0.
Returns:
The present value of the perpetuity.
Raises:
ValueError: If rate <= growth_rate (PV would be infinite or negative).
"""
if rate <= growth_rate:
raise ValueError(
f"rate ({rate}) must be greater than growth_rate ({growth_rate}) "
"for a finite perpetuity value."
)
return payment / (rate - growth_rate)
def fisher_rate(nominal: float, inflation: float) -> float:
"""Compute the real rate of return using the Fisher equation.
r_real = (1 + r_nominal) / (1 + inflation) - 1
Args:
nominal: The nominal interest rate (as a decimal).
inflation: The inflation rate (as a decimal).
Returns:
The real rate of return as a decimal.
"""
return (1.0 + nominal) / (1.0 + inflation) - 1.0
def continuous_compounding(rate: float, time: float) -> float:
"""Compute the growth factor under continuous compounding.
Growth factor = e^(r * t)
Multiply by the principal to get the future value:
FV = PV * e^(r * t)
Args:
rate: The continuously compounded annual rate (as a decimal).
time: Time in years.
Returns:
The growth factor (not the future value).
"""
return math.exp(rate * time)
# ---------------------------------------------------------------------------
# Amortization Schedule
# ---------------------------------------------------------------------------
class AmortizationSchedule:
"""Generate a full amortization schedule for a fixed-rate loan.
Each period's payment is split into interest and principal components.
Early payments are interest-heavy; later payments are principal-heavy.
Args:
principal: The initial loan amount.
annual_rate: The annual interest rate (as a decimal, e.g., 0.06).
periods: Total number of payment periods.
periods_per_year: Number of payment periods per year (default 12
for monthly payments).
"""
def __init__(
self,
principal: float,
annual_rate: float,
periods: int,
periods_per_year: int = 12,
) -> None:
self.principal = principal
self.annual_rate = annual_rate
self.periods = periods
self.periods_per_year = periods_per_year
self.periodic_rate = annual_rate / periods_per_year
def _compute_payment(self) -> float:
"""Compute the fixed periodic payment.
PMT = PV * r / [1 - (1 + r)^(-n)]
"""
r = self.periodic_rate
n = self.periods
if r == 0:
return self.principal / n
return self.principal * r / (1.0 - (1.0 + r) ** (-n))
def schedule(self) -> list[dict]:
"""Generate the full amortization schedule.
Returns:
A list of dictionaries, one per period, each containing:
- period: int (1-indexed)
- payment: float
- principal_payment: float
- interest_payment: float
- remaining_balance: float
"""
payment = self._compute_payment()
balance = self.principal
rows: list[dict] = []
for period_num in range(1, self.periods + 1):
interest = balance * self.periodic_rate
principal_pmt = payment - interest
# Handle final period rounding
if period_num == self.periods:
principal_pmt = balance
payment = principal_pmt + interest
balance -= principal_pmt
rows.append({
"period": period_num,
"payment": round(payment, 2),
"principal_payment": round(principal_pmt, 2),
"interest_payment": round(interest, 2),
"remaining_balance": round(max(balance, 0.0), 2),
})
return rows
def total_interest(self) -> float:
"""Compute the total interest paid over the life of the loan.
Returns:
The sum of all interest payments.
"""
return sum(row["interest_payment"] for row in self.schedule())
def total_payments(self) -> float:
"""Compute the total amount paid over the life of the loan.
Returns:
The sum of all payments (principal + interest).
"""
return sum(row["payment"] for row in self.schedule())
# ---------------------------------------------------------------------------
# Demonstration and verification
# ---------------------------------------------------------------------------
_FUNCTIONS_HELP = """\
Available functions:
present_value(future_value, rate, periods)
future_value(present_val, rate, periods)
npv(rate, cash_flows)
irr(cash_flows, guess=0.1) # Newton's method
annuity_pv(payment, rate, periods, due=False)
annuity_fv(payment, rate, periods, due=False)
growing_annuity_pv(payment, rate, growth_rate, periods)
perpetuity_pv(payment, rate, growth_rate=0.0)
fisher_rate(nominal, inflation)
continuous_compounding(rate, time)
AmortizationSchedule(principal, annual_rate, periods, periods_per_year=12)
.schedule() / .total_interest() / .total_payments()
Import usage (preferred for programmatic work):
from time_value_of_money import npv, irr, AmortizationSchedule
npv(0.10, [-50_000, 12_000, 15_000, 18_000, 22_000, 25_000])
Running bare (or with --verify) prints a demo of every function and
asserts the worked-example values from SKILL.md, exiting nonzero on
any mismatch.
"""
def _verify() -> None:
"""Assert that key outputs match the SKILL.md worked examples."""
# SKILL.md Example 1: $300,000 mortgage, 6.5% annual, 360 monthly
# payments -> PMT = $1,896.20
mortgage = AmortizationSchedule(
principal=300_000, annual_rate=0.065, periods=360, periods_per_year=12
)
pmt = mortgage._compute_payment()
assert abs(pmt - 1_896.20) < 0.005, f"Example 1 mortgage payment mismatch: {pmt}"
# SKILL.md Example 2: NPV of [-50k, 12k, 15k, 18k, 22k, 25k] at 10%
# = $17,378.78; IRR = 21.18%
cfs = [-50_000, 12_000, 15_000, 18_000, 22_000, 25_000]
npv_val = npv(rate=0.10, cash_flows=cfs)
assert abs(npv_val - 17_378.78) < 0.005, f"Example 2 NPV mismatch: {npv_val}"
irr_val = irr(cash_flows=cfs)
assert abs(irr_val - 0.2118) < 5e-5, f"Example 2 IRR mismatch: {irr_val}"
print("\nVerification PASSED: outputs match SKILL.md worked examples")
print(f" Example 1 mortgage payment: ${pmt:,.2f}")
print(f" Example 2 NPV at 10%: ${npv_val:,.2f}")
print(f" Example 2 IRR: {irr_val:.4%}")
def _demo() -> None:
print("=" * 60)
print("Time Value of Money - Reference Implementation Demo")
print("=" * 60)
# 1. Present Value
pv = present_value(future_value=10_000, rate=0.05, periods=10)
print(f"\n1. PV of $10,000 in 10 years at 5%: ${pv:,.2f}")
# 2. Future Value
fv = future_value(present_val=10_000, rate=0.05, periods=10)
print(f"2. FV of $10,000 in 10 years at 5%: ${fv:,.2f}")
# 3. NPV
cfs = [-100_000, 30_000, 35_000, 40_000, 45_000]
npv_val = npv(rate=0.10, cash_flows=cfs)
print(f"\n3. NPV at 10%: ${npv_val:,.2f}")
print(f" Cash flows: {cfs}")
# 4. IRR
irr_val = irr(cash_flows=cfs)
print(f"4. IRR: {irr_val:.4%}")
print(f" Verification NPV at IRR: ${npv(irr_val, cfs):,.6f}")
# 5. Annuity PV
ordinary = annuity_pv(payment=1_000, rate=0.05, periods=20)
due = annuity_pv(payment=1_000, rate=0.05, periods=20, due=True)
print(f"\n5. PV of $1,000/yr annuity, 20 years, 5%:")
print(f" Ordinary: ${ordinary:,.2f}")
print(f" Due: ${due:,.2f}")
# 6. Annuity FV
fv_ord = annuity_fv(payment=500, rate=0.06, periods=30)
print(f"\n6. FV of $500/yr ordinary annuity, 30 years, 6%: ${fv_ord:,.2f}")
# 7. Growing Annuity
ga = growing_annuity_pv(payment=50_000, rate=0.08, growth_rate=0.03, periods=25)
print(f"\n7. PV of growing annuity ($50k, 3% growth, 8% discount, 25 yr): ${ga:,.2f}")
# 8. Perpetuity
perp = perpetuity_pv(payment=10_000, rate=0.05)
grow_perp = perpetuity_pv(payment=10_000, rate=0.05, growth_rate=0.02)
print(f"\n8. Perpetuity ($10k/yr at 5%): ${perp:,.2f}")
print(f" Growing perpetuity (2% growth): ${grow_perp:,.2f}")
# 9. Fisher Equation
real = fisher_rate(nominal=0.07, inflation=0.03)
print(f"\n9. Real rate (7% nominal, 3% inflation): {real:.4%}")
# 10. Continuous Compounding
factor = continuous_compounding(rate=0.05, time=10)
print(f"10. Continuous compounding factor (5%, 10yr): {factor:.6f}")
print(f" FV of $10,000: ${10_000 * factor:,.2f}")
# 11. Amortization Schedule
print(f"\n11. Amortization Schedule: $250,000 mortgage, 6% annual, 360 months")
amort = AmortizationSchedule(
principal=250_000, annual_rate=0.06, periods=360, periods_per_year=12
)
sched = amort.schedule()
print(f" Monthly Payment: ${sched[0]['payment']:,.2f}")
print(f" Total Interest: ${amort.total_interest():,.2f}")
print(f" Total Payments: ${amort.total_payments():,.2f}")
print(f"\n First 5 periods:")
print(f" {'Period':>6} {'Payment':>10} {'Interest':>10} {'Principal':>10} {'Balance':>12}")
for row in sched[:5]:
print(
f" {row['period']:>6} "
f"${row['payment']:>9,.2f} "
f"${row['interest_payment']:>9,.2f} "
f"${row['principal_payment']:>9,.2f} "
f"${row['remaining_balance']:>11,.2f}"
)
print(f" ...")
print(f" Last 3 periods:")
for row in sched[-3:]:
print(
f" {row['period']:>6} "
f"${row['payment']:>9,.2f} "
f"${row['interest_payment']:>9,.2f} "
f"${row['principal_payment']:>9,.2f} "
f"${row['remaining_balance']:>11,.2f}"
)
print("\n" + "=" * 60)
print("All calculations completed successfully.")
print("=" * 60)
def main() -> int:
parser = argparse.ArgumentParser(
description="Time value of money reference implementation.",
epilog=_FUNCTIONS_HELP,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"--verify",
action="store_true",
help="run the demo and assert outputs match the SKILL.md worked "
"examples (this is also the default when run with no arguments)",
)
parser.parse_args()
# Bare invocation and --verify behave identically: demo + verification.
_demo()
try:
_verify()
except AssertionError as exc:
print(f"\nVerification FAILED: {exc}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
Related skills
How it compares
Pick time-value-of-money over generic spreadsheet skills when you need verified TVM formulas, Newton-Raphson IRR solving, and a runnable Python amortization implementation.
FAQ
What does time-value-of-money calculate?
time-value-of-money computes present value, future value, NPV, IRR, annuity values, perpetuities, and full loan amortization schedules. The bundled Python script includes 11 functions plus an AmortizationSchedule class with verification against worked mortgage and project example
How do you run the time-value-of-money Python script?
Run uv run scripts/time_value_of_money.py from the skill directory to demo all functions and assert outputs match SKILL.md examples. The script requires Python 3.11+ and has zero third-party dependencies.
When should developers use time-value-of-money vs return-calculations?
time-value-of-money handles project NPV, IRR, loan amortization, and annuity valuation. return-calculations covers portfolio metrics like TWR, MWR, and CAGR when measuring investor return over contribution and withdrawal streams.