class FinancialCalculator::Xirr

Constants

NUM_ITERATIONS

The number of iterations to perform while attempting to minimize the root.

Attributes

result[R]

@return [DecNum] The internal rate of return @example

transactions = []
transactions << Transaction.new(-1000, :date => Date.new(1985,01,01))
transactions << Transaction.new(  600, :date => Date.new(1990,01,01))
transactions << Transaction.new(  600, :date => Date.new(1995,01,01))
Xirr.with_transactions(transactions).result #=> 0.0249

Public Class Methods

new(cashflows, dates, r_1 = nil, r_2 = nil) click to toggle source

Creates a new Xirr instance @param [Array<Numeric>] cashflows An array of cash flows. @param [Numeric] r_1 An optional first guess to use for the secant method. @param [Numeric] r_2 An optional second guess to use for the secant method. @return [FinancialClaculator::Xirr] An Xirr instance @see en.wikipedia.org/wiki/Internal_rate_of_return @api public

# File lib/financial_calculator/xirr.rb, line 39
def initialize(cashflows, dates, r_1 = nil, r_2 = nil)
  unless (cashflows[0].positive? ^ cashflows[1].positive?) & cashflows[0].nonzero?
    raise ArgumentError.new('The cashflows do not converge') 
  end
  @eps = 1e-7
  @cashflows = cashflows.map { |val| Flt::DecNum(val.to_s) }
  @dates  = dates

  r_1 = r_1 ? Flt::DecNum(r_1.to_s) : initial_r_1
  r_2 = r_2 ? Flt::DecNum(r_2.to_s) : initial_r_2

  @result = solve(@cashflows, @dates, r_1, r_2)
end
with_transactions(transactions, r_1 = nil, r_2 = nil) click to toggle source

Factory method for creating an Xirr instance from an array of transactions @param [Array<Transaction>] transactions An array of transactions @param [Numeric] /_1 An optional first guess to use when calculating the secant method @param [Numeric] r_2 An optional second guess to use when calculating the secant method @return [FinancialCalculator::Xirr] An Xirr instance @api public

# File lib/financial_calculator/xirr.rb, line 21
def self.with_transactions(transactions, r_1 = nil, r_2 = nil)
  unless transactions.all? { |t| t.is_a? Transaction }
    raise ArgumentError.new("Argument \"transactions\" must be an array of Transaction")
  end

  cashflows  = transactions.map(&:amount)
  dates      = transactions.map(&:date)

  self.new(cashflows, dates, r_1, r_2)
end

Public Instance Methods

inspect() click to toggle source

@return [String] @api public

# File lib/financial_calculator/xirr.rb, line 55
def inspect
  "XIRR(#{@result})"
end

Private Instance Methods

abs_c_0() click to toggle source
# File lib/financial_calculator/xirr.rb, line 108
def abs_c_0
  @cashflows[0].abs
end
cap_a() click to toggle source
# File lib/financial_calculator/xirr.rb, line 104
def cap_a
  @cap_a ||= @cashflows[1..-1].sum
end
cap_a_over_abs_cap_c_0() click to toggle source
# File lib/financial_calculator/xirr.rb, line 100
def cap_a_over_abs_cap_c_0
  cap_a / abs_c_0
end
converged?(r_1, r_2) click to toggle source
# File lib/financial_calculator/xirr.rb, line 120
def converged?(r_1, r_2)
  (r_1 - r_2).abs < @eps
end
initial_r_1() click to toggle source

Default first guess for use in the secant method @see en.wikipedia.org/wiki/Internal_rate_of_return#Numerical_solution_for_single_outflow_and_multiple_inflows

# File lib/financial_calculator/xirr.rb, line 90
def initial_r_1
  @initial_r_1 ||= cap_a_over_abs_cap_c_0 ** (2 / Flt::DecNum(@cashflows.length.to_s)) - 1
end
initial_r_2() click to toggle source

Default second guess for use in the secant method @see en.wikipedia.org/wiki/Internal_rate_of_return#Numerical_solution_for_single_outflow_and_multiple_inflows

# File lib/financial_calculator/xirr.rb, line 96
def initial_r_2
  (1 + initial_r_1) ** p - 1
end
iterate(cashflows, dates, r_1, r_2) click to toggle source

Perform a single iteration @param [Array<Numeric>] cashflows @param [Numeric] r_1 @param [Numeric] r_2 @return [Numeric] A rate of return that is closer to the actual internal rate of return

than the previous iteration
# File lib/financial_calculator/xirr.rb, line 67
def iterate(cashflows, dates, r_1, r_2)
  fn_1 = Xnpv.new(r_1, cashflows, dates).result
  fn_2 = Xnpv.new(r_2, cashflows, dates).result

  r_1 - (fn_1 * (r_1 - r_2)) / (fn_1 - fn_2)
end
npv_1_in(rate) click to toggle source
# File lib/financial_calculator/xirr.rb, line 116
def npv_1_in(rate)
  @flows ||= Xnpv.new(rate, [0] + @cashflows[1..-1].flatten, @dates).result
end
p() click to toggle source
# File lib/financial_calculator/xirr.rb, line 112
def p
  Flt::DecNum(Math.log(cap_a_over_abs_cap_c_0).to_s) / Flt::DecNum(Math.log(cap_a / npv_1_in(initial_r_1)).to_s)
end
solve(cashflows, dates, r_1, r_2) click to toggle source

Solve the Xirr @param [Array<Numeric>] cashflows @param [Numeric] r_1 @param [Numeric] r_2 @return [DecNum] The internal rate of return

# File lib/financial_calculator/xirr.rb, line 79
def solve(cashflows, dates, r_1, r_2)
  NUM_ITERATIONS.times do
    break if r_1.infinite? || converged?(r_1, r_2)
    r_2, r_1 = [r_1, iterate(cashflows, dates, r_1, r_2)]
  end

  r_1.infinite? ? r_2 : r_1
end