Time-value-of-money scalars: fv/5, pv/5, pmt/5, nper/5, and rate/6.
Each solves the standard annuity equation for one unknown:
pv·(1+r)^n + pmt·(1 + r·type)·((1+r)^n − 1)/r + fv = 0type is 0 for payments at the end of each period (ordinary annuity) or 1
for the beginning (annuity due). The sign convention follows spreadsheets:
money you receive is positive, money you pay out is negative.
Summary
Functions
Builds the full amortization schedule for a loan of pv repaid with a level
payment over nper periods at rate.
Same as amortization_schedule/4, but returns the rows directly and raises ArgumentError on error.
Works out the future value of an investment: what it grows to after nper
periods, starting from a present value of pv, with a fixed payment of pmt
each period, all compounding at rate.
Same as fv/5, but hands back the value on its own and raises ArgumentError if the calculation fails.
Works out the interest portion of the payment in period per — how much of
that period's fixed payment goes to interest rather than to principal.
Same as ipmt/6, but hands back the value on its own and raises ArgumentError if the calculation fails.
Works out how many periods it takes for payments of pmt to pay off a present
value pv (reaching future value fv) at rate.
Same as nper/5, but hands back the value on its own and raises ArgumentError if the calculation fails.
Works out the level payment per period needed to pay off a present value pv
(and arrive at a future value fv) over nper periods at rate.
Same as pmt/5, but hands back the value on its own and raises ArgumentError if the calculation fails.
Works out the principal portion of the payment in period per — how much of
that period's fixed payment actually pays down the balance.
Same as ppmt/6, but hands back the value on its own and raises ArgumentError if the calculation fails.
Works out the present value of an investment: what a future stream is worth
today. The stream is pmt paid each period for nper periods plus a lump sum
fv at the end, all discounted back at rate.
Same as pv/5, but hands back the value on its own and raises ArgumentError if the calculation fails.
Works out the interest rate per period of an annuity described by nper
payments of pmt, a present value pv, and a future value fv. nper has to
be a whole number of periods.
Same as rate/6, but hands back the rate on its own and raises ArgumentError if the calculation fails.
Types
@type error() :: Finance.error()
@type option() :: Finance.option()
@type rate() :: Finance.rate()
@type schedule_row() :: %{ period: pos_integer(), payment: float() | Decimal.t(), interest: float() | Decimal.t(), principal: float() | Decimal.t(), balance: float() | Decimal.t() }
A row of an amortization schedule; monetary fields follow the pmt/5 sign
convention and are Decimal when the schedule is built from Decimal inputs.
Functions
@spec amortization_schedule( number() | Decimal.t(), pos_integer(), number() | Decimal.t(), [{:precision, non_neg_integer()}] ) :: {:ok, [schedule_row()]} | {:error, error()}
Builds the full amortization schedule for a loan of pv repaid with a level
payment over nper periods at rate.
Returns {:ok, rows} where each row is a map of period, payment,
interest, principal, and remaining balance. Money follows the pmt/5
sign convention (a loan is a positive pv, its payments are negative), and the
balance runs from pv down to exactly 0.0 — the final row absorbs any
rounding residual so the loan pays off cleanly.
Each monetary column is rounded to :precision places, which defaults to 2
(cents) rather than the 6 used elsewhere, since a schedule is a money table.
The schedule is computed in integer minor units (10^:precision), so every
row is exact to the requested precision and the balance ends at exactly zero.
If rate or pv is a Decimal, the monetary fields come back as Decimal;
otherwise they come back as floats.
iex> {:ok, [first | _]} = Finance.TVM.amortization_schedule(0.10 / 12, 12, 1000)
iex> {first.payment, first.interest, first.principal, first.balance}
{-87.92, -8.33, -79.59, 920.41}
@spec amortization_schedule!( number() | Decimal.t(), pos_integer(), number() | Decimal.t(), [{:precision, non_neg_integer()}] ) :: [schedule_row()]
Same as amortization_schedule/4, but returns the rows directly and raises ArgumentError on error.
Works out the future value of an investment: what it grows to after nper
periods, starting from a present value of pv, with a fixed payment of pmt
each period, all compounding at rate.
Use this to answer "if I put this much in now and add this much every period,
what will I have at the end?". As with the rest of the time-value-of-money
functions, type chooses when each payment happens — 0 for the end of the
period (an ordinary annuity) or 1 for the beginning (an annuity due) — and
the sign convention follows spreadsheets, so money you receive is positive and
money you pay out is negative.
iex> {:ok, value} = Finance.TVM.fv(0.05, 10, -100, -1000)
iex> Float.round(value, 2)
2886.68
Same as fv/5, but hands back the value on its own and raises ArgumentError if the calculation fails.
@spec ipmt(number(), number(), number(), number(), number(), 0 | 1) :: {:ok, float()} | {:error, error()}
Works out the interest portion of the payment in period per — how much of
that period's fixed payment goes to interest rather than to principal.
per counts from 1 and must fall within 1..nper. It pairs with ppmt/6,
which gives the principal portion; for every period the two add up to pmt/5.
iex> {:ok, interest} = Finance.TVM.ipmt(0.10 / 12, 1, 12, 1000)
iex> Float.round(interest, 6)
-8.333333
Same as ipmt/6, but hands back the value on its own and raises ArgumentError if the calculation fails.
Works out how many periods it takes for payments of pmt to pay off a present
value pv (reaching future value fv) at rate.
This is the "how long until it's paid off?" question. When the numbers don't
describe a situation that ever resolves, it returns {:error, :undefined}.
iex> {:ok, periods} = Finance.TVM.nper(0.05, -100, 1000)
iex> Float.round(periods, 2)
14.21
Same as nper/5, but hands back the value on its own and raises ArgumentError if the calculation fails.
Works out the level payment per period needed to pay off a present value pv
(and arrive at a future value fv) over nper periods at rate.
This is the loan-payment question: given an amount borrowed today, what fixed
installment clears it over the term? The same type and sign conventions
apply, so a loan you take out is a positive pv and the payment comes back
negative.
iex> {:ok, payment} = Finance.TVM.pmt(0.10, 10, 1000)
iex> Float.round(payment, 2)
-162.75
Same as pmt/5, but hands back the value on its own and raises ArgumentError if the calculation fails.
@spec ppmt(number(), number(), number(), number(), number(), 0 | 1) :: {:ok, float()} | {:error, error()}
Works out the principal portion of the payment in period per — how much of
that period's fixed payment actually pays down the balance.
per counts from 1 and must fall within 1..nper. It is the companion of
ipmt/6; ipmt plus ppmt equals pmt/5 for every period.
iex> {:ok, principal} = Finance.TVM.ppmt(0.10 / 12, 1, 12, 1000)
iex> Float.round(principal, 6)
-79.582554
Same as ppmt/6, but hands back the value on its own and raises ArgumentError if the calculation fails.
Works out the present value of an investment: what a future stream is worth
today. The stream is pmt paid each period for nper periods plus a lump sum
fv at the end, all discounted back at rate.
It answers the mirror image of fv/5's question — "how much would I need to
put in now to fund these future payments?". The same type and sign
conventions apply.
iex> {:ok, value} = Finance.TVM.pv(0.05, 10, -100, -1000)
iex> Float.round(value, 2)
1386.09
Same as pv/5, but hands back the value on its own and raises ArgumentError if the calculation fails.
@spec rate(number(), number(), number(), number(), 0 | 1, [option()]) :: {:ok, rate()} | {:error, error()}
Works out the interest rate per period of an annuity described by nper
payments of pmt, a present value pv, and a future value fv. nper has to
be a whole number of periods.
There is no closed form for the rate, so this reuses the same numerical solver
as Finance.CashFlow.irr/1 and takes the same options. If it can't pin down a
rate, it returns {:error, :did_not_converge}.
iex> Finance.TVM.rate(10, -100, 1000)
{:ok, 0.0}
Same as rate/6, but hands back the rate on its own and raises ArgumentError if the calculation fails.