xirr 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 40bfdec7ad5bfb2ae2cb80594b933c9c75d2053a
4
- data.tar.gz: 196370852aa56efc624cccb7be975104722b33e9
3
+ metadata.gz: ac4ff922b29d910ccb51a3238c6602d0309bc685
4
+ data.tar.gz: 912024facef6c25c236f99f8e86fa0ee75efa496
5
5
  SHA512:
6
- metadata.gz: 2af18cc9ed8157e699c2eb37eeffcee906b89c499265e552d373e0f1eba654a1cfcc236939766d34cdb42fba98dabab451e6a6b78f5225bbacd4c541fdcf8ff1
7
- data.tar.gz: f2bcc8a7a251626d1d4c34cbd433be6e2d9de0d49c614cc55963836211987651f6638638f26307051722141e8401daaa758238f5827b3165b009981e247613b8
6
+ metadata.gz: 68610190f330d76c57f1b638d92210002400a4011387816b1cb369c502055a7e2922545cfd2dfc997abcd0c560b0db4e48e4248342d0a09ee1eb6206b7eb8be3
7
+ data.tar.gz: b31417b22db2501bf5547610340c38031d8d83d372832335196d286ec51893968cb62e94f03b73cb61dca813e469bca4310e671fd5e51bf7967a7347cceece36
@@ -3,6 +3,8 @@ require 'active_support/configurable'
3
3
  require 'xirr/config'
4
4
  require 'xirr/main'
5
5
 
6
+ # @abstract adds a {Xirr::Cashflow} and {Xirr::Transaction} classes to calculate IRR of irregular transactions.
7
+ # Calculates Xirr
6
8
  module Xirr
7
9
 
8
10
  autoload :Transaction, 'xirr/transaction'
@@ -1,14 +1,25 @@
1
1
  module Xirr
2
+
3
+ # Expands [Array] to store a set of transactions which will be used to calculate the XIRR
4
+ # @note A Cashflow should consist of at least two transactions, one positive and one negative.
2
5
  class Cashflow < Array
3
6
  include Xirr::Main
4
7
 
8
+ # @api public
9
+ # @param args [Transaction]
10
+ # @example Creating a Cashflow
11
+ # cf = Cashflow.new
12
+ # cf << Transaction.new( 1000, date: '2013-01-01'.to_time(:utc))
13
+ # cf << Transaction.new(-1234, date: '2013-03-31'.to_time(:utc))
14
+ # Or
15
+ # cf = Cashflow.new Transaction.new( 1000, date: '2013-01-01'.to_time(:utc)), Transaction.new(-1234, date: '2013-03-31'.to_time(:utc))
5
16
  def initialize(*args) # :nodoc:
6
17
  args.each { |a| self << a }
7
18
  self.flatten!
8
19
  end
9
20
 
10
21
  # Check if Cashflow is invalid and raises ArgumentError
11
- # retuns [Boolean]
22
+ # @return [Boolean]
12
23
  def invalid?
13
24
  if positives.empty? || negatives.empty?
14
25
  raise ArgumentError, invalid_message
@@ -18,64 +29,96 @@ module Xirr
18
29
  end
19
30
 
20
31
  # Inverse of #invalid?
21
- # returns [Boolean]
32
+ # @return [Boolean]
22
33
  def valid?
23
34
  !invalid?
24
35
  end
25
36
 
37
+ # @return [Float]
38
+ # Sums all amounts in a cashflow
39
+ def sum # :nodoc:
40
+ self.map(&:amount).sum
41
+ end
26
42
 
27
- # calculates a simple IRR guess based on period of investment and multiples
28
- # returns [Float]
29
- def irr_guess
30
- ((multiple ** (1 / years_of_investment)) - 1).round(3)
43
+ # Last investment date
44
+ # @return [Time]
45
+ def max_date # :nodoc:
46
+ @max_date ||= self.map(&:date).max
31
47
  end
32
48
 
33
- def sum # :nodoc:
34
- self.map(&:amount).sum
49
+ # Calculates a simple IRR guess based on period of investment and multiples.
50
+ # @return [Float]
51
+ def irr_guess
52
+ ((multiple ** (1 / years_of_investment)) - 1).round(3)
35
53
  end
36
54
 
37
55
  private
38
56
 
