Internal Rate of Return (#156)

On Feb 9, 10:41 pm, ThoML [email protected] wrote:

irr([+100,+10,+10,+10])
=> Infinity

Such cases have an undefined IRR, and thus the behavior is undefined.

I assume IRR=-1.4228295850 → -2.8421709430404e-14 wouldn’t qualify as
a solution?

Ah… nice catch! :slight_smile:

I assume IRR=-1.4228295850 -> -2.8421709430404e-14 wouldn’t qualify as
a solution?

Ah… nice catch! :slight_smile:

In the meantime I read HR’s post that the function isn’t defined for
-100%. I’d assume it isn’t defined for any values < -0.999999… As it
was previously stated, negative values probably are difficult to
interpret anyway. It seems in practice people use certain deprecation
rules in order to exclude such values.

Here’s my first solution, attached and pastied here:
http://pastie.caboo.se/149976

Its basically a binary search, repeatedly calculating the NVP and
adjusting
lower and upper bounds. Not too complicated, so let me just note a
couple
things:

  • Line 12 could have been a mirror of line 10, but I wanted to emphasize
    the
    symmetry.
  • Rationals are used throughout to ensure accuracy.
  • The “+ 1” on line 10 prevents 0 from being a fixed point.
  • The given accuracy guarantees the result will be within 1 / 10 **
    [accuracy]
    of the true answer. This is not the same thing as having [accuracy]
    number of
    correct digits. (Though the program could be made to do that if the
    guesses
    fell on negative powers of ten).

I haven’t done all that many quizzes, but those I have were very
enjoyable.
Thank you James.

-Jesse M.

Here is my golfed solution, using Newton’s method.
No sanity checks for the input or anything, but usually gives a result
which is accurate to >10 digits.

def irr(i)
x=1;9.times{x=x-(n=d=t=0;i.map{|e|n+==ex**t-=1;d+=t/x};n/d)};x-1
end

And James: So long, and thanks for all the quiz’.

Agh, hate to keep replying to myself, but I’ve made one more tweak (in
the
pastie) to manually check for increasing NVPs.

I wrote:

Here’s my first solution, attached and pastied here:
Parked at Loopia

I’ve taken out the net-negative check, since it was throwing out cases
where
there is a solution (like [-1000, 999]). I was trying to toss out the
cases
where the NVP graph is increasing past -1 (like [-100, -30, 35, -40,
-45]).
How can these be detected?

-Jesse M.

Hi,

here’s my solution (using Newton’s method, too).

def evaluate( cost_flows, x, n = 0 )
if n >= cost_flows.size
0.0
else
cost_flows[n] + evaluate(cost_flows, x, n+1) / (1.0 + x)
end
end

def irr( cost_flows, x = 0 )
d_cost_flows = (0…cost_flows.size).map{|t| -t*cost_flows[t]}

begin
    y = evaluate( cost_flows, x )
    yd = evaluate( d_cost_flows, x ) / (1.0+x)
    x -= y/yd
end until y.abs < 1e-9

return x

end

Here is my solution using Newton’s method. This works since the original
equation can be expressed as a polynomial by replacing (1-IRR) with X
and
dividing by the highest order of X. This solution is generally accurate
although for certain values it is possible for the solution to either
not
converge or converge to the wrong value:

Solve polynomial for given value of X and coefficients

def poly(x, coeff)
(0…coeff.size).inject(0) {|sum, i| sum + coeff[i] * (x **
(coeff.size -
i - 1))}
end

Solve for first derivative of a polynomial for given value of X and

coefficients
def poly_first_deriv(x, coeff)
poly(x, (0…coeff.size-1).inject([]){|deriv, i| deriv << coeff[i] * (
coeff.size - i - 1)})
end

def irr(coeff)
x, last_x = 1.0, 0.0
10.times do ||
last_x = x
x = x - (poly(x, coeff) / poly_first_deriv(x, coeff))
return (x - 1) * 100 if (last_x - x).abs < 0.00001
end
nil
end
So long James, and best of luck to you.
Thanks,

Justin

Mine is less concise, but it uses, guess what, Newton’s method.

compute NPV given cash flows and IRR

def npv (cf, irr)
(0…cf.length).inject(0) { |npv, t| npv + (cf[t]/(1+irr)**t) }
end

compute derivative of the NPV with respect to IRR

d(C_t * (1+IRR)t)/dIRR = -t * C_t / (1+IRR)(t-1)

def dnpv (cf, irr)
(1…cf.length).inject(0) { |npv, t| npv - (t*cf[t]/
(1+irr)**(t-1)) }
end

solve for IRR with newton’s method: x_{n+1} = x_n - f(x) / f’(x)

def irr (cf)
irr = 0.5
it = 0
begin
begin
oirr = irr
irr -= npv(cf, irr) / dnpv(cf, irr)
it += 1
return nil if it > 50
end until (irr - oirr).abs < 0.0001
rescue ZeroDivisionError
return nil
end
irr
end

puts irr([-100,30,35,40,45])
puts irr([-1.0,1.0])
puts irr([-1000.0,999.99])
puts irr([-1000.0,999.0])
puts irr([100,10,10,10])
puts irr([0.0])
puts irr([])

x=1;9.times{x=x-(n=d=t=0;i.map{|e|n+==ex**t-=1;d+=t/x};n/d)};x-1

Well, my solution is slightly more verbose. Which doesn’t imply it
yields better results. It uses the Secant method (which no other
solution has used yet, ha!) and the code in the core method was
shamelessly copied from wikipedia. Yeah! By default, my solution tries
to find values that converge with with Float::EPSILON. If the values
diverge, the interval is scanned for zero-values, which could also be
done in the first place (since this allows to find multiple roots).

Regards,
Thomas.

#!/usr/bin/env ruby19

require ‘date’

module IRR
module_function

# Use Secant method to find the roots. In case, the upper and

lower
# limit diverge, reset the start values.
#
# This method may miss certain IRRs outside the [0.0…1.0]
# intervall. In such a case use #irrer or set the optional :a
# (lower limit) and :b (upper limit) arguments.
#
# Based on:
# Secant method - Wikipedia
def irr(values, args={})
a = a0 = args[:a] || 0.0
b = b0 = args[:b] || 1.0
n = args[:n] || 100
e = args[:e] || Float::EPSILON
c0 = (a - b).abs
ab = nil
n.times do
fa = npv(values, a)
fb = npv(values, b)
c = (a - b) / (fa - fb) * fa;
if c.nan?
does_not_compute(values)
elsif c.infinite?
# break
return c
elsif c.abs < e
return a
end
# Protect against bad start values.
if c.abs > c0
ab ||= guess_start_values(values, args.merge(:min =>
a0, :max => b0))
if !ab.empty?
a, b, _ = ab.shift
c0 = (a - b).abs
next
end
end
b = a
a = a - c
c0 = c.abs
end
does_not_compute(values)
end

# Guess appropriate start values, return all solutions as Array.
def irrer(values, args={})
    guess_start_values(values, args).map do |a, b|
        irr(values, args.merge(:a => a, :b => b))
    end
end

# Calculate the NPV for a hypothetical IRR.
# Values are either an array of cash flows or of pairs [cash,
# date or days].
def npv(values, irr)
    sum  = 0
    d0   = nil
    values.each_with_index do |(v, d), t|
        # I have no idea if this is the right way to deal with
        # irregular time series.
        if d
            if d0
                t = (d - d0).to_f / 365.25
            else
                d0 = d
            end
        end
        sum += v / (1 + irr) ** t
    end
    sum
end

def does_not_compute(values)
    raise RuntimeError, %{Does not compute: %s} % values.inspect
end

# Check whether computation will converge easily.
def check_values(values)
    csgn = 0
    val, dat = values[-1]
    values.reverse.each do |v, d|
        csgn += 1 if val * v < 0
        val = v
    end
    return csgn == 1
end

# Try to find appropriate start values.
def guess_start_values(values, args={})
    min   = args[:min]   || -1.0
    max   = args[:max]   ||  2.0
    delta = args[:delta] ||  (max - min).to_f / (args[:steps] ||
  1. vals = []
    b, fb = nil

    The NPV is undefined for IRR < -100% or so they say.

    min.step(max, delta) do |a|
    fa = npv(values, a)
    if fb and !fa.infinite? and !fb.infinite? and fa * fb < 0
    vals << [b, a]
    end
    b = a
    fb = fa
    end
    return vals
    end

end

if FILE == $0
values = ARGV.map do |e|
v, d = e.split(/,/)
v = v.to_f
d ? [v, Date.parse(d)] : v
end
puts “Default solution: #{IRR.irr(values) rescue puts $!.message}”
begin
IRR.irrer(values).zip(IRR.guess_start_values(values)) do |irr,
(a, b)|
puts ‘[%5.2f…%5.2f] %13.10f → %13.10f’ % [a, b, irr,
IRR.npv(values, irr)]
end
rescue RuntimeError => e
puts e.message
end
puts “Possibly incorrect IRR value(s)” unless
IRR.check_values(values)
end

My solution, looping the Bolzano’s Theorem:

class Irr

attr_accessor :data, :irr

def initialize(data)
@data = data
@irr = 0
irr_calculus
end

def npv_calculus(rate)
npv = 0
@data.each do |c|
t = @data.index©
npv = npv + c / (1 + rate)**t
end
npv
end

def irr_calculus
r1 = 0.0
r2 = 1.0
npv1 = npv_calculus(r1)
npv2 = npv_calculus(r2)

# calcule initial interval
while npv1*npv2 > 0
  r1 = r1 + 1
  r2 = r2 + 1
  npv1 = npv_calculus(r1)
  npv2 = npv_calculus(r2)
end

# halfing interval to achieve precission
value = 1
while value > (1.0/10**4)
  r3 = (r1+r2)/2
  npv3 = npv_calculus(r3)
  if npv1*npv3 < 0
    r2 = r3
    npv2 = npv3
  else
    r1 = r3
    npv1 = npv3
  end
  value = (r1-r2).abs
end

@irr = (r1*10000).round/10000.0

end

end

data = [-100, 30, 35, 40, 45]
i = Irr.new(data)
puts i.irr

Here’s my binary search solution. It can return negative values
(extra credit) and returns nil for undefined behavior.

require “enumerator”
require “rubygems”
require “facets/numeric/round”

def npv(irr, cash_flows)
cash_flows.enum_with_index.inject(0) do |sum, (c_t, t)|
sum + c_t / (1+irr)**t
end
end

def irr(cash_flows, precision=10 ** -4)

establish an upper bound, return nil if none

max = 1.0
max *= 2 until npv(max, cash_flows) < 0 or max.infinite?
return nil if max.infinite?

initialize search variables

last_irr, irr, radius = max, 0.0, max

binary search until precision is met

until irr.approx?(last_irr, precision/10)
last_irr = irr

# improve approximation of irr
if npv(irr, cash_flows) < 0
  irr -= radius
else
  irr += radius
end

# reduce the search space by half
radius /= 2

end

irr.round_to(precision)
end

if FILE == $PROGRAM_NAME
puts irr(ARGV.map { |e| Float(e) }) || “Undefined”
end

On Feb 8, 2008 3:01 PM, Ruby Q. [email protected] wrote:

The three rules of Ruby Q.:

  1. Please do not post any solutions or spoiler discussion for this quiz until
    48 hours have passed from the time on this message.

  2. Support Ruby Q. by submitting ideas as often as you can:

http://www.rubyquiz.com/

  1. Enjoy!

Hi,

I don’t have a solution for today’s quiz, but I just wanted to say a
big Thank You !! to James for the Ruby Q… It has been and it is a
great asset of the Ruby community and that you have done a wonderful
job as the Quizmaster.

Again thanks.

Jesus.

Here’s a second solution from me:
http://pastie.caboo.se/150029

Well, maybe its a half-solution, since it defers the real work to an
external
program. A while ago I wrote some code for communicating with a Maxima
process [1]. This solution uses it to directly solve the NVP equation
(for
both real and complex IRRs). Note that there is a bug in version 0.0.4
of my
library that incorrectly returns only the first line of a multi-line
result,
so it doesn’t always work. I’ve had it fixed in a newer version for a
while,
but never released it. I’ll try to get it up within the next few days.

[1] http://www.jessemerriman.com/project/ltf

On Feb 8, 4:01 pm, Ruby Q. [email protected] wrote:

This week’s quiz is to calculate the IRR for any given variable-length list of
numbers…

Here is my solution.

I’m too lazy to use Newton’s method, so I’ve employed a binary
search. The most difficult part was, no surprise, determining the
bounds of search. :slight_smile:

#!/usr/bin/env ruby

def npv(ct, i)
sum = 0
ct.each_with_index{ |c,t| sum += c/(1 + i.to_f)**t }
sum
end

def irr(ct)
l = -0.9999
sign = npv(ct, l)

p sign

r = 1.0
while npv(ct, r)*sign > 0 do

p npv(ct, r)

r *= 2
break if r > 1000

end
if r > 1000
l = -1.0001
sign = npv(ct, l)
r = -2.0
while npv(ct, r)*sign > 0 do
r *= 2
return 0.0/0.0 if r.abs > 1000
end
end

m = 0
loop do
m = (l + r)/2.0
v = npv(ct, m)

p v

break if v.abs < 1e-8
if v*sign < 0
  r = m
else
  l = m
end

end
m
end

if FILE == $0
p irr([-100,+30,+35,+40,+45])
p irr([-100,+10,+10,+10])
p irr([+100,+10,+10,+10])
p irr([+100,-90,-90,-90])
p irr([+0,+10,+10,+10])
end

Here’s my solution:

irr.rb:

require ‘algebra’

class IRR

def self.calculate(profits)
begin
function(profits).zero
rescue Algebra::MaximumIterationsReached => mir
nil
end
end

private

def self.function(profits)
Algebra::Function.new do |x|
sumands = Array.new
profits.each_with_index {|profit, index| sumands <<
profit.to_f / (1 + x) ** index }
sumands.inject(0) {|sum, sumand| sum + sumand }
end
end

end

puts IRR.calculate([-100, 30, 35, 40, 45])
puts IRR.calculate([-1, 1])
puts IRR.calculate([])

algebra.rb:

module Algebra

class MaximumIterationsReached < Exception
end

class NewtonsMethod

def self.calculate(function, x)
  x - function.evaluated_at(x) / function.derivative_at(x)
end

end

class NewtonsDifferenceQuotient

def self.calculate(function, x, delta=0.1)
  (function.evaluated_at(x + delta) -

function.evaluated_at(x) ).to_f / delta
end

end

class Function

attr_accessor :differentiation_method, :root_method,
:maximum_iterations, :tolerance

def initialize(differentiation_method=NewtonsDifferenceQuotient,

root_method=NewtonsMethod, &block)
@definition = block
@differentiation_method, @root_method = differentiation_method,
root_method
@maximum_iterations = 1000
@tolerance = 0.0001
end

def evaluated_at(x)
  @definition.call(x)
end

def derivative_at(x)
  differentiation_method.calculate(self, x)
end

def zero(initial_value=0)
  recursive_zero(initial_value, 1)
end

private

def recursive_zero(guess, iteration)
  raise MaximumIterationsReached if iteration >=

@maximum_iterations

  better_guess = @root_method.calculate(self, guess)

  if (better_guess - guess).abs <= @tolerance
    better_guess
  else
    recursive_zero(better_guess, iteration + 1)
  end
end

end

end

Comments welcomed. Thanks,

On Feb 10, 2008, at 12:39 PM, Jesús Gabriel y Galán wrote:

I just wanted to say a
big Thank You !! to James for the Ruby Q… It has been and it is a
great asset of the Ruby community and that you have done a wonderful
job as the Quizmaster.

Thanks so much to you for participating in many of the problems.

James Edward G. II

On Feb 10, 2008, at 8:58 AM, Sander L. wrote:

And James: So long, and thanks for all the quiz’.

Thank you for being a regular.

James Edward G. II

On 2/8/08, Ruby Q. [email protected] wrote:

This week’s quiz is to calculate the IRR for any given variable-length list of
numbers,

Here’s my iterative solution. It should produce at least as many
significant digits as you specify, and return +/-Infinity for
solutions which don’t converge.


class Array; def sum;inject(0){|s,v|s+v};end;end

def npv c,irr
npv=0
c.each_with_index{|c_t,t|
npv+= c_t.to_f / (1.0+irr)**t.to_f
}
npv
end

def irr c, significant_digits=4
limit = 10**-(significant_digits)
estimate = c.sum/(c.size-1.0)/75.0
delta = estimate.abs
n=npv(c,estimate)
while n.abs > limit
sign = n/n.abs

p “%.6f → %.6f”%[estimate,n]

if (delta.abs < limit/1000)
  delta=estimate.abs               #if we aren't getting anywhere,

take a big jump
return sign/0.0 if (n-c[0]).abs < limit
end
estimate += (delta/=2) * sign
n=npv c,estimate
end
estimate
end

Thank you for all the work that went into producing these quizzes,
James. I’ve really enjoyed them.

-Adam

On Feb 10, 2008, at 10:41 AM, Justin E. wrote:

So long James, and best of luck to you.

Thank you.

James Edward G. II