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 returns the value directly and raises ArgumentError on 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.
Same as ipmt/6, but returns the value directly and raises ArgumentError on error.
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 returns the value directly and raises ArgumentError on error.
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 returns the value directly and raises ArgumentError on 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.
Same as ppmt/6, but returns the value directly and raises ArgumentError on error.
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 returns the value directly and raises ArgumentError on 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.
Same as rate/6, but returns the rate directly and raises ArgumentError on error.
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 returns the value directly and raises ArgumentError on error.
@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 returns the value directly and raises ArgumentError on error.
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 returns the value directly and raises ArgumentError on error.
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 returns the value directly and raises ArgumentError on error.
@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 returns the value directly and raises ArgumentError on error.
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 returns the value directly and raises ArgumentError on error.
@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 returns the rate directly and raises ArgumentError on error.