Module: Finrb::TVM
- Defined in:
- lib/finrb/tvm.rb,
sig/finrb.rbs
Overview
Time-value-of-money calculations for periodic rates and cashflows.
Class Method Summary collapse
- .discount_rate(n:, pv:, fv:, pmt:, type: 0, guess: nil, lower: UNSET_BOUND, upper: UNSET_BOUND) ⇒ decimal
- .fv(r:, n:, pv: 0, pmt: 0, type: 0) ⇒ decimal
- .fv_annuity(r:, n:, pmt:, type: 0) ⇒ decimal
- .fv_simple(r:, n:, pv:) ⇒ decimal
- .fv_uneven(r:, cf:) ⇒ decimal
- .n_period(r:, pv:, fv:, pmt:, type: 0) ⇒ decimal
- .npv(r:, cf:) ⇒ decimal
- .pmt(r:, n:, pv:, fv:, type: 0) ⇒ decimal
- .pv(r:, n:, fv: 0, pmt: 0, type: 0) ⇒ decimal
- .pv_annuity(r:, n:, pmt:, type: 0) ⇒ decimal
- .pv_perpetuity(r:, pmt:, g: 0, type: 0) ⇒ decimal
- .pv_simple(r:, n:, fv:) ⇒ decimal
- .pv_uneven(r:, cf:) ⇒ decimal
- .r_perpetuity(pmt:, pv:) ⇒ decimal
Class Method Details
.discount_rate(n:, pv:, fv:, pmt:, type: 0, guess: nil, lower: UNSET_BOUND, upper: UNSET_BOUND) ⇒ decimal
20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/finrb/tvm.rb', line 20 def discount_rate(n:, pv:, fv:, pmt:, type: 0, guess: nil, lower: UNSET_BOUND, upper: UNSET_BOUND) n = period_count(n) pv, fv, pmt = decimal_inputs(pv:, fv:, pmt:).values type = payment_type(type) function = ->(rate) { fv_simple(r: rate, n:, pv:) + fv_annuity(r: rate, n:, pmt:, type:) - fv } bounds = rate_bounds(function, guess:, lower:, upper:) return bounds.first if bounds.first == bounds.last Numerical::Brent.new(tolerance: Finrb.config.eps).solve(function, lower: bounds.first, upper: bounds.last) end |
.fv(r:, n:, pv: 0, pmt: 0, type: 0) ⇒ decimal
32 33 34 35 36 37 38 |
# File 'lib/finrb/tvm.rb', line 32 def fv(r:, n:, pv: 0, pmt: 0, type: 0) rate = periodic_rate(r) periods = period_count(n) payment_type(type) fv_simple(r: rate, n: periods, pv:) + fv_annuity(r: rate, n: periods, pmt:, type:) end |
.fv_annuity(r:, n:, pmt:, type: 0) ⇒ decimal
40 41 42 43 44 45 46 47 48 |
# File 'lib/finrb/tvm.rb', line 40 def fv_annuity(r:, n:, pmt:, type: 0) rate = periodic_rate(r) periods = period_count(n) payment = Validation.decimal(pmt, name: 'payment') payment_timing = payment_type(type) return -payment * periods if rate.zero? (payment / rate * (((rate + 1)**periods) - 1)) * ((rate + 1)**payment_timing) * -1 end |
.fv_simple(r:, n:, pv:) ⇒ decimal
50 51 52 53 54 55 |
# File 'lib/finrb/tvm.rb', line 50 def fv_simple(r:, n:, pv:) rate = periodic_rate(r) periods = period_count(n) present_value = Validation.decimal(pv, name: 'present value') (present_value * ((rate + 1)**periods)) * -1 end |
.fv_uneven(r:, cf:) ⇒ decimal
57 58 59 60 61 62 63 64 |
# File 'lib/finrb/tvm.rb', line 57 def fv_uneven(r:, cf:) rate = periodic_rate(r) cashflows = cashflow_values(cf) cashflows.each_with_index.sum do |cashflow, index| fv_simple(r: rate, n: cashflows.size - index - 1, pv: cashflow) end end |
.n_period(r:, pv:, fv:, pmt:, type: 0) ⇒ decimal
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/finrb/tvm.rb', line 66 def n_period(r:, pv:, fv:, pmt:, type: 0) rate = periodic_rate(r) values = decimal_inputs(pv:, fv:, pmt:) payment_timing = payment_type(type) return zero_rate_periods(**values) if rate.zero? numerator = ((values[:fv] * rate) - (values[:pmt] * ((rate + 1)**payment_timing))) * -1 denominator = (values[:pv] * rate) + (values[:pmt] * ((rate + 1)**payment_timing)) periods = (numerator / denominator).log / (rate + 1).log raise(DomainError, 'Inputs do not produce a finite non-negative period count.') unless periods.finite? && !periods.negative? periods rescue Flt::Num::Exception, Math::DomainError, ZeroDivisionError => e raise(DomainError, "Inputs do not produce a real period count: #{e.}", e.backtrace) end |
.npv(r:, cf:) ⇒ decimal
83 84 85 86 87 88 89 |
# File 'lib/finrb/tvm.rb', line 83 def npv(r:, cf:) rate = periodic_rate(r) cashflows = cashflow_values(cf) return cashflows.first if cashflows.one? (pv_uneven(r: rate, cf: cashflows.drop(1)) * -1) + cashflows.first end |
.pmt(r:, n:, pv:, fv:, type: 0) ⇒ decimal
91 92 93 94 95 96 97 98 99 |
# File 'lib/finrb/tvm.rb', line 91 def pmt(r:, n:, pv:, fv:, type: 0) rate = periodic_rate(r) periods = positive_period_count(n) values = decimal_inputs(pv:, fv:) payment_timing = payment_type(type) return -(values[:pv] + values[:fv]) / periods if rate.zero? (values[:pv] + (values[:fv] / ((rate + 1)**periods))) * rate / (1 - (Flt::DecNum(1) / ((rate + 1)**periods))) * -1 * ((rate + 1)**(payment_timing * -1)) end |
.pv(r:, n:, fv: 0, pmt: 0, type: 0) ⇒ decimal
101 102 103 104 105 106 107 |
# File 'lib/finrb/tvm.rb', line 101 def pv(r:, n:, fv: 0, pmt: 0, type: 0) rate = periodic_rate(r) periods = period_count(n) payment_type(type) pv_simple(r: rate, n: periods, fv:) + pv_annuity(r: rate, n: periods, pmt:, type:) end |
.pv_annuity(r:, n:, pmt:, type: 0) ⇒ decimal
109 110 111 112 113 114 115 116 117 |
# File 'lib/finrb/tvm.rb', line 109 def pv_annuity(r:, n:, pmt:, type: 0) rate = periodic_rate(r) periods = period_count(n) payment = Validation.decimal(pmt, name: 'payment') payment_timing = payment_type(type) return -payment * periods if rate.zero? (payment / rate * (1 - (Flt::DecNum(1) / ((rate + 1)**periods)))) * ((rate + 1)**payment_timing) * -1 end |
.pv_perpetuity(r:, pmt:, g: 0, type: 0) ⇒ decimal
119 120 121 122 123 124 125 126 127 |
# File 'lib/finrb/tvm.rb', line 119 def pv_perpetuity(r:, pmt:, g: 0, type: 0) rate = periodic_rate(r) payment = Validation.decimal(pmt, name: 'payment') growth = periodic_rate(g, name: :g) payment_timing = payment_type(type) raise(DomainError, 'Growth rate must be smaller than the discount rate.') if growth >= rate (payment / (rate - growth)) * ((rate + 1)**payment_timing) * -1 end |
.pv_simple(r:, n:, fv:) ⇒ decimal
129 130 131 132 133 134 |
# File 'lib/finrb/tvm.rb', line 129 def pv_simple(r:, n:, fv:) rate = periodic_rate(r) periods = period_count(n) future_value = Validation.decimal(fv, name: 'future value') (future_value / ((rate + 1)**periods)) * -1 end |
.pv_uneven(r:, cf:) ⇒ decimal
136 137 138 139 140 141 |
# File 'lib/finrb/tvm.rb', line 136 def pv_uneven(r:, cf:) rate = periodic_rate(r) cashflow_values(cf).each_with_index.sum do |cashflow, index| pv_simple(r: rate, n: index + 1, fv: cashflow) end end |
.r_perpetuity(pmt:, pv:) ⇒ decimal
143 144 145 146 147 148 149 |
# File 'lib/finrb/tvm.rb', line 143 def r_perpetuity(pmt:, pv:) payment = Validation.decimal(pmt, name: 'payment') present_value = Validation.decimal(pv, name: 'present value') raise(DomainError, 'Present value must be non-zero.') if present_value.zero? payment * -1 / present_value end |