diff --git a/finance.gemspec b/finance.gemspec index 637eac4..3c2a7c3 100644 --- a/finance.gemspec +++ b/finance.gemspec @@ -15,7 +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.has_rdoc = true - s.extra_rdoc_files = ['README.md', 'COPYING', 'COPYING.LESSER', 'HISTORY'] end diff --git a/lib/finance/cashflows.rb b/lib/finance/cashflows.rb index 985ca36..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 @@ -33,7 +33,18 @@ 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. + # 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). + # 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 + + [ 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 diff --git a/lib/finance/transaction.rb b/lib/finance/transaction.rb index 0ca6e76..af6bb94 100644 --- a/lib/finance/transaction.rb +++ b/lib/finance/transaction.rb @@ -71,7 +71,7 @@ def interest? # @api public def inspect - "Transaction(#{@amount})" + "Transaction(#{@amount}#{date && date.strftime(' <%F>')})" end # Modify a Transaction's amount by passing a block diff --git a/test/test_cashflows.rb b/test/test_cashflows.rb index 835cc8c..f75dd83 100644 --- a/test/test_cashflows.rb +++ b/test/test_cashflows.rb @@ -28,5 +28,32 @@ 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 + # 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 + + 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, 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) + + 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