Christopher Dicely
2/10/2008 4:53:00 PM
Here's my solution. Like others, it uses Newton's method. It may have
problems because it always uses zero as its initial estimate, though
all the test data I've thrown at it seems to work properly. The one
thing that is different than the other solutions I've seen posted is
that I wrapped everything up in a class. This is also my first quiz.
class IncomeStream
DEFAULT_TOLERANCE = 0.000_000_05
def self.irr(*flows)
self.new(flows).irr
end
def self.npv(discount_rate, *flows)
self.new(flows).npv(discount_rate)
end
def self.dpv(amount, time)
lambda {|rate| (-time) * amount * (1+rate)**(-time-1)}
end
def self.pv(amount, time)
lambda {|rate| amount * (1+rate)**(-time)}
end
def initialize(flows)
@flows = flows.to_a
if (not @flows.empty?) and (@flows.first.kind_of? Numeric)
@flows.each_index {|idx| @flows[idx]=[@flows[idx],idx]}
end
@pvs = @flows.map {|amount, time| IncomeStream.pv(amount, time)}
@dpvs = @flows.map {|amount, time| IncomeStream.dpv(amount, time)}
end
def npv(rate)
@pvs.inject(0) {|npv, item| npv+item.call(rate)}
end
def dnpv(rate)
@dpvs.inject(0) {|dnpv, item| dnpv+item.call(rate)}
end
def irr(tolerance=DEFAULT_TOLERANCE)
return nil if @flows.empty? or @flows.all? {|amount, time| amount = 0}
rate = 0.0
while (((n=npv(rate)).abs > tolerance) and rate.finite?)
d = dnpv(rate)
rate -= n/d
end
rate
end