39
- def first_transaction_direction # :nodoc:
40
- self.sort! { |x,y| x.date <=> y.date }
57
+ # @api private
58
+ # Sorts the {Cashflow} by date ascending
59
+ # and finds the signal of the first transaction.
60
+ # This implies the first transaction is a disembursement
61
+ # @return [Integer]
62
+ def first_transaction_direction
63
+ self.sort! { |x, y| x.date <=> y.date }
41
64
  self.first.amount / self.first.amount.abs
42
65
  end
43
66
 
44
- # Based on the direction of the first investment we create the multiple
45
- def multiple # :nodoc:
46
- if first_transaction_direction > 0
47
- positives.sum(&:amount) / -negatives.sum(&:amount)
48
- else
49
- -negatives.sum(&:amount) / positives.sum(&:amount)
50
- end
67
+ # Based on the direction of the first investment finds the multiple cash-on-cash
68
+ # @example
69
+ # [100,100,-300] and [-100,-100,300] returns 1.5
70
+ # @api private
71
+ # @return [Float]
72
+ def multiple # :nodoc:
73
+ result = positives.sum(&:amount) / -negatives.sum(&:amount)
74
+ first_transaction_direction > 0 ? result : 1 / result
51
75
  end
52
76
 
77
+ # @api private
78
+ # Counts how many years from first to last transaction in the cashflow
79
+ # @return
53
80
  def years_of_investment # :nodoc:
54
81
  (max_date - min_date) / (365 * 24 * 60 * 60).to_f
55
82
  end
56
83
 
57
- def max_date # :nodoc:
58
- @max_date ||= self.map(&:date).max
59
- end
60
-
84
+ # @api private
85
+ # First investment date
86
+ # @return [Time]
61
87
  def min_date # :nodoc:
62
88
  @min_date ||= self.map(&:date).min
63
89
  end
64
90
 
91
+ # @api private
92
+ # @return [Array]
93
+ # @see #negatives
94
+ # @see #split_transactions
95
+ # Finds all transactions income from Cashflow
65
96
  def positives # :nodoc:
66
97
  split_transactions
67
98
  @positives
68
99
  end
69
100
 
101
+ # @api private
102
+ # @return [Array]
103
+ # @see #positives
104
+ # @see #split_transactions
105
+ # Finds all transactions investments from Cashflow
70
106
  def negatives # :nodoc:
71
107
  split_transactions
72
108
  @negatives
73
109
  end
74
110
 
111
+ # @api private
112
+ # @see #positives
113
+ # @see #negatives
114
+ # Uses partition to separate the investment transactions Negatives and the income transactions (Positives)
75
115
  def split_transactions # :nodoc:
76
116
  @negatives, @positives = self.partition { |x| x.amount >= 0 } # Inverted as negative amount is good
77
117
  end
78
118
 
119
+ # @api private
120
+ # @return [String]
121
+ # Error message depending on the missing transaction
79
122
  def invalid_message # :nodoc:
80
123
  return 'No positive transaction' if positives.empty?
81
124
  return 'No negatives transaction' if negatives.empty?
@@ -1,11 +1,13 @@
1
1
  module Xirr
2
2
  include ActiveSupport::Configurable
3
3
 
4
+ # Default values
4
5
  default_values = {
5
6
  eps: '1.0e-12',
6
7
  days_in_year: 365,
7
8
  }
8
9
 
10
+ # Iterates trhough default values and sets in config
9
11
  default_values.each do |key, value|
10
12
  self.config.send("#{key.to_sym}=", value)
11
13
  end
@@ -2,70 +2,77 @@ require 'active_support/concern'
2
2
 
3
3
  module Xirr
4
4
 
5
+ # Methods that will be included in Cashflow to calculate XIRR
5
6
  module Main
6
7
  extend ActiveSupport::Concern
7
8
 
8
9
  # Calculates yearly Internal Rate of Return
9
- # returns [Float]
10
+ # @return [Float]
11
+ # @param guess [Float] an initial guess rate that will override the {Cashflow#irr_guess}
10
12
  def xirr(guess = nil)
11
13
 
14
+ # Raises error if Cashflow is not valid
12
15
  self.valid?
13
16
 
14
17
  # Bisection method finding the rate to zero nfv
15
18
 
19
+ # Initial values
16
20
  days_in_year = Xirr.config.days_in_year.to_f
17
-
18
21
  left = -0.99/days_in_year
19
22
  right = 9.99/days_in_year
20
23
  epsilon = Xirr.config.eps.to_f
