Module: Xirr::TVM
- Defined in:
- lib/xirr/tvm.rb
Overview
Time-value-of-money scalars: TVM.fv, TVM.pv, TVM.pmt, TVM.ipmt, TVM.ppmt, TVM.nper, and TVM.rate, plus an TVM.amortization_schedule. 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 (an ordinary annuity) or 1
for the beginning (an annuity due). The sign convention follows spreadsheets:
money you receive is positive, money you pay out is negative.
Class Method Summary collapse
-
.amortization_schedule(rate, nper, pv, precision: 2) ⇒ Array<Hash>
Full amortization schedule for a loan of
pvrepaid with a level payment overnperperiods atrate. -
.fv(rate, nper, pmt, pv = 0.0, type = 0) ⇒ Float
Future value: what an investment grows to after
nperperiods, starting from present valuepvwith a fixedpmteach period, compounding atrate. -
.ipmt(rate, per, nper, pv, fv = 0.0, type = 0) ⇒ Float
Interest portion of the payment in period
per(counting from 1). -
.nper(rate, pmt, pv, fv = 0.0, type = 0) ⇒ Float
Number of periods it takes for payments of
pmtto pay offpv(reachingfv) atrate. -
.pmt(rate, nper, pv, fv = 0.0, type = 0) ⇒ Float
Level payment per period that pays off
pv(and reachesfv) overnperperiods atrate. -
.ppmt(rate, per, nper, pv, fv = 0.0, type = 0) ⇒ Float
Principal portion of the payment in period
per(counting from 1). -
.pv(rate, nper, pmt, fv = 0.0, type = 0) ⇒ Float
Present value: what a stream of
pmtper period fornperperiods plus a lump sumfvat the end is worth today, discounted atrate. -
.rate(nper, pmt, pv, fv = 0.0, type = 0, guess: 0.1) ⇒ Float
Interest rate per period of an annuity of
nperpayments ofpmt, present valuepv, and future valuefv.
Class Method Details
.amortization_schedule(rate, nper, pv, precision: 2) ⇒ Array<Hash>
Full amortization schedule for a loan of pv repaid with a level payment
over nper periods at rate. Returns an array of row hashes with
:period, :payment, :interest, :principal, and :balance; the
balance runs from pv down to exactly 0. Monetary columns are rounded to
precision (default 2, i.e. cents).
105 106 107 108 109 110 |
# File 'lib/xirr/tvm.rb', line 105 def amortization_schedule(rate, nper, pv, precision: 2) n = nper.to_i raise ArgumentError, 'nper must be a positive whole number' unless nper == n && n >= 1 build_schedule(rate.to_f, n, pv.to_f, precision) end |
.fv(rate, nper, pmt, pv = 0.0, type = 0) ⇒ Float
Future value: what an investment grows to after nper periods, starting
from present value pv with a fixed pmt each period, compounding at
rate.
20 21 22 23 24 25 26 27 |
# File 'lib/xirr/tvm.rb', line 20 def fv(rate, nper, pmt, pv = 0.0, type = 0) validate_type!(type) if rate.zero? -(pv + pmt * nper) * 1.0 else -(pv * (1 + rate)**nper + pmt * annuity(rate, nper, type)) * 1.0 end end |
.ipmt(rate, per, nper, pv, fv = 0.0, type = 0) ⇒ Float
55 56 57 58 |
# File 'lib/xirr/tvm.rb', line 55 def ipmt(rate, per, nper, pv, fv = 0.0, type = 0) validate_type!(type) split_payment(rate, per, nper, pv, fv, type).first end |
.nper(rate, pmt, pv, fv = 0.0, type = 0) ⇒ Float
Number of periods it takes for payments of pmt to pay off pv (reaching
fv) at rate.
71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/xirr/tvm.rb', line 71 def nper(rate, pmt, pv, fv = 0.0, type = 0) validate_type!(type) raise ArgumentError, 'undefined' if rate.zero? && pmt.zero? return -(pv + fv) / pmt * 1.0 if rate.zero? raise ArgumentError, 'undefined' if 1 + rate <= 0 k = pmt * (1 + rate * type) / rate denom = pv + k raise ArgumentError, 'undefined' if denom.zero? || (k - fv) / denom <= 0 Math.log((k - fv) / denom) / Math.log(1 + rate) end |
.pmt(rate, nper, pv, fv = 0.0, type = 0) ⇒ Float
Level payment per period that pays off pv (and reaches fv) over nper
periods at rate.
44 45 46 47 48 49 50 |
# File 'lib/xirr/tvm.rb', line 44 def pmt(rate, nper, pv, fv = 0.0, type = 0) validate_type!(type) raise ArgumentError, 'nper must be non-zero' if nper.zero? return -(pv + fv) / nper * 1.0 if rate.zero? -(pv * (1 + rate)**nper + fv) / annuity(rate, nper, type) * 1.0 end |
.ppmt(rate, per, nper, pv, fv = 0.0, type = 0) ⇒ Float
Principal portion of the payment in period per (counting from 1). The
companion of ipmt.
63 64 65 66 |
# File 'lib/xirr/tvm.rb', line 63 def ppmt(rate, per, nper, pv, fv = 0.0, type = 0) validate_type!(type) split_payment(rate, per, nper, pv, fv, type).last end |
.pv(rate, nper, pmt, fv = 0.0, type = 0) ⇒ Float
Present value: what a stream of pmt per period for nper periods plus a
lump sum fv at the end is worth today, discounted at rate.
32 33 34 35 36 37 38 39 |
# File 'lib/xirr/tvm.rb', line 32 def pv(rate, nper, pmt, fv = 0.0, type = 0) validate_type!(type) if rate.zero? -(fv + pmt * nper) * 1.0 else -(fv + pmt * annuity(rate, nper, type)) / (1 + rate)**nper * 1.0 end end |
.rate(nper, pmt, pv, fv = 0.0, type = 0, guess: 0.1) ⇒ Float
Interest rate per period of an annuity of nper payments of pmt, present
value pv, and future value fv. There is no closed form, so this reuses
the RtSafe solver.
88 89 90 91 92 93 94 95 96 97 |
# File 'lib/xirr/tvm.rb', line 88 def rate(nper, pmt, pv, fv = 0.0, type = 0, guess: 0.1) validate_type!(type) n = nper.to_i raise ArgumentError, 'nper must be a positive whole number' unless nper == n && n.positive? result = RtSafe.find(tvm_flows(n, pmt, pv, fv, type), guess: guess) raise ArgumentError, 'rate did not converge' if result.nil? result end |