Class: FinancialCalculator::Amortization

Inherits:
Object
  • Object
show all
Defined in:
lib/financial_calculator/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)


130
131
132
133
134
135
136
137
138
139
140
# File 'lib/financial_calculator/amortization.rb', line 130

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)



22
23
24
# File 'lib/financial_calculator/amortization.rb', line 22

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



25
26
27
# File 'lib/financial_calculator/amortization.rb', line 25

def payment
  @payment
end

#principalDecNum (readonly)

Returns the principal amount of the loan.

Returns:

  • (DecNum)

    the principal amount of the loan



28
29
30
# File 'lib/financial_calculator/amortization.rb', line 28

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



31
32
33
# File 'lib/financial_calculator/amortization.rb', line 31

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:



172
173
174
175
176
177
178
179
# File 'lib/financial_calculator/amortization.rb', line 172

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:



37
38
39
# File 'lib/financial_calculator/amortization.rb', line 37

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



47
48
49
# File 'lib/financial_calculator/amortization.rb', line 47

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



55
56
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
# File 'lib/financial_calculator/amortization.rb', line 55

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



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

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



120
121
122
# File 'lib/financial_calculator/amortization.rb', line 120

def duration
  self.payments.length
end

#inspectObject



143
144
145
# File 'lib/financial_calculator/amortization.rb', line 143

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



157
158
159
# File 'lib/financial_calculator/amortization.rb', line 157

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



187
188
189
# File 'lib/financial_calculator/amortization.rb', line 187

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