From b8126ede94e5ff79ce431054a9d3533c6bb433ed Mon Sep 17 00:00:00 2001 From: weshatheleopard Date: Wed, 26 Aug 2015 00:32:02 +0000 Subject: [PATCH 01/11] Issue #38 --- lib/finance/cashflows.rb | 12 +++++++++++- lib/finance/transaction.rb | 3 ++- test/test_cashflows.rb | 25 +++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/lib/finance/cashflows.rb b/lib/finance/cashflows.rb index 985ca36..459550f 100644 --- a/lib/finance/cashflows.rb +++ b/lib/finance/cashflows.rb @@ -33,7 +33,17 @@ def initialize(transactions, function) def values(x) value = @transactions.send(@function, Flt::DecNum.new(x[0].to_s)) - [ BigDecimal.new(value.to_s) ] + + # Due to the fact that somewhere along the way we are taking negative + # values to a fractional power, +value+ ends up being a +Complex+ number. + # When you do +value.to_s+ of a +Complex+ number has a form "12.34+56.78i" + # and when you take +BigDecimal.new()+ of that, the imaginary part is completely + # ignored, guiding the Newtonian into entirely wrong direction (see Issue #38). + # So we have to do some dancing around to ensure it doesn't get ignored. + value_direction = (value.real <=>0) + value_direction = (value.imaginary <=> 0) if value_direction == 0 + + [ BigDecimal.new((value.magnitude * value_direction).to_s) ] end end diff --git a/lib/finance/transaction.rb b/lib/finance/transaction.rb index 0ca6e76..91ef021 100644 --- a/lib/finance/transaction.rb +++ b/lib/finance/transaction.rb @@ -71,7 +71,8 @@ def interest? # @api public def inspect - "Transaction(#{@amount})" + dt = date.strftime(' <%F>') if date + "Transaction(#{@amount}#{dt})" end # Modify a Transaction's amount by passing a block diff --git a/test/test_cashflows.rb b/test/test_cashflows.rb index 835cc8c..d564625 100644 --- a/test/test_cashflows.rb +++ b/test/test_cashflows.rb @@ -28,5 +28,30 @@ it "should have a Net Present Value" do assert_equal D("-937.41"), @xactions.xnpv(0.6).round(2) end + + it "should properly calculate IRR when Complex numbers arise from calculations" do + arr = [ Finance::Transaction.new( 70, :date => Time.new(2015,7,31)), + Finance::Transaction.new(-90, :date => Time.new(2021,1,13)), + Finance::Transaction.new(-20, :date => Time.new(2021,3,31)) ] + assert_equal D("0.085677"), arr.xirr.effective.round(6) + end + + it "IRR should decrease as payback moves further and further into the future" do + prev_max = 99999 + + (1..30).each do |n| + arr = [ Finance::Transaction.new(100, :date => Time.new(2001,1,1)), + Finance::Transaction.new(-50, :date => Time.new(2001,2,1)), + Finance::Transaction.new(-60, :date => Time.new(2001,2,1) + n * 30*24*3600) ] + + irr = arr.xirr.effective.round(6) + + assert(irr > 0, "IRR should not be negative; encountered #{irr}") + assert(irr < prev_max, "IRR should only decrease, but it increased from #{prev_max} to #{irr}") + + prev_max = irr + end + end + end end From ce3bd6f6979e7a461fa2808a7e2c80523431fc41 Mon Sep 17 00:00:00 2001 From: weshatheleopard Date: Fri, 28 Aug 2015 00:33:00 +0000 Subject: [PATCH 02/11] Cleaning up and updating comments --- lib/finance/cashflows.rb | 5 +++-- lib/finance/transaction.rb | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/finance/cashflows.rb b/lib/finance/cashflows.rb index 459550f..d2b8be7 100644 --- a/lib/finance/cashflows.rb +++ b/lib/finance/cashflows.rb @@ -36,10 +36,11 @@ def values(x) # Due to the fact that somewhere along the way we are taking negative # values to a fractional power, +value+ ends up being a +Complex+ number. - # When you do +value.to_s+ of a +Complex+ number has a form "12.34+56.78i" + # String representation (+value.to_s+) of a +Complex+ number looks like "12.34+56.78i", # and when you take +BigDecimal.new()+ of that, the imaginary part is completely # ignored, guiding the Newtonian into entirely wrong direction (see Issue #38). - # So we have to do some dancing around to ensure it doesn't get ignored. + # Instead, we should get the magnitude of complex number and ensure it points + # the vector points in the right direction, too. value_direction = (value.real <=>0) value_direction = (value.imaginary <=> 0) if value_direction == 0 diff --git a/lib/finance/transaction.rb b/lib/finance/transaction.rb index 91ef021..3d86cec 100644 --- a/lib/finance/transaction.rb +++ b/lib/finance/transaction.rb @@ -71,8 +71,8 @@ def interest? # @api public def inspect - dt = date.strftime(' <%F>') if date - "Transaction(#{@amount}#{dt})" + dt = date && date.strftime(' <%F>') || '' + str = "Transaction(#{@amount}#{dt})" end # Modify a Transaction's amount by passing a block From d32d20f0d4a6a3882c213fc3a991ac0c0fa82e0f Mon Sep 17 00:00:00 2001 From: Eric Nelson Date: Thu, 7 Jan 2016 16:42:35 -0600 Subject: [PATCH 03/11] Remove the rake dependency from gemspec --- finance.gemspec | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/finance.gemspec b/finance.gemspec index b77d282..9e9cc4e 100644 --- a/finance.gemspec +++ b/finance.gemspec @@ -1,5 +1,4 @@ require 'rubygems' -require 'rake' SPEC = Gem::Specification.new do |s| s.name = "finance" @@ -15,8 +14,7 @@ SPEC = Gem::Specification.new do |s| s.add_dependency 'flt', '>=1.3.0' s.add_development_dependency 'minitest', '>= 4.7.5' s.add_development_dependency 'activesupport', '>= 4.0.0' - s.files = FileList['README.md', 'COPYING', 'COPYING.LESSER', 'HISTORY', 'lib/**/*.rb', 'test/**/*.rb'].to_a - + s.files = ['README.md', 'COPYING', 'COPYING.LESSER', 'HISTORY', Dir.glob('lib/**/*.rb'), Dir.glob('test/**/*.rb')].flatten s.has_rdoc = true s.extra_rdoc_files = ['README.md', 'COPYING', 'COPYING.LESSER', 'HISTORY'] end From a4f7ca1341752140d4beebc449ba659c24afb7dd Mon Sep 17 00:00:00 2001 From: weshatheleopard Date: Thu, 14 Jan 2016 00:26:09 +0000 Subject: [PATCH 04/11] Making sure the DST doesn't mess up with interest calcilations --- test/test_cashflows.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/test_cashflows.rb b/test/test_cashflows.rb index d564625..8ae1a35 100644 --- a/test/test_cashflows.rb +++ b/test/test_cashflows.rb @@ -30,9 +30,11 @@ end it "should properly calculate IRR when Complex numbers arise from calculations" do - arr = [ Finance::Transaction.new( 70, :date => Time.new(2015,7,31)), - Finance::Transaction.new(-90, :date => Time.new(2021,1,13)), - Finance::Transaction.new(-20, :date => Time.new(2021,3,31)) ] + # Note that times must be in UTC (+00:00) here, so + # the local system's DST settings don't screw us up. + arr = [ Finance::Transaction.new( 70, :date => Time.new(2015,7,31,0,0,0,'+00:00')), + Finance::Transaction.new(-90, :date => Time.new(2021,1,13,0,0,0,'+00:00')), + Finance::Transaction.new(-20, :date => Time.new(2021,3,31,0,0,0,'+00:00')) ] assert_equal D("0.085677"), arr.xirr.effective.round(6) end From ea76a8405d2fef1320aa48d5353b5ed705d1dcfa Mon Sep 17 00:00:00 2001 From: weshatheleopard Date: Thu, 14 Jan 2016 01:55:04 +0000 Subject: [PATCH 05/11] Remove unused variable --- lib/finance/transaction.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/finance/transaction.rb b/lib/finance/transaction.rb index 3d86cec..a00a8fb 100644 --- a/lib/finance/transaction.rb +++ b/lib/finance/transaction.rb @@ -72,7 +72,7 @@ def interest? # @api public def inspect dt = date && date.strftime(' <%F>') || '' - str = "Transaction(#{@amount}#{dt})" + "Transaction(#{@amount}#{dt})" end # Modify a Transaction's amount by passing a block From 6a772dbdedd5800408e85bcf0bd288d291860da0 Mon Sep 17 00:00:00 2001 From: weshatheleopard Date: Thu, 14 Jan 2016 01:57:49 +0000 Subject: [PATCH 06/11] further simplification --- lib/finance/transaction.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/finance/transaction.rb b/lib/finance/transaction.rb index a00a8fb..af6bb94 100644 --- a/lib/finance/transaction.rb +++ b/lib/finance/transaction.rb @@ -71,8 +71,7 @@ def interest? # @api public def inspect - dt = date && date.strftime(' <%F>') || '' - "Transaction(#{@amount}#{dt})" + "Transaction(#{@amount}#{date && date.strftime(' <%F>')})" end # Modify a Transaction's amount by passing a block From 9036eedb39954749c013b03fe34405d2f31ccc34 Mon Sep 17 00:00:00 2001 From: weshatheleopard Date: Tue, 19 Jan 2016 19:14:16 +0000 Subject: [PATCH 07/11] Update another test. This is not critical, but let's be consistent --- test/test_cashflows.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/test_cashflows.rb b/test/test_cashflows.rb index 8ae1a35..2f29638 100644 --- a/test/test_cashflows.rb +++ b/test/test_cashflows.rb @@ -32,9 +32,9 @@ it "should properly calculate IRR when Complex numbers arise from calculations" do # Note that times must be in UTC (+00:00) here, so # the local system's DST settings don't screw us up. - arr = [ Finance::Transaction.new( 70, :date => Time.new(2015,7,31,0,0,0,'+00:00')), - Finance::Transaction.new(-90, :date => Time.new(2021,1,13,0,0,0,'+00:00')), - Finance::Transaction.new(-20, :date => Time.new(2021,3,31,0,0,0,'+00:00')) ] + arr = [ Finance::Transaction.new( 70, :date => Time.new(2015, 7, 31, 0, 0, 0,'+00:00')), + Finance::Transaction.new(-90, :date => Time.new(2021, 1, 13, 0, 0, 0,'+00:00')), + Finance::Transaction.new(-20, :date => Time.new(2021, 3, 31, 0, 0, 0,'+00:00')) ] assert_equal D("0.085677"), arr.xirr.effective.round(6) end @@ -42,9 +42,9 @@ prev_max = 99999 (1..30).each do |n| - arr = [ Finance::Transaction.new(100, :date => Time.new(2001,1,1)), - Finance::Transaction.new(-50, :date => Time.new(2001,2,1)), - Finance::Transaction.new(-60, :date => Time.new(2001,2,1) + n * 30*24*3600) ] + arr = [ Finance::Transaction.new(100, :date => Time.new(2001, 1, 1, 0, 0, 0,'+00:00')), + Finance::Transaction.new(-50, :date => Time.new(2001, 2, 1, 0, 0, 0,'+00:00')), + Finance::Transaction.new(-60, :date => Time.new(2001, 2, 1, 0, 0, 0,'+00:00') + (n * 30*24*3600)) ] irr = arr.xirr.effective.round(6) From 1b09c596bad09617e1878f0079c778a289076521 Mon Sep 17 00:00:00 2001 From: weshatheleopard Date: Tue, 19 Jan 2016 19:15:22 +0000 Subject: [PATCH 08/11] CONISTENT, I said :) --- test/test_cashflows.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/test_cashflows.rb b/test/test_cashflows.rb index 2f29638..f75dd83 100644 --- a/test/test_cashflows.rb +++ b/test/test_cashflows.rb @@ -32,9 +32,9 @@ it "should properly calculate IRR when Complex numbers arise from calculations" do # Note that times must be in UTC (+00:00) here, so # the local system's DST settings don't screw us up. - arr = [ Finance::Transaction.new( 70, :date => Time.new(2015, 7, 31, 0, 0, 0,'+00:00')), - Finance::Transaction.new(-90, :date => Time.new(2021, 1, 13, 0, 0, 0,'+00:00')), - Finance::Transaction.new(-20, :date => Time.new(2021, 3, 31, 0, 0, 0,'+00:00')) ] + arr = [ Finance::Transaction.new( 70, :date => Time.new(2015, 7, 31, 0, 0, 0, '+00:00')), + Finance::Transaction.new(-90, :date => Time.new(2021, 1, 13, 0, 0, 0, '+00:00')), + Finance::Transaction.new(-20, :date => Time.new(2021, 3, 31, 0, 0, 0, '+00:00')) ] assert_equal D("0.085677"), arr.xirr.effective.round(6) end @@ -42,9 +42,9 @@ prev_max = 99999 (1..30).each do |n| - arr = [ Finance::Transaction.new(100, :date => Time.new(2001, 1, 1, 0, 0, 0,'+00:00')), - Finance::Transaction.new(-50, :date => Time.new(2001, 2, 1, 0, 0, 0,'+00:00')), - Finance::Transaction.new(-60, :date => Time.new(2001, 2, 1, 0, 0, 0,'+00:00') + (n * 30*24*3600)) ] + arr = [ Finance::Transaction.new(100, :date => Time.new(2001, 1, 1, 0, 0, 0, '+00:00')), + Finance::Transaction.new(-50, :date => Time.new(2001, 2, 1, 0, 0, 0, '+00:00')), + Finance::Transaction.new(-60, :date => Time.new(2001, 2, 1, 0, 0, 0, '+00:00') + (n * 30*24*3600)) ] irr = arr.xirr.effective.round(6) From f769d5fe184561fe07a0a4db806c6c0bc86cbb06 Mon Sep 17 00:00:00 2001 From: weshatheleopard Date: Wed, 6 Jun 2018 23:24:26 +0000 Subject: [PATCH 09/11] Remove deprecated parameter --- finance.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/finance.gemspec b/finance.gemspec index 637eac4..4f5a7c7 100644 --- a/finance.gemspec +++ b/finance.gemspec @@ -16,6 +16,5 @@ SPEC = Gem::Specification.new do |s| s.add_development_dependency 'pry' s.files = `git ls-files`.split("\n") - s.has_rdoc = true s.extra_rdoc_files = ['README.md', 'COPYING', 'COPYING.LESSER', 'HISTORY'] end From f06c6678610aca686494589e5657ff227fd28b5a Mon Sep 17 00:00:00 2001 From: weshatheleopard Date: Tue, 27 Nov 2018 20:21:09 +0000 Subject: [PATCH 10/11] Remove rdoc deprecation --- finance.gemspec | 2 -- 1 file changed, 2 deletions(-) diff --git a/finance.gemspec b/finance.gemspec index 4f5a7c7..3c2a7c3 100644 --- a/finance.gemspec +++ b/finance.gemspec @@ -15,6 +15,4 @@ SPEC = Gem::Specification.new do |s| s.add_development_dependency 'activesupport', '>= 4.0.0' s.add_development_dependency 'pry' s.files = `git ls-files`.split("\n") - - s.extra_rdoc_files = ['README.md', 'COPYING', 'COPYING.LESSER', 'HISTORY'] end From 28cb9daf231e60113d443117fe0cfd8f5285487e Mon Sep 17 00:00:00 2001 From: weshatheleopard Date: Mon, 17 Dec 2018 21:22:35 +0000 Subject: [PATCH 11/11] Fix deprecations --- lib/finance/cashflows.rb | 4 ++-- lib/finance/decimal.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/finance/cashflows.rb b/lib/finance/cashflows.rb index d2b8be7..4c80289 100644 --- a/lib/finance/cashflows.rb +++ b/lib/finance/cashflows.rb @@ -22,7 +22,7 @@ class Function values.each do |key, value| define_method key do - BigDecimal.new value + Kernel.BigDecimal(value) end end @@ -44,7 +44,7 @@ def values(x) value_direction = (value.real <=>0) value_direction = (value.imaginary <=> 0) if value_direction == 0 - [ BigDecimal.new((value.magnitude * value_direction).to_s) ] + [ Kernel.BigDecimal((value.magnitude * value_direction).to_s) ] end end diff --git a/lib/finance/decimal.rb b/lib/finance/decimal.rb index 28a72d4..cd8e769 100644 --- a/lib/finance/decimal.rb +++ b/lib/finance/decimal.rb @@ -7,7 +7,7 @@ end DecNum.context.define_conversion_to(BigDecimal) do |x| - BigDecimal.new(x.to_s) + Kernel.BigDecimal(x.to_s) end class Numeric