class FinancialCalculator::Amortization
the Amortization
class provides an interface for working with loan amortizations. @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.
@example 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)
@example 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)
@example 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 }
@api public
Attributes
@return [DecNum] the balance of the loan at the end of the amortization period (usually zero) @api public
@return [DecNum] the required monthly payment. For loans with more than one rate, returns nil @api public
@return [DecNum] the principal amount of the loan @api public
@return [Array] the interest rates used for calculating the amortization @api public
Public Class Methods
create a new Amortization
instance @return [Amortization] @param [DecNum] principal the initial amount of the loan or investment @param [Rate] rates the applicable interest rates @param [Proc] block @api public
# 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
@return [DecNum] the periodic payment due on a loan @param [DecNum] principal the initial amount of the loan or investment @param [Rate] rate the applicable interest rate (per period) @param [Integer] periods the number of periods needed for repayment @note in most cases, you will probably want to use rate.monthly when calling this function outside of an Amortization
instance. @example
rate = Rate.new(0.0375, :apr, :duration => (30 * 12)) rate.duration #=> 360 Amortization.payment(200000, rate.monthly, rate.duration) #=> DecNum('-926.23')
@see en.wikipedia.org/wiki/Amortization_calculator @api public
# 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
Public Instance Methods
compare two Amortization
instances @return [Numeric] -1, 0, or +1 @param [Amortization] @api public
# 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
@return [Array] the amount of any additional payments in each period @example
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'), ... ]
@api public
# File lib/financial_calculator/amortization.rb, line 47 def additional_payments @transactions.find_all(&:payment?).collect{ |p| p.difference } end
amortize the balance of loan with the given interest rate @return none @param [Rate] rate the interest rate to use in the amortization @api private
# 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
compute the amortization of the principal @return none @api private
# 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
@return [Integer] the time required to pay off the loan, in months @example 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
@example 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
@api public
# File lib/financial_calculator/amortization.rb, line 120 def duration self.payments.length end
@api public
# File lib/financial_calculator/amortization.rb, line 143 def inspect "Amortization.new(#{@principal})" end
@return [Array] the amount of interest charged in each period @example 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')
@example 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')
@api public
# File lib/financial_calculator/amortization.rb, line 157 def interest @transactions.find_all(&:interest?).collect{ |p| p.amount } end
@return [Array] the amount of the payment in each period @example 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')
@api public
# File lib/financial_calculator/amortization.rb, line 187 def payments @transactions.find_all(&:payment?).collect{ |p| p.amount } end