Class: Finance::Amortization

Inherits:
Object
  • Object
show all
Defined in:
lib/finance/amortization.rb

Overview

Note:

There are two ways to create an amortization. The first example uses the amortize method for the Numeric class. The second calls Amortization.new directly.

the Amortization class provides an interface for working with loan amortizations.

Examples:

Borrow $250,000 under a 30 year, fixed-rate loan with a 4.25% APR

rate = Rate.new(0.0425, :apr, :duration => (30 * 12))
amortization = 250000.amortize(rate)

Borrow $250,000 under a 30 year, adjustable rate loan, with an APR starting at 4.25%, and increasing by 1% every five years

values = %w{ 0.0425 0.0525 0.0625 0.0725 0.0825 0.0925 }
rates = values.collect { |value| Rate.new( value, :apr, :duration = (5 * 12) ) }
arm = Amortization.new(250000, *rates)

Borrow $250,000 under a 30 year, fixed-rate loan with a 4.25% APR, but pay $150 extra each month

rate = Rate.new(0.0425, :apr, :duration => (5 * 12))
extra_payments = 250000.amortize(rate){ |period| period.payment - 150 }

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(principal, *rates, &block) ⇒ Amortization

create a new Amortization instance

Parameters:

  • principal (DecNum)

    the initial amount of the loan or investment

  • rates (Rate)

    the applicable interest rates

  • block (Proc)

132
133
134
135
136
137
138
139
140
141
142
# File 'lib/finance/amortization.rb', line 132

def initialize(principal, *rates, &block)
  @principal = Flt::DecNum.new(principal.to_s)
  @rates     = rates
  @block     = block

  # compute the total duration from all of the rates.
  @periods = (rates.collect { |r| r.duration }).sum
  @period  = 0

  compute
end

Instance Attribute Details

#balanceDecNum (readonly)

Returns the balance of the loan at the end of the amortization period (usually zero).

Returns:

  • (DecNum)

    the balance of the loan at the end of the amortization period (usually zero)


24
25
26
# File 'lib/finance/amortization.rb', line 24

def balance
  @balance
end

#paymentDecNum (readonly)

Returns the required monthly payment. For loans with more than one rate, returns nil.

Returns:

  • (DecNum)

    the required monthly payment. For loans with more than one rate, returns nil


27
28
29
# File 'lib/finance/amortization.rb', line 27

def payment
  @payment
end

#principalDecNum (readonly)

Returns the principal amount of the loan.

Returns:

  • (DecNum)

    the principal amount of the loan


30
31
32
# File 'lib/finance/amortization.rb', line 30

def principal
  @principal
end

#ratesArray (readonly)

Returns the interest rates used for calculating the amortization.

Returns:

  • (Array)

    the interest rates used for calculating the amortization


33
34
35
# File 'lib/finance/amortization.rb', line 33

def rates
  @rates
end

Class Method Details

.payment(principal, rate, periods) ⇒ DecNum

Note:

in most cases, you will probably want to use rate.monthly when calling this function outside of an Amortization instance.

Returns the periodic payment due on a loan.

Examples:

rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
rate.duration #=> 360
Amortization.payment(200000, rate.monthly, rate.duration) #=> DecNum('-926.23')

Parameters:

  • principal (DecNum)

    the initial amount of the loan or investment

  • rate (Rate)

    the applicable interest rate (per period)

  • periods (Integer)

    the number of periods needed for repayment

Returns:

  • (DecNum)

    the periodic payment due on a loan

See Also:


174
175
176
177
178
179
180
181
# File 'lib/finance/amortization.rb', line 174

def Amortization.payment(principal, rate, periods)
  if rate.zero?
    # simplified formula to avoid division-by-zero when interest rate is zero
    return -(principal / periods).round(2)
  else
    return -(principal * (rate + (rate / ((1 + rate) ** periods - 1)))).round(2)
  end
end

Instance Method Details

#==(amortization) ⇒ Numeric

compare two Amortization instances

Parameters:

