Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing Issue #38 #39

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions finance.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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
15 changes: 13 additions & 2 deletions lib/finance/cashflows.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Function

values.each do |key, value|
define_method key do
BigDecimal.new value
Kernel.BigDecimal(value)
end
end

Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion lib/finance/decimal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion lib/finance/transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions test/test_cashflows.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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