finance 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/HISTORY CHANGED
@@ -1,3 +1,10 @@
1
+ = Version 1.1.0
2
+ 11 Sep 2011
3
+
4
+ * Added XNPV and XIRR functions, with basic testing.
5
+ * Bugfix: Array#sum no longer collides with the Array#sum defined in Rails.
6
+ * Bugfix: Numeric#amortize now correctly calls Finance::Amortization#new.
7
+
1
8
  = Version 1.0.0
2
9
  20 Jul 2011
3
10
 
@@ -191,6 +191,6 @@ class Numeric
191
191
  # @see Amortization#new
192
192
  # @api public
193
193
  def amortize(*rates, &block)
194
- amortization = Amortization.new(self, *rates, &block)
194
+ amortization = Finance::Amortization.new(self, *rates, &block)
195
195
  end
196
196
  end
@@ -1,24 +1,58 @@
1
1
  require_relative 'decimal'
2
+ require_relative 'rates'
3
+
4
+ require 'bigdecimal'
5
+ require 'bigdecimal/newton'
6
+ include Newton
2
7
 
3
8
  module Finance
4
9
  # Provides methods for working with cash flows (collections of transactions)
5
10
  # @api public
6
11
  module Cashflow
12
+ # Base class for working with Newton's Method.
13
+ # @api private
14
+ class Function
15
+ values = {
16
+ eps: "1.0e-16",
17
+ one: "1.0",
18
+ two: "2.0",
19
+ ten: "10.0",
20
+ zero: "0.0"
21
+ }
22
+
23
+ values.each do |key, value|
24
+ define_method key do
25
+ BigDecimal.new value
26
+ end
27
+ end
28
+
29
+ def initialize(transactions, function)
30
+ @transactions = transactions
31
+ @function = function
32
+ end
33
+
34
+ def values(x)
35
+ value = @transactions.send(@function, x[0].to_d)
36
+ [ BigDecimal.new(value.to_s) ]
37
+ end
38
+ end
39
+
7
40
  # calculate the internal rate of return for a sequence of cash flows
8
41
  # @return [DecNum] the internal rate of return
9
42
  # @example
10
43
  # [-4000,1200,1410,1875,1050].irr #=> 0.143
11
44
  # @see http://en.wikipedia.org/wiki/Internal_rate_of_return
12
45
  # @api public
13
- def irr(iterations=100)
14
- self.collect! { |entry| entry.to_d }
46
+ def irr
47
+ func = Function.new(self, :npv)
48
+ rate = [ func.one ]
49
+ n = nlsolve( func, rate )
50
+ rate[0]
51
+ end
15
52
 
16
- rate, investment = 1.to_d, self[0]
17
- iterations.times do
18
- rate *= (1 - self.npv(rate) / investment)
19
- end
20
-
21
- rate
53
+ def method_missing(name, *args, &block)
54
+ return self.inject(:+) if name.to_s == "sum"
55
+ super
22
56
  end
23
57
 
24
58
  # calculate the net present value of a sequence of cash flows
@@ -39,13 +73,39 @@ module Finance
39
73
  total
40
74
  end
41
75
 
42
- # @return [Numeric] the total value of a sequence of cash flows
76
+ # calculate the internal rate of return for a sequence of cash flows with dates
77
+ # @return [Rate] the internal rate of return
78
+ # @example
79
+ # @transactions = []
80
+ # @transactions << Transaction.new(-1000, :date => Time.new(1985,01,01))
81
+ # @transactions << Transaction.new( 600, :date => Time.new(1990,01,01))
82
+ # @transactions << Transaction.new( 600, :date => Time.new(1995,01,01))
83
+ # @transactions.xirr(0.6).round(2) #=> Rate("0.024851", :apr, :compounds => :annually)
43
84
  # @api public
44
- def sum
45
- self.inject(:+)
85
+ def xirr(iterations=100)
86
+ func = Function.new(self, :xnpv)
87
+ rate = [ func.one ]
88
+ n = nlsolve( func, rate )
89
+ Rate.new(rate[0], :apr, :compounds => :annually)
46
90
  end
47
91
 
48
- def xirr
92
+ # calculate the net present value of a sequence of cash flows
93
+ # @return [DecNum]
94
+ # @example
95
+ # @transactions = []
96
+ # @transactions << Transaction.new(-1000, :date => Time.new(1985,01,01))
97
+ # @transactions << Transaction.new( 600, :date => Time.new(1990,01,01))
98
+ # @transactions << Transaction.new( 600, :date => Time.new(1995,01,01))
99
+ # @transactions.xnpv(0.6).round(2) #=> -937.41
100
+ # @api public
101
+ def xnpv(rate)
102
+ rate = rate.to_d
103
+ start = self[0].date
104
+
105
+ self.inject(0) do |sum, t|
106
+ n = t.amount / ( (1 + rate) ** ((t.date-start) / 31536000.to_d)) # 365 * 86400
107
+ sum + n
108
+ end
49
109
  end
