[QUIZ] Internal Rate of Return (#156)

I was hoping someone would try this! It wouldn't be very difficult to
adapt an existing yearly algorithm to do it; all that changes is the
exponent for each term. I'd take only two arguments, though: the
starting date and {dates => cash flows}.

Harrison Reiser

···

On Feb 8, 4:43 pm, Siep Korteling <s.kortel...@gmail.com> wrote:

James Gray wrote:
> by Harrison Reiser
(...)
>This week's quiz is to calculate the IRR for any given variable-length
>list of numbers, which represent yearly cash flows,

In real life, it's not yearly. IIR needs a time dimension.

irr([-100, a_date],[+30,another_date],[+35,yet_a _date])

James, I learned a LOT thanks to you.

regards,

Siep

--
Posted viahttp://www.ruby-forum.com/.

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+=_=e*x**t-=1;d+=t*_/x};n/d)};x-1
end

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

I wrote:

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

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 Merriman

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

James Edward Gray II

···

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 Quiz. It has been and it is a
great asset of the Ruby community and that you have done a wonderful
job as the Quizmaster.

Thank you.

James Edward Gray II

···

On Feb 10, 2008, at 10:41 AM, Justin Ethier wrote:

So long James, and best of luck to you.

Thank you Adam. It's great to hear from another regular.

James Edward Gray II

···

On Feb 11, 2008, at 12:14 PM, Adam Shelly wrote:

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

Yes, you want the NPV to be zero... so you really just have to solve for IRR. Another good thing pointed out in the article which I wish I'd read before trying to solve it generally is that you can't solve it in general with a formula :stuck_out_tongue:

···

On Feb 9, 2008, at 10:04 AM, Christopher Dicely wrote:

2008/2/8 Matthew Moss <matthew.moss@gmail.com>:

