Finance.TVM (finance v1.4.1)

Copy Markdown View Source

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 = 0

type 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

Types

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

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

error()

@type error() :: Finance.error()

option()

@type option() :: Finance.option()

rate()

@type rate() :: Finance.rate()

schedule_row()

@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

amortization_schedule(rate, nper, pv, opts \\ [])

@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}

amortization_schedule!(rate, nper, pv, opts \\ [])

@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.

fv(rate, nper, pmt, pv \\ 0.0, type \\ 0)

@spec fv(number(), number(), number(), number(), 0 | 1) ::
  {:ok, float()} | {:error, 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

fv!(rate, nper, pmt, pv \\ 0.0, type \\ 0)

@spec fv!(number(), number(), number(), number(), 0 | 1) :: float()

Same as fv/5, but returns the value directly and raises ArgumentError on error.

ipmt(rate, per, nper, pv, fv \\ 0.0, type \\ 0)

@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

ipmt!(rate, per, nper, pv, fv \\ 0.0, type \\ 0)

@spec ipmt!(number(), number(), number(), number(), number(), 0 | 1) :: float()

Same as ipmt/6, but returns the value directly and raises ArgumentError on error.

nper(rate, pmt, pv, fv \\ 0.0, type \\ 0)

@spec nper(number(), number(), number(), number(), 0 | 1) ::
  {:ok, float()} | {:error, 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

nper!(rate, pmt, pv, fv \\ 0.0, type \\ 0)

@spec nper!(number(), number(), number(), number(), 0 | 1) :: float()

Same as nper/5, but returns the value directly and raises ArgumentError on error.

pmt(rate, nper, pv, fv \\ 0.0, type \\ 0)

@spec pmt(number(), number(), number(), number(), 0 | 1) ::
  {:ok, float()} | {:error, 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

pmt!(rate, nper, pv, fv \\ 0.0, type \\ 0)

@spec pmt!(number(), number(), number(), number(), 0 | 1) :: float()

Same as pmt/5, but returns the value directly and raises ArgumentError on error.

ppmt(rate, per, nper, pv, fv \\ 0.0, type \\ 0)

@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

ppmt!(rate, per, nper, pv, fv \\ 0.0, type \\ 0)

@spec ppmt!(number(), number(), number(), number(), number(), 0 | 1) :: float()

Same as ppmt/6, but returns the value directly and raises ArgumentError on error.

pv(rate, nper, pmt, fv \\ 0.0, type \\ 0)

@spec pv(number(), number(), number(), number(), 0 | 1) ::
  {:ok, float()} | {:error, 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

pv!(rate, nper, pmt, fv \\ 0.0, type \\ 0)

@spec pv!(number(), number(), number(), number(), 0 | 1) :: float()

Same as pv/5, but returns the value directly and raises ArgumentError on error.

rate(nper, pmt, pv, fv \\ 0.0, type \\ 0, opts \\ [])

@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}

rate!(nper, pmt, pv, fv \\ 0.0, type \\ 0, opts \\ [])

@spec rate!(number(), number(), number(), number(), 0 | 1, [option()]) :: rate()

Same as rate/6, but returns the rate directly and raises ArgumentError on error.