xirr 0.6.1 → 0.7.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 +4 -4
- data/.ruby-version +1 -1
- data/.travis.yml +4 -6
- data/CHANGE_LOG.md +12 -0
- data/README.md +16 -3
- data/lib/xirr/base.rb +3 -10
- data/lib/xirr/bisection.rb +8 -12
- data/lib/xirr/cashflow.rb +30 -29
- data/lib/xirr/config.rb +9 -9
- data/lib/xirr/newton_method.rb +9 -8
- data/lib/xirr/transaction.rb +3 -4
- data/lib/xirr/version.rb +1 -1
- data/test/test_cashflow.rb +104 -2
- data/test/test_helper.rb +3 -26
- data/xirr.gemspec +8 -8
- metadata +33 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1281b23c8069cbd5bf16cecf50ae129ea2d4bab31c4c5e89906d970f6e15b7d
|
4
|
+
data.tar.gz: 2431eed14a1de0deb4694621eb8136b10b53c6ce44d888682101e9ca04e46aca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6e38c8051ab43168cfafb1442fc897a96e9e25c62b55eb95320cc8f22fca57421ac360626ade4764115c17a4c7aa0fca84568f8d03238d78bc5bb3a123039cf
|
7
|
+
data.tar.gz: 4b450e70ef5d58150e0b9cf416f9246e67b8fa015449f6697042dafe49b49e9114065bc6f711dafa41c5e2bcbff7882ee87d2a77ae55046c89c55aa7d81f6b90
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.3.1
|
data/.travis.yml
CHANGED
data/CHANGE_LOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## Unreleased
|
2
|
+
* Fix removal of `RubyInLine`
|
3
|
+
* Allow activesupport 7. Fixes #30
|
4
|
+
* Breaking: Require activesupport >= 6.1
|
5
|
+
* Breaking: Require ruby >= 3.1
|
6
|
+
|
7
|
+
## Version 0.7.0
|
8
|
+
* Removed `RubyInLine`
|
9
|
+
* Removed possibility to return false from `irr_guess`
|
10
|
+
* Removed global newton module
|
11
|
+
* Bumped dependencies
|
12
|
+
|
1
13
|
## Version 0.5.4
|
2
14
|
* Fallsback If Newton Methods returns NaN
|
3
15
|
|
data/README.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
|
+
[](https://travis-ci.org/tubedude/xirr)[](https://coveralls.io/r/tubedude/xirr?branch=master)[](https://codeclimate.com/github/tubedude/xirr)
|
2
|
+
|
3
|
+
## NOTE
|
4
|
+
|
5
|
+
This gem is not very well maintained. You can check out a new and properlly maintained library for fast XIRR calculation:
|
6
|
+
https://github.com/fintual-oss/fast-xirr
|
7
|
+
|
8
|
+
I haven't tried it yet, but I liked two things:
|
9
|
+
- All calculations are underlying C ( = Fast )
|
10
|
+
- Bad results return NaN ( = you can decide how to deal with it )
|
11
|
+
|
1
12
|
# Xirr
|
2
|
-
[](https://travis-ci.org/tubedude/xirr)[](https://coveralls.io/r/tubedude/xirr?branch=master)[](https://codeclimate.com/github/tubedude/xirr)[](https://ebertapp.io/github/tubedude/xirr)
|
3
13
|
|
4
14
|
This is a gem to calculate XIRR on Bisection Method or Newton Method.
|
5
15
|
|
@@ -21,7 +31,7 @@ Or install it yourself as:
|
|
21
31
|
|
22
32
|
```rb
|
23
33
|
include Xirr
|
24
|
-
|
34
|
+
|
25
35
|
cf = Xirr::Cashflow.new
|
26
36
|
cf << Xirr::Transaction.new(-1000, date: '2014-01-01'.to_date)
|
27
37
|
cf << Xirr::Transaction.new(-2000, date: '2014-03-01'.to_date)
|
@@ -41,7 +51,7 @@ cf.xirr
|
|
41
51
|
## Configuration
|
42
52
|
|
43
53
|
# intializer/xirr.rb
|
44
|
-
|
54
|
+
|
45
55
|
Xirr.configure do |config|
|
46
56
|
config.eps = '1.0e-12'
|
47
57
|
config.days_in_year = 365.25
|
@@ -58,10 +68,13 @@ http://rubydoc.info/github/tubedude/xirr/master/frames
|
|
58
68
|
- 2.2.1
|
59
69
|
- 2.3
|
60
70
|
- 2.4
|
71
|
+
- 2.7
|
61
72
|
|
62
73
|
ActiveSupport:
|
63
74
|
- 4.2
|
64
75
|
- 5
|
76
|
+
- 6
|
77
|
+
- 7
|
65
78
|
|
66
79
|
## Thanks
|
67
80
|
|
data/lib/xirr/base.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Xirr
|
3
4
|
# Base module for XIRR calculation Methods
|
4
5
|
module Base
|
5
6
|
extend ActiveSupport::Concern
|
6
|
-
require 'inline'
|
7
7
|
attr_reader :cf
|
8
8
|
|
9
9
|
# @param cf [Cashflow]
|
@@ -24,16 +24,9 @@ module Xirr
|
|
24
24
|
# @return [BigDecimal]
|
25
25
|
def xnpv(rate)
|
26
26
|
cf.inject(0) do |sum, t|
|
27
|
-
sum + (xnpv_c rate, t.amount, periods_from_start(t.date))
|
27
|
+
# sum + (xnpv_c rate, t.amount, periods_from_start(t.date))
|
28
|
+
sum + t.amount / (1+rate.to_f) ** periods_from_start(t.date)
|
28
29
|
end
|
29
30
|
end
|
30
|
-
|
31
|
-
inline { |builder|
|
32
|
-
builder.include '<math.h>'
|
33
|
-
builder.c '
|
34
|
-
double xnpv_c(double rate, double amount, double period) {
|
35
|
-
return amount / pow(1 + rate, period);
|
36
|
-
}'
|
37
|
-
}
|
38
31
|
end
|
39
32
|
end
|
data/lib/xirr/bisection.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Xirr
|
3
4
|
# Methods that will be included in Cashflow to calculate XIRR
|
4
5
|
class Bisection
|
@@ -9,27 +10,24 @@ module Xirr
|
|
9
10
|
# @param midpoint [Float]
|
10
11
|
# An initial guess rate will override the {Cashflow#irr_guess}
|
11
12
|
def xirr(midpoint, options)
|
12
|
-
|
13
13
|
# Initial values
|
14
|
-
left = [BigDecimal(-0.99999999, Xirr
|
15
|
-
right = [BigDecimal(9.99999999, Xirr
|
14
|
+
left = [BigDecimal(-0.99999999, Xirr.config.precision), cf.irr_guess].min
|
15
|
+
right = [BigDecimal(9.99999999, Xirr.config.precision), cf.irr_guess + 1].max
|
16
16
|
@original_right = right
|
17
17
|
midpoint ||= cf.irr_guess
|
18
18
|
|
19
19
|
midpoint, runs = loop_rates(left, midpoint, right, options[:iteration_limit])
|
20
20
|
|
21
21
|
get_answer(midpoint, options, runs)
|
22
|
-
|
23
22
|
end
|
24
23
|
|
25
|
-
|
26
24
|
private
|
27
25
|
|
28
26
|
# @param midpoint [BigDecimal]
|
29
27
|
# @return [Boolean]
|
30
28
|
# Checks if result is the right limit.
|
31
29
|
def right_limit_reached?(midpoint)
|
32
|
-
(@original_right - midpoint).abs < Xirr
|
30
|
+
(@original_right - midpoint).abs < Xirr.config.eps
|
33
31
|
end
|
34
32
|
|
35
33
|
# @param left [BigDecimal]
|
@@ -54,24 +52,22 @@ module Xirr
|
|
54
52
|
# @param right [Float]
|
55
53
|
# @return [Float] IRR of the Cashflow
|
56
54
|
def format_irr(left, right)
|
57
|
-
irr = (right+left) / 2
|
55
|
+
irr = (right + left) / 2
|
58
56
|
end
|
59
57
|
|
60
58
|
def get_answer(midpoint, options, runs)
|
61
59
|
if runs >= options[:iteration_limit]
|
62
60
|
if options[:raise_exception]
|
63
61
|
raise ArgumentError, "Did not converge after #{runs} tries."
|
64
|
-
else
|
65
|
-
nil
|
66
62
|
end
|
67
63
|
else
|
68
|
-
midpoint.round Xirr
|
64
|
+
midpoint.round Xirr.config.precision
|
69
65
|
end
|
70
66
|
end
|
71
67
|
|
72
68
|
def loop_rates(left, midpoint, right, iteration_limit)
|
73
69
|
runs = 0
|
74
|
-
while (right - left).abs > Xirr
|
70
|
+
while (right - left).abs > Xirr.config.eps && runs < iteration_limit do
|
75
71
|
runs += 1
|
76
72
|
left, midpoint, right, should_stop = bisection(left, midpoint, right)
|
77
73
|
break if should_stop
|
@@ -80,7 +76,7 @@ module Xirr
|
|
80
76
|
@original_right *= 2
|
81
77
|
end
|
82
78
|
end
|
83
|
-
|
79
|
+
[midpoint, runs]
|
84
80
|
end
|
85
81
|
end
|
86
82
|
end
|
data/lib/xirr/cashflow.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Xirr
|
3
4
|
# Expands [Array] to store a set of transactions which will be used to calculate the XIRR
|
4
5
|
# @note A Cashflow should consist of at least two transactions, one positive and one negative.
|
@@ -12,12 +13,12 @@ module Xirr
|
|
12
13
|
# cf << Transaction.new(-1234, date: '2013-03-31'.to_date)
|
13
14
|
# Or
|
14
15
|
# cf = Cashflow.new Transaction.new( 1000, date: '2013-01-01'.to_date), Transaction.new(-1234, date: '2013-03-31'.to_date)
|
15
|
-
def initialize(flow: [], period: Xirr
|
16
|
+
def initialize(flow: [], period: Xirr.config.period, ** options)
|
16
17
|
@period = period
|
17
|
-
@fallback = options[:fallback] || Xirr
|
18
|
+
@fallback = options[:fallback] || Xirr.config.fallback
|
18
19
|
@options = options
|
19
20
|
self << flow
|
20
|
-
|
21
|
+
flatten!
|
21
22
|
end
|
22
23
|
|
23
24
|
# Check if Cashflow is invalid
|
@@ -35,21 +36,23 @@ module Xirr
|
|
35
36
|
# @return [Float]
|
36
37
|
# Sums all amounts in a cashflow
|
37
38
|
def sum
|
38
|
-
|
39
|
+
map(&:amount).sum
|
39
40
|
end
|
40
41
|
|
41
42
|
# Last investment date
|
42
43
|
# @return [Time]
|
43
44
|
def max_date
|
44
|
-
@max_date ||=
|
45
|
+
@max_date ||= map(&:date).max
|
45
46
|
end
|
46
47
|
|
47
48
|
# Calculates a simple IRR guess based on period of investment and multiples.
|
48
49
|
# @return [Float]
|
49
50
|
def irr_guess
|
50
51
|
return @irr_guess = 0.0 if periods_of_investment.zero?
|
51
|
-
@irr_guess =
|
52
|
-
|
52
|
+
return @irr_guess = 0.0 if multiple <= 0
|
53
|
+
|
54
|
+
@irr_guess = valid? ? ((multiple**(1.0 / periods_of_investment)) - 1).round(3) : 0.0
|
55
|
+
@irr_guess.infinite? ? 0.0 : @irr_guess
|
53
56
|
end
|
54
57
|
|
55
58
|
# @param guess [Float]
|
@@ -59,18 +62,18 @@ module Xirr
|
|
59
62
|
method, options = process_options(method, options)
|
60
63
|
if invalid?
|
61
64
|
raise ArgumentError, invalid_message if options[:raise_exception]
|
62
|
-
BigDecimal(0, Xirr
|
65
|
+
BigDecimal(0, Xirr.config.precision)
|
63
66
|
else
|
64
67
|
xirr = choose_(method).send :xirr, guess, options
|
65
68
|
xirr = choose_(other_calculation_method(method)).send(:xirr, guess, options) if (xirr.nil? || xirr.nan?) && fallback
|
66
|
-
xirr || Xirr
|
69
|
+
xirr || Xirr.config.replace_for_nil
|
67
70
|
end
|
68
71
|
end
|
69
72
|
|
70
73
|
def process_options(method, options)
|
71
74
|
@temporary_period = options[:period]
|
72
|
-
options[:raise_exception] ||= @options[:raise_exception] || Xirr
|
73
|
-
options[:iteration_limit] ||= @options[:iteration_limit] || Xirr
|
75
|
+
options[:raise_exception] ||= @options[:raise_exception] || Xirr.config.raise_exception
|
76
|
+
options[:iteration_limit] ||= @options[:iteration_limit] || Xirr.config.iteration_limit
|
74
77
|
return switch_fallback(method), options
|
75
78
|
end
|
76
79
|
|
@@ -78,13 +81,13 @@ module Xirr
|
|
78
81
|
# it return either the provided method or the system default
|
79
82
|
# @param method [Symbol]
|
80
83
|
# @return [Symbol]
|
81
|
-
def switch_fallback
|
84
|
+
def switch_fallback(method)
|
82
85
|
if method
|
83
86
|
@fallback = false
|
84
87
|
method
|
85
88
|
else
|
86
|
-
@fallback = Xirr
|
87
|
-
Xirr
|
89
|
+
@fallback = Xirr.config.fallback
|
90
|
+
Xirr.config.default_method
|
88
91
|
end
|
89
92
|
end
|
90
93
|
|
@@ -95,14 +98,14 @@ module Xirr
|
|
95
98
|
def compact_cf
|
96
99
|
# self
|
97
100
|
compact = Hash.new 0
|
98
|
-
|
101
|
+
each { |flow| compact[flow.date] += flow.amount }
|
99
102
|
Cashflow.new flow: compact.map { |key, value| Transaction.new(value, date: key) }, options: options, period: period
|
100
103
|
end
|
101
104
|
|
102
105
|
# First investment date
|
103
106
|
# @return [Time]
|
104
107
|
def min_date
|
105
|
-
@min_date ||=
|
108
|
+
@min_date ||= map(&:date).min
|
106
109
|
end
|
107
110
|
|
108
111
|
# @return [String]
|
@@ -116,9 +119,9 @@ module Xirr
|
|
116
119
|
@temporary_period || @period
|
117
120
|
end
|
118
121
|
|
119
|
-
def <<
|
122
|
+
def <<(arg)
|
120
123
|
super arg
|
121
|
-
|
124
|
+
sort! { |x, y| x.date <=> y.date }
|
122
125
|
self
|
123
126
|
end
|
124
127
|
|
@@ -129,12 +132,12 @@ module Xirr
|
|
129
132
|
# @return [Class]
|
130
133
|
def choose_(method)
|
131
134
|
case method
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
135
|
+
when :bisection
|
136
|
+
Bisection.new compact_cf
|
137
|
+
when :newton_method
|
138
|
+
NewtonMethod.new compact_cf
|
139
|
+
else
|
140
|
+
raise ArgumentError, "There is no method called #{method} "
|
138
141
|
end
|
139
142
|
end
|
140
143
|
|
@@ -145,7 +148,7 @@ module Xirr
|
|
145
148
|
# @return [Integer]
|
146
149
|
def first_transaction_direction
|
147
150
|
# self.sort! { |x, y| x.date <=> y.date }
|
148
|
-
@first_transaction_direction ||=
|
151
|
+
@first_transaction_direction ||= first.amount / first.amount.abs
|
149
152
|
end
|
150
153
|
|
151
154
|
# Based on the direction of the first investment finds the multiple cash-on-cash
|
@@ -173,7 +176,7 @@ module Xirr
|
|
173
176
|
# @see #outflows
|
174
177
|
# Selects all positives transactions from Cashflow
|
175
178
|
def inflow
|
176
|
-
|
179
|
+
select { |x| x.amount * first_transaction_direction < 0 }
|
177
180
|
end
|
178
181
|
|
179
182
|
# @api private
|
@@ -181,9 +184,7 @@ module Xirr
|
|
181
184
|
# @see #inflow
|
182
185
|
# Selects all negatives transactions from Cashflow
|
183
186
|
def outflows
|
184
|
-
|
187
|
+
select { |x| x.amount * first_transaction_direction > 0 }
|
185
188
|
end
|
186
|
-
|
187
189
|
end
|
188
|
-
|
189
190
|
end
|
data/lib/xirr/config.rb
CHANGED
@@ -3,15 +3,15 @@ module Xirr
|
|
3
3
|
|
4
4
|
# Sets as constants all the entries in the Hash Default values
|
5
5
|
default_values = {
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
eps: '1.0e-6'.to_f,
|
7
|
+
period: 365.0,
|
8
|
+
iteration_limit: 50,
|
9
|
+
precision: 6,
|
10
|
+
default_method: :newton_method,
|
11
|
+
fallback: true,
|
12
|
+
replace_for_nil: 0.0,
|
13
|
+
compact: true,
|
14
|
+
raise_exception: false
|
15
15
|
}
|
16
16
|
|
17
17
|
# Iterates though default values and sets in config
|
data/lib/xirr/newton_method.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'bigdecimal/newton'
|
3
|
-
include Newton
|
4
3
|
|
5
4
|
module Xirr
|
6
5
|
# Class to calculate IRR using Newton Method
|
7
6
|
class NewtonMethod
|
8
7
|
include Base
|
8
|
+
include Newton
|
9
|
+
|
9
10
|
|
10
11
|
# Base class for working with Newton's Method.
|
11
12
|
# @api private
|
12
13
|
class Function
|
13
14
|
values = {
|
14
|
-
eps: Xirr
|
15
|
+
eps: Xirr.config.eps,
|
15
16
|
one: '1.0',
|
16
17
|
two: '2.0',
|
17
18
|
ten: '10.0',
|
@@ -21,7 +22,7 @@ module Xirr
|
|
21
22
|
# define default values
|
22
23
|
values.each do |key, value|
|
23
24
|
define_method key do
|
24
|
-
BigDecimal(value, Xirr
|
25
|
+
BigDecimal(value, Xirr.config.precision)
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
@@ -36,22 +37,22 @@ module Xirr
|
|
36
37
|
# Necessary for #nlsolve
|
37
38
|
# @param x [BigDecimal]
|
38
39
|
def values(x)
|
39
|
-
value = @transactions.send(@function, BigDecimal(x[0].to_s, Xirr
|
40
|
-
[BigDecimal(value.to_s, Xirr
|
40
|
+
value = @transactions.send(@function, BigDecimal(x[0].to_s, Xirr.config.precision))
|
41
|
+
[BigDecimal(value.to_s, Xirr.config.precision)]
|
41
42
|
end
|
42
43
|
end
|
43
44
|
|
44
45
|
# Calculates XIRR using Newton method
|
45
46
|
# @return [BigDecimal]
|
46
47
|
# @param guess [Float]
|
47
|
-
def xirr
|
48
|
+
def xirr(guess, _options)
|
48
49
|
func = Function.new(self, :xnpv)
|
49
50
|
rate = [guess || cf.irr_guess]
|
50
51
|
begin
|
51
52
|
nlsolve(func, rate)
|
52
|
-
(rate[0] <= -1 || rate[0].nan?) ? nil : rate[0].round(Xirr
|
53
|
+
(rate[0] <= -1 || rate[0].nan?) ? nil : rate[0].round(Xirr.config.precision)
|
53
54
|
|
54
|
-
# rate[0].round(Xirr
|
55
|
+
# rate[0].round(Xirr.config.precision)
|
55
56
|
rescue
|
56
57
|
nil
|
57
58
|
end
|
data/lib/xirr/transaction.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Xirr
|
3
4
|
# A unit of the Cashflow.
|
4
5
|
class Transaction
|
@@ -9,7 +10,7 @@ module Xirr
|
|
9
10
|
# @param amount [Numeric]
|
10
11
|
# @param opts [Hash]
|
11
12
|
# @note Don't forget to add date: [Date] in the opts hash.
|
12
|
-
def initialize(amount, opts={})
|
13
|
+
def initialize(amount, opts = {})
|
13
14
|
self.amount = amount
|
14
15
|
|
15
16
|
# Set optional attributes..
|
@@ -22,7 +23,7 @@ module Xirr
|
|
22
23
|
# @param value [Date, Time]
|
23
24
|
# @return [Date]
|
24
25
|
def date=(value)
|
25
|
-
@date = value.
|
26
|
+
@date = value.is_a?(Time) ? value.to_date : value
|
26
27
|
end
|
27
28
|
|
28
29
|
# Sets the amount
|
@@ -36,7 +37,5 @@ module Xirr
|
|
36
37
|
def inspect
|
37
38
|
"T(#{@amount},#{@date})"
|
38
39
|
end
|
39
|
-
|
40
40
|
end
|
41
|
-
|
42
41
|
end
|
data/lib/xirr/version.rb
CHANGED
data/test/test_cashflow.rb
CHANGED
@@ -84,7 +84,7 @@ describe 'Cashflows' do
|
|
84
84
|
assert_equal '22.352207 '.to_f, @cf.xirr(method: :bisection)
|
85
85
|
end
|
86
86
|
|
87
|
-
it '
|
87
|
+
it 'it won\'t fall back if method provided' do
|
88
88
|
@cf.xirr method: :bisection
|
89
89
|
assert_equal false, @cf.fallback
|
90
90
|
end
|
@@ -265,10 +265,10 @@ describe 'Cashflows' do
|
|
265
265
|
@cf << Transaction.new(84710.65, date: '2013-05-21'.to_date)
|
266
266
|
@cf << Transaction.new(-84710.65, date: '2013-05-21'.to_date)
|
267
267
|
@cf << Transaction.new(-144413.24, date: '2013-05-21'.to_date)
|
268
|
-
|
269
268
|
end
|
270
269
|
|
271
270
|
it 'is a long and bad investment and newton generates an error' do
|
271
|
+
skip 'Test is weirdly taking too long'
|
272
272
|
assert_equal '-1.0'.to_f, @cf.xirr #(method: :newton_method)
|
273
273
|
end
|
274
274
|
end
|
@@ -309,4 +309,106 @@ describe 'Cashflows' do
|
|
309
309
|
assert_equal 0.112339, cf.xirr(period: 365.0)
|
310
310
|
end
|
311
311
|
end
|
312
|
+
|
313
|
+
describe 'with changing precision values' do
|
314
|
+
before(:all) do
|
315
|
+
# {"2021-05-17"=>-3005.69, "2021-06-03"=>-4781.38, "2021-06-17"=>3.09, "2021-06-21"=>8509.93}
|
316
|
+
@cf = Cashflow.new
|
317
|
+
@cf << Transaction.new(-117.38, date: '2021-03-17'.to_date)
|
318
|
+
@cf << Transaction.new(-2370.02, date: '2021-03-23'.to_date)
|
319
|
+
@cf << Transaction.new(0.29, date: '2021-03-26'.to_date)
|
320
|
+
@cf << Transaction.new(0.32, date: '2021-04-01'.to_date)
|
321
|
+
@cf << Transaction.new(-3005.69, date: '2021-05-17'.to_date)
|
322
|
+
@cf << Transaction.new(-4781.38, date: '2021-06-03'.to_date)
|
323
|
+
@cf << Transaction.new(3.09, date: '2021-06-17'.to_date)
|
324
|
+
@cf << Transaction.new(8509.93, date: '2021-06-21'.to_date)
|
325
|
+
end
|
326
|
+
|
327
|
+
it 'gives nil value for xirr' do
|
328
|
+
assert_equal 0.0, @cf.xirr
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'gives correct value with configuration' do
|
332
|
+
Xirr.configure do |config|
|
333
|
+
config.precision = 10
|
334
|
+
config.eps = '1.0e-8'.to_f
|
335
|
+
end
|
336
|
+
assert_equal -0.8317173694e0, @cf.xirr
|
337
|
+
end
|
338
|
+
|
339
|
+
# resetting configuration
|
340
|
+
after(:all) do
|
341
|
+
Xirr.configure do |config|
|
342
|
+
config.precision = 6
|
343
|
+
config.eps = '1.0e-6'.to_f
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
describe 'irr_guess' do
|
349
|
+
it 'Basic Scenario with Simple Positive Cash Flows' do
|
350
|
+
@cf = Cashflow.new
|
351
|
+
@cf << Transaction.new(1000, date: '2000-01-01'.to_date)
|
352
|
+
@cf << Transaction.new(-1200, date: '2001-01-01'.to_date)
|
353
|
+
assert_equal '0.2'.to_f, @cf.irr_guess
|
354
|
+
end
|
355
|
+
|
356
|
+
it 'Zero Periods of Investment' do
|
357
|
+
@cf = Cashflow.new
|
358
|
+
@cf << Transaction.new(1000, date: '2000-01-01'.to_date)
|
359
|
+
@cf << Transaction.new(-1000, date: '2000-01-01'.to_date)
|
360
|
+
assert_equal '0.0'.to_f, @cf.irr_guess
|
361
|
+
end
|
362
|
+
|
363
|
+
it 'Negative Multiple' do
|
364
|
+
@cf = Cashflow.new
|
365
|
+
@cf << Transaction.new(1000, date: '2000-01-01'.to_date)
|
366
|
+
@cf << Transaction.new(-2000, date: '2000-01-01'.to_date)
|
367
|
+
assert_equal '0.0'.to_f, @cf.irr_guess
|
368
|
+
end
|
369
|
+
|
370
|
+
it 'No Cash Flows' do
|
371
|
+
@cf = Cashflow.new
|
372
|
+
assert_equal '0.0'.to_f, @cf.irr_guess
|
373
|
+
end
|
374
|
+
|
375
|
+
it 'Multiple Positive Cash Flows and Large Investment Period' do
|
376
|
+
@cf = Cashflow.new
|
377
|
+
@cf << Transaction.new(1000, date: '2000-01-01'.to_date)
|
378
|
+
@cf << Transaction.new(-100, date: '2010-01-01'.to_date)
|
379
|
+
@cf << Transaction.new(-100, date: '2020-01-01'.to_date)
|
380
|
+
assert_equal '0.072'.to_f, @cf.irr_guess
|
381
|
+
end
|
382
|
+
|
383
|
+
it 'Very Small Periods of Investment' do
|
384
|
+
@cf = Cashflow.new
|
385
|
+
@cf << Transaction.new(1000, date: '2000-01-01'.to_date)
|
386
|
+
@cf << Transaction.new(-500, date: '2000-01-02'.to_date)
|
387
|
+
assert_equal '0.0'.to_f, @cf.irr_guess
|
388
|
+
end
|
389
|
+
|
390
|
+
it 'Complex Cash Flows' do
|
391
|
+
@cf = Cashflow.new
|
392
|
+
@cf << Transaction.new(1000, date: '2000-01-01'.to_date)
|
393
|
+
@cf << Transaction.new(-500, date: '2001-01-01'.to_date)
|
394
|
+
@cf << Transaction.new(-300, date: '2002-01-01'.to_date)
|
395
|
+
@cf << Transaction.new(-200, date: '2003-01-01'.to_date)
|
396
|
+
assert_equal '0.195'.to_f, @cf.irr_guess
|
397
|
+
end
|
398
|
+
|
399
|
+
it 'Only Negative Cash Flows' do
|
400
|
+
@cf = Cashflow.new
|
401
|
+
@cf << Transaction.new(-1000, date: '2000-01-01'.to_date)
|
402
|
+
@cf << Transaction.new(-500, date: '2001-01-01'.to_date)
|
403
|
+
assert_equal '0.0'.to_f, @cf.irr_guess
|
404
|
+
end
|
405
|
+
|
406
|
+
it 'Positive and Negative Cash Flows Spread Over Different Periods' do
|
407
|
+
@cf = Cashflow.new
|
408
|
+
@cf << Transaction.new(500, date: '2000-01-01'.to_date)
|
409
|
+
@cf << Transaction.new(-100, date: '2005-01-01'.to_date)
|
410
|
+
@cf << Transaction.new(-400, date: '2010-01-01'.to_date)
|
411
|
+
assert_equal '0.033'.to_f, @cf.irr_guess
|
412
|
+
end
|
413
|
+
end
|
312
414
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
|
2
|
+
|
1
3
|
require 'coveralls'
|
2
|
-
Coveralls.wear!
|
4
|
+
# Coveralls.wear!
|
3
5
|
|
4
6
|
require 'minitest/autorun'
|
5
7
|
require 'minitest/spec'
|
@@ -14,28 +16,3 @@ require 'xirr/cashflow.rb'
|
|
14
16
|
require 'xirr/transaction.rb'
|
15
17
|
include Xirr
|
16
18
|
|
17
|
-
=begin
|
18
|
-
|
19
|
-
require 'active_support/all'
|
20
|
-
require_relative 'lib/xirr.rb'
|
21
|
-
require_relative 'lib/xirr/config.rb'
|
22
|
-
require_relative 'lib/xirr/base.rb'
|
23
|
-
require_relative 'lib/xirr/bisection.rb'
|
24
|
-
require_relative 'lib/xirr/newton_method.rb'
|
25
|
-
require_relative 'lib/xirr/cashflow.rb'
|
26
|
-
require_relative 'lib/xirr/transaction.rb'
|
27
|
-
include Xirr
|
28
|
-
@x = Cashflow.new
|
29
|
-
@x << Transaction.new(-10000.0, :date => Date.new(2014,4,15))
|
30
|
-
@x << Transaction.new(-10000.0, :date => Date.new(2014,04,16))
|
31
|
-
@x << Transaction.new(305.6, :date => Date.new(2014,05,16))
|
32
|
-
@x << Transaction.new(9800.07, :date => Date.new(2014,06,15))
|
33
|
-
@x << Transaction.new(5052.645, :date => Date.new(2014,06,15))
|
34
|
-
@x.xirr
|
35
|
-
|
36
|
-
cf << Transaction.new(1000000, date: Date.today - 180)
|
37
|
-
cf << Transaction.new(-2200000, date: Date.today - 60)
|
38
|
-
cf << Transaction.new(-800000, date: Date.today - 30)
|
39
|
-
cf.xirr
|
40
|
-
|
41
|
-
=end
|
data/xirr.gemspec
CHANGED
@@ -18,14 +18,14 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.required_ruby_version = '>=
|
21
|
+
spec.required_ruby_version = '>=3.1'
|
22
22
|
|
23
|
-
spec.add_dependency 'activesupport', '>=
|
24
|
-
|
23
|
+
spec.add_dependency 'activesupport', '>= 6.1', '< 8'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'activesupport', '>= 6.1', '< 8'
|
26
|
+
spec.add_development_dependency 'minitest', '~> 5.14'
|
27
|
+
spec.add_development_dependency 'coveralls', '~> 0.8'
|
28
|
+
spec.add_development_dependency 'bundler', '>= 2.2'
|
29
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
25
30
|
|
26
|
-
spec.add_development_dependency 'activesupport', '~> 4.1.0'
|
27
|
-
spec.add_development_dependency 'minitest', '~> 5.11'
|
28
|
-
spec.add_development_dependency 'coveralls', '~> 0'
|
29
|
-
spec.add_development_dependency 'bundler', '~> 1.6'
|
30
|
-
spec.add_development_dependency 'rake', '~> 10'
|
31
31
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xirr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- tubedude
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,104 +16,96 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
-
- - "
|
19
|
+
version: '6.1'
|
20
|
+
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
22
|
+
version: '8'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
30
|
-
- - "
|
29
|
+
version: '6.1'
|
30
|
+
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
33
|
-
- !ruby/object:Gem::Dependency
|
34
|
-
name: RubyInline
|
35
|
-
requirement: !ruby/object:Gem::Requirement
|
36
|
-
requirements:
|
37
|
-
- - "~>"
|
38
|
-
- !ruby/object:Gem::Version
|
39
|
-
version: '3'
|
40
|
-
type: :runtime
|
41
|
-
prerelease: false
|
42
|
-
version_requirements: !ruby/object:Gem::Requirement
|
43
|
-
requirements:
|
44
|
-
- - "~>"
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '3'
|
32
|
+
version: '8'
|
47
33
|
- !ruby/object:Gem::Dependency
|
48
34
|
name: activesupport
|
49
35
|
requirement: !ruby/object:Gem::Requirement
|
50
36
|
requirements:
|
51
|
-
- - "
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '6.1'
|
40
|
+
- - "<"
|
52
41
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
42
|
+
version: '8'
|
54
43
|
type: :development
|
55
44
|
prerelease: false
|
56
45
|
version_requirements: !ruby/object:Gem::Requirement
|
57
46
|
requirements:
|
58
|
-
- - "
|
47
|
+
- - ">="
|
59
48
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
49
|
+
version: '6.1'
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '8'
|
61
53
|
- !ruby/object:Gem::Dependency
|
62
54
|
name: minitest
|
63
55
|
requirement: !ruby/object:Gem::Requirement
|
64
56
|
requirements:
|
65
57
|
- - "~>"
|
66
58
|
- !ruby/object:Gem::Version
|
67
|
-
version: '5.
|
59
|
+
version: '5.14'
|
68
60
|
type: :development
|
69
61
|
prerelease: false
|
70
62
|
version_requirements: !ruby/object:Gem::Requirement
|
71
63
|
requirements:
|
72
64
|
- - "~>"
|
73
65
|
- !ruby/object:Gem::Version
|
74
|
-
version: '5.
|
66
|
+
version: '5.14'
|
75
67
|
- !ruby/object:Gem::Dependency
|
76
68
|
name: coveralls
|
77
69
|
requirement: !ruby/object:Gem::Requirement
|
78
70
|
requirements:
|
79
71
|
- - "~>"
|
80
72
|
- !ruby/object:Gem::Version
|
81
|
-
version: '0'
|
73
|
+
version: '0.8'
|
82
74
|
type: :development
|
83
75
|
prerelease: false
|
84
76
|
version_requirements: !ruby/object:Gem::Requirement
|
85
77
|
requirements:
|
86
78
|
- - "~>"
|
87
79
|
- !ruby/object:Gem::Version
|
88
|
-
version: '0'
|
80
|
+
version: '0.8'
|
89
81
|
- !ruby/object:Gem::Dependency
|
90
82
|
name: bundler
|
91
83
|
requirement: !ruby/object:Gem::Requirement
|
92
84
|
requirements:
|
93
|
-
- - "
|
85
|
+
- - ">="
|
94
86
|
- !ruby/object:Gem::Version
|
95
|
-
version: '
|
87
|
+
version: '2.2'
|
96
88
|
type: :development
|
97
89
|
prerelease: false
|
98
90
|
version_requirements: !ruby/object:Gem::Requirement
|
99
91
|
requirements:
|
100
|
-
- - "
|
92
|
+
- - ">="
|
101
93
|
- !ruby/object:Gem::Version
|
102
|
-
version: '
|
94
|
+
version: '2.2'
|
103
95
|
- !ruby/object:Gem::Dependency
|
104
96
|
name: rake
|
105
97
|
requirement: !ruby/object:Gem::Requirement
|
106
98
|
requirements:
|
107
99
|
- - "~>"
|
108
100
|
- !ruby/object:Gem::Version
|
109
|
-
version: '
|
101
|
+
version: '13.0'
|
110
102
|
type: :development
|
111
103
|
prerelease: false
|
112
104
|
version_requirements: !ruby/object:Gem::Requirement
|
113
105
|
requirements:
|
114
106
|
- - "~>"
|
115
107
|
- !ruby/object:Gem::Version
|
116
|
-
version: '
|
108
|
+
version: '13.0'
|
117
109
|
description: Calculates IRR of a Cashflow, similar to Excel's, XIRR formula. It defaults
|
118
110
|
to Newton Method, but will calculate Bisection as well.
|
119
111
|
email:
|
@@ -148,7 +140,7 @@ homepage: https://github.com/tubedude/xirr
|
|
148
140
|
licenses:
|
149
141
|
- MIT
|
150
142
|
metadata: {}
|
151
|
-
post_install_message:
|
143
|
+
post_install_message:
|
152
144
|
rdoc_options: []
|
153
145
|
require_paths:
|
154
146
|
- lib
|
@@ -156,16 +148,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
156
148
|
requirements:
|
157
149
|
- - ">="
|
158
150
|
- !ruby/object:Gem::Version
|
159
|
-
version:
|
151
|
+
version: '3.1'
|
160
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
161
153
|
requirements:
|
162
154
|
- - ">="
|
163
155
|
- !ruby/object:Gem::Version
|
164
156
|
version: '0'
|
165
157
|
requirements: []
|
166
|
-
|
167
|
-
|
168
|
-
signing_key:
|
158
|
+
rubygems_version: 3.5.23
|
159
|
+
signing_key:
|
169
160
|
specification_version: 4
|
170
161
|
summary: Calculates XIRR (Bisection and Newton method) of a cashflow
|
171
162
|
test_files:
|