Internal Rate of Return (IRR -http://en.wikipedia.org/wiki/Internal_rate_of_return\) is a common financial
metric, used by investment firms to predict the profitability of a company or
project. Finding the IRR of a company amounts to solving for it in the equation
for Net Present Value (NPV -http://en.wikipedia.org/wiki/Net_present_value\),
another valuable decision-making metric:

              N C_t
       NPV = Σ ------------
             t=0 (1 + IRR)**t

This week's quiz is to calculate the IRR for any given variable-length list of
numbers, which represent yearly cash flows, the C_t's in the formula above: C_0,
C_1, etc. (C_0 is typically a negative value, corresponding to the initial
investment into the project.) From the example in the Wikipedia article
(http://en.wikipedia.org/wiki/Internal_rate_of_return\), for instance, you should
be able to produce a rate of 17.09% (to four decimal places, let's say) from
this or a similar command:

       irr([-100,+30,+35,+40,+45])
       => 0.1709...

I think one point, which isn't brought out here and not well in the
wikipedia article either, is that given all of the C_t, you still have
two unknowns: IRR (which we are attempting to solve for) and NPV.

In this case, you want NPV set to zero in order to solve for IRR. Or
did I miss something?

Isn't IRR defined as the discount rate that results in an NPV of 0?

How should the following values be handeld? According to some info I
found somewhere, 0..2 should be considered illegal, which doesn't
quite match my computations though.

[-1.0, 1.0]

0

[-1000.0, 999.99]

-0.00001

[-1000.0, 999.0]

-0.001

[0.0]

does not find a solution (mine returns nil)

division by zero. (nil after I add a begin..rescue..end :slight_smile:

Paolo

···

On Feb 9, 2:08 pm, ThoML <micat...@gmail.com> wrote:

Yes, (0..2) doesn't make any sense at all, nor does anything that has
an initial investment of $0. The IRR would be 0/0, which can be
anything, and so you may return anything (or throw an error) if the
first element is a 0. An input list of less than two elements is
similarly ambiguous. As for the others, they do in fact have real
IRRs:

[-1.0, 1.0] => 0.0%
[-1000.0, 999.99] => -0.0001%
[-1000.0, 999.0] => -0.01%

The first case is interesting, however, because you will not be able
to find an IRR of 0% exactly if you are using an iterative solution,
only approach it, depending on how many iterations you use. Also, the
formula does not allow -100% IRR, even though it may be possible.

Harrison Reiser

···

On Feb 9, 6:08 am, ThoML <micat...@gmail.com> wrote:

How should the following values be handeld? According to some info I
found somewhere, 0..2 should be considered illegal, which doesn't
quite match my computations though.

[-1.0, 1.0]
[-1000.0, 999.99]
[-1000.0, 999.0]
[0.0]

Regards,
Thomas.

> 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?

Is it OK to format user's hard drive then? :wink:

···

On Feb 9, 9:14 pm, b...@insecti.com wrote:

On Feb 9, 5:48 am, Alex Shulgin <alex.shul...@gmail.com> wrote:

> Can we return 1/0.0 if NPV does not evaluate to 0 for a reasonably
> large IRR?
>
> irr([+100,+10,+10,+10])
> => Infinity

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

--
Alex

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:

···

I wrote:
> Here's my first solution, attached and pastied here:
> http://pastie.caboo.se/149976

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 Merriman

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+=_=e*x**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] ||
100)
        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

Thank you for being a regular.

James Edward Gray II

···

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

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

What's the best way to find all occurances of a string in a hash's index?

That is,

x = { 'Hello' => 1, 'Goodbye' => 2, 'Hello there' => 3}

How do I most efficiently get 1 and 3 to return on a search for 'Hello' in the index of the hash? I know I can iterate over the index, but is there a better way?

Thanks.

> How should the following values be handeld? According to some info I
> found somewhere, 0..2 should be considered illegal, which doesn't
> quite match my computations though.
>
> [-1.0, 1.0]

0

> [-1000.0, 999.99]

-0.00001

> [-1000.0, 999.0]

-0.001

> [0.0]

does not find a solution (mine returns nil)

Its a meaningless input anyway, but wouldn't any rate be a solution?
Since you are looking for the discount rate at which the NPV is 0, if
the only flow is an initial investment of 0, any discount rate will
produce an NPV of 0. (Mine produces 0 for this case.)

>

division by zero. (nil after I add a begin..rescue..end :slight_smile:

I would think this would be equivalent to the preceding case and,
likewise, any discount rate you try would be a valid solution. (And,
again, mine produces 0 here.)

···

On Feb 9, 2008 9:44 AM, <paolo.bonzini@gmail.com> wrote:

On Feb 9, 2:08 pm, ThoML <micat...@gmail.com> wrote:

Is that really a special result of the first case? It seems to me any
iterative method is likely to only approximate any result except for
the one used as the initial starting point (of course, if zero is the
initial starting point, then it can get zero exactly).

···

On Feb 9, 2008 11:44 AM, <bug@insecti.com> wrote:

On Feb 9, 6:08 am, ThoML <micat...@gmail.com> wrote:
> How should the following values be handeld? According to some info I
> found somewhere, 0..2 should be considered illegal, which doesn't
> quite match my computations though.
>
> [-1.0, 1.0]
> [-1000.0, 999.99]
> [-1000.0, 999.0]
> [0.0]
>
>
> Regards,
> Thomas.

Yes, (0..2) doesn't make any sense at all, nor does anything that has
an initial investment of $0. The IRR would be 0/0, which can be
anything, and so you may return anything (or throw an error) if the
first element is a 0. An input list of less than two elements is
similarly ambiguous. As for the others, they do in fact have real
IRRs:

[-1.0, 1.0] => 0.0%
[-1000.0, 999.99] => -0.0001%
[-1000.0, 999.0] => -0.01%

The first case is interesting, however, because you will not be able
to find an IRR of 0% exactly if you are using an iterative solution,
only approach it, depending on how many iterations you use.

Ah... nice catch! :slight_smile:

···

On Feb 9, 10:41 pm, ThoML <micat...@gmail.com> 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?

--
Alex

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(c)
      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

···

--
Posted via http://www.ruby-forum.com/.