21
-
22
24
  guess = self.irr_guess.to_f
23
25
 
26
+ # Loops until difference is within error margin
24
27
  while ((right-left).abs > 2 * epsilon) do
25
28
 
26
29
  midpoint = guess || (right + left)/2
27
30
  guess = nil
31
+ nfv_positive?(left, midpoint) ? left = midpoint : right = midpoint
28
32
 
29
- if (nfv(left) * nfv(midpoint) > 0)
30
-
31
- left = midpoint
33
+ end
32
34
 
33
- else
35
+ return format_irr(left, right)
34
36
 
35
- right = midpoint
37
+ end
36
38
 
37
- end
39
+ private
38
40
 
39
- end
41
+ # @param left [Float]
42
+ # @param midpoint [Float]
43
+ # @return [Bolean]
44
+ # Returns true if result is to the right ot the range
45
+ def nfv_positive?(left, midpoint)
46
+ (nfv(left) * nfv(midpoint) > 0)
47
+ end
40
48
 
49
+ # @param left [Float]
50
+ # @param right [Float]
51
+ # @return [Float] IRR of the Cashflow
52
+ def format_irr(left, right)
53
+ days_in_year = Xirr.config.days_in_year.to_f
41
54
  # Irr for daily cashflow (not in percentage format)
42
55
  irr = (right+left) / 2
43
56
  # Irr for daily cashflow multiplied by 365 to get yearly return
44
57
  irr = irr * days_in_year
45
58
  # Annualized yield (return) reflecting compounding effect of daily returns
46
59
  irr = (1 + irr / days_in_year) ** days_in_year - 1
47
-
48
- return irr
49
-
50
60
  end
51
61
 
52
- private
53
-
62
+ # Returns the Net future value of the flow given a Rate
63
+ # @param rate [Float]
64
+ # @return [Float]
54
65
  def nfv(rate) # :nodoc:
55
-
56
- today = self.map(&:date).max.to_date
57
- nfv = 0
58
- self.each do |t|
59
- cf, date = t.amount, t.date
60
-
61
- datestring = date.to_s
62
- formatteddate = Date.parse(datestring).to_date
63
- t_in_days = (today - formatteddate).numerator / (today - formatteddate).denominator
64
- nfv = nfv + cf * ((1 + rate) ** t_in_days)
65
-
66
+ self.inject(0) do |nfv,t|
67
+ nfv = nfv + t.amount * ((1 + rate) ** t_in_days(t.date))
66
68
  end
67
- return nfv
69
+ end
68
70
 
71
+ # Calculates days until last transaction
72
+ # @return [Rational]
73
+ # @param date [Time]
74
+ def t_in_days(date)
75
+ Date.parse(max_date.to_s) - Date.parse(date.to_s)
69
76
  end
70
77
 
71
78
  end
@@ -1,12 +1,14 @@
1
1
  module Xirr
2
2
 
3
+ # A unit of the Cashflow.
3
4
  class Transaction
4
5
  attr_reader :amount
5
6
  attr_accessor :date
6
7
 
8
+ # @example
9
+ # Transaction.new -1000, date: Time.now
7
10
  def initialize(amount, opts={})
8
11
  @amount = amount
9
- @original = amount
10
12
 
11
13
  # Set optional attributes..
12
14
  opts.each do |key, value|
@@ -14,30 +16,18 @@ module Xirr
14
16
  end
15
17
  end
16
18
 
19
+ # Sets the amount
20
+ # @param value [Float, Integer]
21
+ # @return [Float]
17
22
  def amount=(value)
18
23
  @amount = value.to_f || 0
19
24
  end
20
25
 
26
+ # @return [String]
21
27
  def inspect
22
28
  "T(#{@amount},#{@date})"
23
29
  end
24
30
 
25
- def description
26
- investment ? "#{self.investment.transaction_type_name}: #{round.description}" : @description
27
- end
28
-
29
- def round
30
- @investment.nil? ? nil : investment.round
31
- end
32
-
33
- def company
34
- @company || investment.company
35
- end
36
-
37
- def shareholder
38
- @shareholder || investment.shareholder
39
- end
40
-
41
31
  end
42
32
 
43
33
  end
@@ -1,3 +1,4 @@
1
1
  module Xirr
2
- VERSION = "0.1.0"
2
+ # Version of the Gem
3
+ VERSION = "0.1.1"
3
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xirr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - tubedude