From 88174a259e2a2c112f05c4c9c9304fb6a1d71771 Mon Sep 17 00:00:00 2001 From: tubedude Date: Sun, 10 Aug 2014 03:51:16 -0300 Subject: [PATCH] Bisection retries in Newton if right limit reached. --- CHANGE_LOG.md | 5 +++++ lib/xirr/base.rb | 7 ------- lib/xirr/bisection.rb | 22 +++++++++++++++++----- lib/xirr/cashflow.rb | 2 +- lib/xirr/config.rb | 12 +++++++----- lib/xirr/version.rb | 2 +- test/test_cashflow.rb | 29 +++++++++++++++++++++++++---- test/test_helper.rb | 7 ++++--- 8 files changed, 60 insertions(+), 26 deletions(-) diff --git a/CHANGE_LOG.md b/CHANGE_LOG.md index f85d104..3c7e2e7 100644 --- a/CHANGE_LOG.md +++ b/CHANGE_LOG.md @@ -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 diff --git a/lib/xirr/base.rb b/lib/xirr/base.rb index 7678cce..8d4189d 100644 --- a/lib/xirr/base.rb +++ b/lib/xirr/base.rb @@ -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 diff --git a/lib/xirr/bisection.rb b/lib/xirr/bisection.rb index c336a86..36f5909 100644 --- a/lib/xirr/bisection.rb +++ b/lib/xirr/bisection.rb @@ -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] @@ -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 diff --git a/lib/xirr/cashflow.rb b/lib/xirr/cashflow.rb index 8522558..78816c3 100644 --- a/lib/xirr/cashflow.rb +++ b/lib/xirr/cashflow.rb @@ -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 diff --git a/lib/xirr/config.rb b/lib/xirr/config.rb index 4fe269a..a13ffa6 100644 --- a/lib/xirr/config.rb +++ b/lib/xirr/config.rb @@ -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 \ No newline at end of file diff --git a/lib/xirr/version.rb b/lib/xirr/version.rb index aed4f66..c43223a 100644 --- a/lib/xirr/version.rb +++ b/lib/xirr/version.rb @@ -1,4 +1,4 @@ module Xirr # Version of the Gem - VERSION = "0.2.6" + VERSION = "0.2.7" end diff --git a/test/test_cashflow.rb b/test/test_cashflow.rb index 7a26771..c1ef1b1 100644 --- a/test/test_cashflow.rb +++ b/test/test_cashflow.rb @@ -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 @@ -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 @@ -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 diff --git a/test/test_helper.rb b/test/test_helper.rb index 13c0ef9..7a1ba8e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -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