Skip to content

Commit

Permalink
Bisection retries in Newton if right limit reached.
Browse files Browse the repository at this point in the history
  • Loading branch information
tubedude committed Aug 10, 2014
1 parent 1e60e3c commit 88174a2
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 26 deletions.
5 changes: 5 additions & 0 deletions CHANGE_LOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## Version 0.2.7

* Bisection will now retry XIRR in Newton Method if right limit is reached
* Options in config are now module constants

## Version 0.2.6

* New Bisection method to avoid not converging to negative IRRs if not enough precision
Expand Down
7 changes: 0 additions & 7 deletions lib/xirr/base.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
module Xirr

# Precision for BigDecimal
PRECISION = Xirr.config.precision.to_i
# Days in a year
DAYS_IN_YEAR = Xirr.config.days_in_year.to_f
# Epsilon: error margin
EPS = Xirr.config.eps.to_f

# Base module for XIRR calculation Methods
module Base
extend ActiveSupport::Concern
Expand Down
22 changes: 17 additions & 5 deletions lib/xirr/bisection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,25 @@ def xirr(midpoint = nil)
raise ArgumentError, "Did not converge after #{runs} tries."
end

# If enabled, will retry XIRR with NewtonMethod
if Xirr::FALLBACK && right_limit_reached?(right, midpoint)
return NewtonMethod.new(cf).xirr
end

return midpoint.round Xirr::PRECISION

end

private

# @param right [BigDecimal]
# @param midpoint [BigDecimal]
# @return [Boolean]
# Checks if result is the right limit.
def right_limit_reached?(right, midpoint)
(right - midpoint).abs < Xirr::EPS
end

# @param left [BigDecimal]
# @param midpoint [BigDecimal]
# @param right [BigDecimal]
Expand All @@ -43,12 +56,11 @@ def bisection(left, midpoint, right)
_left, _mid = npv_positive?(left), npv_positive?(midpoint)
if _left && _mid
return left, left, left if npv_positive?(right) # Not Enough Precision in the left to find the IRR
end
if _left == _mid
return midpoint, format_irr(midpoint, right), right # Result is to the Right
else
if _left == _mid
return midpoint, format_irr(midpoint, right), right # Result is to the Right
else
return left, format_irr(left, midpoint), midpoint # Result is to the Left
end
return left, format_irr(left, midpoint), midpoint # Result is to the Left
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/xirr/cashflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def choose_(method)
when :newton_method
NewtonMethod.new(self)
else
raise ArgumentError, "There is no #{method} method"
raise ArgumentError, "There is no method called #{method} "
end
end

Expand Down
12 changes: 7 additions & 5 deletions lib/xirr/config.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
module Xirr
include ActiveSupport::Configurable

# Default values
# Sets as constants all the entries in the Hash Default values
default_values = {
eps: '1.0e-12',
eps: '1.0e-6'.to_f,
days_in_year: 365,
iteration_limit: 100,
iteration_limit: 50,
precision: 6,
default_method: :bisection
default_method: :bisection,
fallback: true
}

# Iterates trhough default values and sets in config
default_values.each do |key, value|
self.config.send("#{key.to_sym}=", value)
const_set key.to_s.upcase.to_sym, value
end
end
end
2 changes: 1 addition & 1 deletion lib/xirr/version.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module Xirr
# Version of the Gem
VERSION = "0.2.6"
VERSION = "0.2.7"
end
29 changes: 25 additions & 4 deletions test/test_cashflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,27 @@
end
end

describe 'of a very good investment' do
before(:all) do
@cf = Cashflow.new
@cf << Transaction.new(1000000, date: Date.today - 180)
@cf << Transaction.new(-2200000, date: Date.today - 60)
@cf << Transaction.new(-800000, date: Date.today - 30)
end

it 'has an Internal Rate of Return on Bisection Method' do
assert_equal '22.352206 '.to_f, @cf.xirr
end

it 'has an Internal Rate of Return on Newton Method' do
assert_equal '22.352206 '.to_f, @cf.xirr(nil, :newton_method)
end

it 'has an educated guess' do
assert_equal '13.488 '.to_f, @cf.irr_guess
end
end

describe 'of a good investment' do
before(:all) do
@cf = Cashflow.new
Expand Down Expand Up @@ -97,16 +118,16 @@
end

it 'returns 0 instead of expection ' do
assert_equal BigDecimal.new(0, 6), @cf.xirr_no_exception
assert_equal BigDecimal.new(0, 6), @cf.xirr
end


it 'with a wrong method is invalid' do
assert_raises(ArgumentError) { @cf.xirr(nil, :no_method) }
assert_raises(ArgumentError) { @cf.xirr_with_exception(nil, :no_method) }
end

it 'raises error when xirr is called' do
assert_raises(ArgumentError) { @cf.xirr }
assert_raises(ArgumentError) { @cf.xirr_with_exception }
end

it 'raises error when xirr is called' do
Expand All @@ -127,7 +148,7 @@
end

it 'raises error when xirr is called' do
assert_raises(ArgumentError) { @cf.xirr }
assert_raises(ArgumentError) { @cf.xirr_with_exception }
end

it 'raises error when xirr is called' do
Expand Down
7 changes: 4 additions & 3 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@
require_relative 'lib/xirr/transaction.rb'
include Xirr
cf = Cashflow.new
cf << Transaction.new(1000, date: '1985-01-01'.to_date)
cf << Transaction.new(-6000, date: '1985-01-02'.to_date)
cf.xirr(0.15)
cf << Transaction.new(1000000, date: Date.today - 180)
cf << Transaction.new(-2200000, date: Date.today - 60)
cf << Transaction.new(-800000, date: Date.today - 30)
cf.xirr
=end

0 comments on commit 88174a2

Please sign in to comment.