50
110
  end
51
111
  end
@@ -1,12 +1,21 @@
1
1
  require 'rubygems'
2
2
  require 'flt'
3
+ include Flt
4
+
5
+ DecNum.context.define_conversion_from(BigDecimal) do |x, context|
6
+ DecNum(x.to_s)
7
+ end
8
+
9
+ DecNum.context.define_conversion_to(BigDecimal) do |x|
10
+ BigDecimal.new(x.to_s)
11
+ end
3
12
 
4
13
  class Numeric
5
14
  def to_d
6
- if self.instance_of? Flt::DecNum
15
+ if self.instance_of? DecNum
7
16
  self
8
17
  else
9
- Flt::DecNum self.to_s
18
+ DecNum self.to_s
10
19
  end
11
20
  end
12
21
  end
@@ -0,0 +1,25 @@
1
+ require_relative 'cashflows'
2
+ require_relative 'transaction'
3
+
4
+ require 'time'
5
+ include Finance
6
+
7
+ trans = [
8
+ Transaction.new(-1000, :date => Time.parse("2011-01-01")),
9
+ Transaction.new(100, :date => Time.parse("2011-02-11")),
10
+ Transaction.new(100, :date => Time.parse("2011-03-11")),
11
+ Transaction.new(100, :date => Time.parse("2011-04-11")),
12
+ Transaction.new(100, :date => Time.parse("2011-05-11")),
13
+ Transaction.new(100, :date => Time.parse("2011-06-11")),
14
+ Transaction.new(100, :date => Time.parse("2011-07-11")),
15
+ Transaction.new(100, :date => Time.parse("2011-08-11")),
16
+ Transaction.new(100, :date => Time.parse("2011-09-11")),
17
+ Transaction.new(100, :date => Time.parse("2011-10-11")),
18
+ Transaction.new(100, :date => Time.parse("2011-11-11")),
19
+ Transaction.new(100, :date => Time.parse("2011-12-11")),
20
+ Transaction.new(100, :date => Time.parse("2012-01-11"))
21
+ # Transaction.new(-100, :date => Time.parse("2011-01-11")),
22
+ # Transaction.new(150, :date => Time.parse("2012-01-11"))
23
+ ]
24
+
25
+ puts trans.xirr.effective.round(9)
@@ -11,6 +11,9 @@ module Finance
11
11
  # @note this attribute is mainly used in the case of mortgage amortization with no dates
12
12
  # @api public
13
13
  attr_accessor :period
14
+ # @return [Date] the date of the transaction
15
+ # @api public
16
+ attr_accessor :date
14
17
 
15
18
  # Set the cash value of the transaction
16
19
  # @return None
@@ -1,11 +1,12 @@
1
1
  require_relative '../lib/finance/cashflows.rb'
2
+ require_relative '../lib/finance/rates.rb'
2
3
 
3
4
  require 'flt/d'
4
5
  require 'minitest/unit'
5
6
  require 'shoulda'
6
7
 
7
8
  class TestCashflows < Test::Unit::TestCase
8
- context "an array of cashflows" do
9
+ context "an array of numeric cashflows" do
9
10
  should "have an Internal Rate of Return" do
10
11
  assert_equal D("0.143"), [-4000,1200,1410,1875,1050].irr.round(3)
11
12
  end
@@ -13,4 +14,20 @@ class TestCashflows < Test::Unit::TestCase
13
14
  assert_equal D("49.211"), [-100.0, 60, 60, 60].npv(0.1).round(3)
14
15
  end
15
16
  end
17
+ context "an array of Transactions" do
18
+ setup do
19
+ @xactions=[]
20
+ @xactions << Transaction.new(-1000, :date => Time.new(1985,01,01))
21
+ @xactions << Transaction.new( 600, :date => Time.new(1990,01,01))
22
+ @xactions << Transaction.new( 600, :date => Time.new(1995,01,01))
23
+ end
24
+
25
+ should "have an Internal Rate of Return" do
26
+ assert_equal D("0.024851"), @xactions.xirr.effective.round(6)
27
+ end
28
+
29
+ should "have a Net Present Value" do
30
+ assert_equal D("-937.41"), @xactions.xnpv(0.6).round(2)
31
+ end
32
+ end
16
33
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: finance
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 1
8
+ - 1
8
9
  - 0
9
- - 0
10
- version: 1.0.0
10
+ version: 1.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Bill Kranec
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-07-20 00:00:00 Z
18
+ date: 2011-09-11 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: flt
@@ -52,6 +52,7 @@ files:
52
52
  - lib/finance/amortization.rb
53
53
  - lib/finance/cashflows.rb
54
54
  - lib/finance/decimal.rb
55
+ - lib/finance/gistfile1.rb
55
56
  - lib/finance/interval.rb
56
57
  - lib/finance/rates.rb
57
58
  - lib/finance/transaction.rb