Returns:


39
40
41
# File 'lib/finance/amortization.rb', line 39

def ==(amortization)
  self.principal == amortization.principal and self.rates == amortization.rates and self.payments == amortization.payments
end

#additional_paymentsArray

Returns the amount of any additional payments in each period.

Examples:

rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
amt = 300000.amortize(rate){ |payment| payment.amount-100}
amt.additional_payments #=> [DecNum('-100.00'), DecNum('-100.00'), ... ]

Returns:

  • (Array)

    the amount of any additional payments in each period


49
50
51
# File 'lib/finance/amortization.rb', line 49

def additional_payments
  @transactions.find_all(&:payment?).collect{ |p| p.difference }
end

#amortize(rate) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

amortize the balance of loan with the given interest rate

Parameters:

  • rate (Rate)

    the interest rate to use in the amortization

Returns:

  • none


57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/finance/amortization.rb', line 57

def amortize(rate)
  # For the purposes of calculating a payment, the relevant time
  # period is the remaining number of periods in the loan, not
  # necessarily the duration of the rate itself.
  periods = @periods - @period
  amount = Amortization.payment @balance, rate.monthly, periods

  pmt = Payment.new(amount, :period => @period)
  if @block then pmt.modify(&@block) end

  rate.duration.to_i.times do
    # Do this first in case the balance is zero already.
    if @balance.zero? then break end

    # Compute and record interest on the outstanding balance.
    int = (@balance * rate.monthly).round(2)
    interest = Interest.new(int, :period => @period)
    @balance += interest.amount
    @transactions << interest.dup

    # Record payment.  Don't pay more than the outstanding balance.
    if pmt.amount.abs > @balance then pmt.amount = -@balance end
    @transactions << pmt.dup
    @balance += pmt.amount

    @period += 1
  end
end

#computeObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

compute the amortization of the principal

Returns:

  • none


89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/finance/amortization.rb', line 89

def compute
  @balance = @principal
  @transactions = []

  @rates.each do |rate|
    amortize(rate)
  end

  # Add any remaining balance due to rounding error to the last payment.
  unless @balance.zero?
    @transactions.find_all(&:payment?)[-1].amount -= @balance
    @balance = 0
  end

  if @rates.length == 1
    @payment = self.payments[0]
  else
    @payment = nil
  end

  @transactions.freeze
end

#durationInteger

Returns the time required to pay off the loan, in months.

Examples:

In most cases, the duration is equal to the total duration of all rates

rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
amt = 300000.amortize(rate)
amt.duration #=> 360

Extra payments may reduce the duration

rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
amt = 300000.amortize(rate){ |payment| payment.amount-100}
amt.duration #=> 319

Returns:

  • (Integer)

    the time required to pay off the loan, in months


122
123
124
# File 'lib/finance/amortization.rb', line 122

def duration
  self.payments.length
end

#inspectObject


145
146
147
# File 'lib/finance/amortization.rb', line 145

def inspect
  "Amortization.new(#{@principal})"
end

#interestArray

Returns the amount of interest charged in each period.

Examples:

find the total cost of interest for a loan

rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
amt = 300000.amortize(rate)
amt.interest.sum #=> DecNum('200163.94')

find the total interest charges in the first six months

rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
amt = 300000.amortize(rate)
amt.interest[0,6].sum #=> DecNum('5603.74')

Returns:

  • (Array)

    the amount of interest charged in each period


159
160
161
# File 'lib/finance/amortization.rb', line 159

def interest
  @transactions.find_all(&:interest?).collect{ |p| p.amount }
end

#paymentsArray

Returns the amount of the payment in each period.

Examples:

find the total payments for a loan

rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
amt = 300000.amortize(rate)
amt.payments.sum #=> DecNum('-500163.94')

Returns:

  • (Array)

    the amount of the payment in each period


189
190
191
# File 'lib/finance/amortization.rb', line 189

def payments
  @transactions.find_all(&:payment?).collect{ |p| p.amount }
end