xirr 0.1.1 → 0.2.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/CHANGE_LOG.md +13 -0
- data/README.md +26 -7
- data/lib/xirr.rb +7 -3
- data/lib/xirr/base.rb +37 -0
- data/lib/xirr/bisection.rb +59 -0
- data/lib/xirr/cashflow.rb +28 -22
- data/lib/xirr/config.rb +2 -0
- data/lib/xirr/newton_method.rb +54 -0
- data/lib/xirr/transaction.rb +7 -4
- data/lib/xirr/version.rb +1 -1
- data/test/test_cashflow.rb +62 -15
- data/test/test_helper.rb +3 -1
- data/test/test_transactions.rb +13 -0
- data/xirr.gemspec +14 -14
- metadata +13 -9
- data/lib/xirr/main.rb +0 -81
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4503151b19d0e3540c2f88066fcbefe12799d70d
|
4
|
+
data.tar.gz: 70a4f1a010d6d4a084babadeb326d59f02e7d284
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f433267dae83354352f38d3b861ae02a2b7d0d83ef009b350ea059a8d479f8f323b05f9ba197b16c2b363c3db5e2ce2ed23d05d53008749ec87276647d118dcd
|
7
|
+
data.tar.gz: 59c0511149b7473f4fee70eddfb1cb67f8bc060fd5cea1255e110724448cc15eb9f6501e83d9b3ecefbc8dc863cfbe3f02c82cf2f50fafe36fb05ecf2ba3aa51
|
data/CHANGE_LOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
## Version 0.2.1
|
2
|
+
|
3
|
+
* Output is rounded to default precision.
|
4
|
+
|
5
|
+
## Version 0.2.0
|
6
|
+
|
7
|
+
* Added Newton Method, but Bisection is still the default.
|
8
|
+
* Added new configs: precision, iteration limit.
|
9
|
+
* Raises a simple error if iteration limit is reached.
|
10
|
+
* Output is now BigDecimal.
|
11
|
+
* Fixed calculation of Bisection#npv
|
12
|
+
* Amounts in Transactions are now converted to Float
|
13
|
+
* Transactions now take Date Argument as Date instead of Time.
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Xirr
|
2
|
-
[](https://travis-ci.org/tubedude/xirr)[](https://coveralls.io/r/tubedude/xirr)[](https://codeclimate.com/github/tubedude/xirr)
|
2
|
+
[](https://travis-ci.org/tubedude/xirr)[](https://coveralls.io/r/tubedude/xirr)[](https://codeclimate.com/github/tubedude/xirr)[](https://gemnasium.com/tubedude/xirr)
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
|
5
|
+
This is a gem to calculate XIRR on Bisection Method or Newton Method.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -24,11 +24,30 @@ Or install it yourself as:
|
|
24
24
|
include Xirr
|
25
25
|
|
26
26
|
cf = Cashflow.new
|
27
|
-
cf << Transaction.new(-1000, date: '2014-01-01'.
|
28
|
-
cf << Transaction.new(-2000, date: '2014-03-01'.
|
29
|
-
cf << Transaction.new( 4500, date: '2015-12-01'.
|
27
|
+
cf << Transaction.new(-1000, date: '2014-01-01'.to_date)
|
28
|
+
cf << Transaction.new(-2000, date: '2014-03-01'.to_date)
|
29
|
+
cf << Transaction.new( 4500, date: '2015-12-01'.to_date)
|
30
30
|
cf.xirr
|
31
|
-
# 0.25159694345042327 # [
|
31
|
+
# 0.25159694345042327 # [BigDecimal]
|
32
|
+
|
33
|
+
## Configuration
|
34
|
+
|
35
|
+
# intializer/xirr.rb
|
36
|
+
|
37
|
+
Xirr.configure do |config|
|
38
|
+
config.eps = '1.0e-12'
|
39
|
+
config.days_in_year = 365.25
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
## Documentation
|
44
|
+
|
45
|
+
http://rubydoc.info/github/tubedude/xirr/master/frames
|
46
|
+
|
47
|
+
## Thanks
|
48
|
+
|
49
|
+
http://puneinvestor.wordpress.com/2013/10/01/calculate-xirr-in-ruby-bisection-method/
|
50
|
+
https://github.com/wkranec/finance
|
32
51
|
|
33
52
|
## Contributing
|
34
53
|
|
data/lib/xirr.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
require 'xirr/version'
|
2
|
+
require 'bigdecimal'
|
2
3
|
require 'active_support/configurable'
|
4
|
+
require 'active_support/concern'
|
3
5
|
require 'xirr/config'
|
4
|
-
require 'xirr/
|
6
|
+
require 'xirr/base'
|
7
|
+
require 'xirr/bisection'
|
8
|
+
require 'xirr/newton_method'
|
5
9
|
|
6
10
|
# @abstract adds a {Xirr::Cashflow} and {Xirr::Transaction} classes to calculate IRR of irregular transactions.
|
7
11
|
# Calculates Xirr
|
8
12
|
module Xirr
|
9
13
|
|
10
|
-
autoload :Transaction,
|
11
|
-
autoload :Cashflow,
|
14
|
+
autoload :Transaction, 'xirr/transaction'
|
15
|
+
autoload :Cashflow, 'xirr/cashflow'
|
12
16
|
|
13
17
|
end
|
data/lib/xirr/base.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Xirr
|
2
|
+
|
3
|
+
# Precision for BigDecimal
|
4
|
+
PRECISION = Xirr.config.precision.to_i
|
5
|
+
# Days in a year
|
6
|
+
DAYS_IN_YEAR = Xirr.config.days_in_year.to_f
|
7
|
+
# Epsilon: error margin
|
8
|
+
EPS = Xirr.config.eps
|
9
|
+
|
10
|
+
# Base module for XIRR calculation Methods
|
11
|
+
module Base
|
12
|
+
extend ActiveSupport::Concern
|
13
|
+
attr_reader :cf
|
14
|
+
|
15
|
+
# @param cf [Cashflow]
|
16
|
+
# Must provide the calling Cashflow in order to calculate
|
17
|
+
def initialize(cf)
|
18
|
+
@cf = cf
|
19
|
+
end
|
20
|
+
|
21
|
+
# Calculates days until last transaction
|
22
|
+
# @return [Rational]
|
23
|
+
# @param date [Date]
|
24
|
+
def t_in_days(date)
|
25
|
+
(date - cf.min_date) / Xirr::DAYS_IN_YEAR
|
26
|
+
end
|
27
|
+
|
28
|
+
# Net Present Value funtion that will be used to reduce the cashflow
|
29
|
+
# @param rate [BigDecimal]
|
30
|
+
def xnpv(rate)
|
31
|
+
cf.inject(0) do |sum, t|
|
32
|
+
sum += t.amount / (1 + rate) ** t_in_days(t.date)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Xirr
|
2
|
+
|
3
|
+
# Methods that will be included in Cashflow to calculate XIRR
|
4
|
+
class Bisection
|
5
|
+
include Base
|
6
|
+
|
7
|
+
# Calculates yearly Internal Rate of Return
|
8
|
+
# @return [BigDecimal]
|
9
|
+
# @param midpoint [Float]
|
10
|
+
# An initial guess rate will override the {Cashflow#irr_guess}
|
11
|
+
def xirr(midpoint = nil)
|
12
|
+
|
13
|
+
# Raises error if Cashflow is not valid
|
14
|
+
cf.valid?
|
15
|
+
|
16
|
+
# Bisection method finding the rate to zero nfv
|
17
|
+
|
18
|
+
# Initial values
|
19
|
+
left = BigDecimal.new -0.99, Xirr::PRECISION
|
20
|
+
right = BigDecimal.new 9.99, Xirr::PRECISION
|
21
|
+
eps = Xirr::EPS
|
22
|
+
midpoint ||= cf.irr_guess
|
23
|
+
limit = Xirr.config.iteration_limit.to_i
|
24
|
+
runs = 0
|
25
|
+
|
26
|
+
# Loops until difference is within error margin
|
27
|
+
while ((right-left).abs > eps) do
|
28
|
+
|
29
|
+
raise 'Did not converge' if runs == limit
|
30
|
+
runs += 1
|
31
|
+
npv_positive?(midpoint) ? right = midpoint : left = midpoint
|
32
|
+
midpoint = format_irr(left, right)
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
return midpoint.round Xirr::PRECISION
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# @param midpoint [Float]
|
43
|
+
# @return [Bolean]
|
44
|
+
# Returns true if result is to the right ot the range
|
45
|
+
def npv_positive?(midpoint)
|
46
|
+
xnpv(midpoint) > 0
|
47
|
+
end
|
48
|
+
|
49
|
+
# @param left [Float]
|
50
|
+
# @param right [Float]
|
51
|
+
# @return [Float] IRR of the Cashflow
|
52
|
+
def format_irr(left, right)
|
53
|
+
irr = (right+left) / 2
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
data/lib/xirr/cashflow.rb
CHANGED
@@ -3,17 +3,15 @@ module Xirr
|
|
3
3
|
# Expands [Array] to store a set of transactions which will be used to calculate the XIRR
|
4
4
|
# @note A Cashflow should consist of at least two transactions, one positive and one negative.
|
5
5
|
class Cashflow < Array
|
6
|
-
include Xirr::Main
|
7
6
|
|
8
|
-
# @api public
|
9
7
|
# @param args [Transaction]
|
10
8
|
# @example Creating a Cashflow
|
11
9
|
# cf = Cashflow.new
|
12
|
-
# cf << Transaction.new( 1000, date: '2013-01-01'.
|
13
|
-
# cf << Transaction.new(-1234, date: '2013-03-31'.
|
10
|
+
# cf << Transaction.new( 1000, date: '2013-01-01'.to_date)
|
11
|
+
# cf << Transaction.new(-1234, date: '2013-03-31'.to_date)
|
14
12
|
# Or
|
15
|
-
# cf = Cashflow.new Transaction.new( 1000, date: '2013-01-01'.
|
16
|
-
def initialize(*args)
|
13
|
+
# cf = Cashflow.new Transaction.new( 1000, date: '2013-01-01'.to_date), Transaction.new(-1234, date: '2013-03-31'.to_date)
|
14
|
+
def initialize(*args)
|
17
15
|
args.each { |a| self << a }
|
18
16
|
self.flatten!
|
19
17
|
end
|
@@ -36,13 +34,13 @@ module Xirr
|
|
36
34
|
|
37
35
|
# @return [Float]
|
38
36
|
# Sums all amounts in a cashflow
|
39
|
-
def sum
|
37
|
+
def sum
|
40
38
|
self.map(&:amount).sum
|
41
39
|
end
|
42
40
|
|
43
41
|
# Last investment date
|
44
42
|
# @return [Time]
|
45
|
-
def max_date
|
43
|
+
def max_date
|
46
44
|
@max_date ||= self.map(&:date).max
|
47
45
|
end
|
48
46
|
|
@@ -52,6 +50,20 @@ module Xirr
|
|
52
50
|
((multiple ** (1 / years_of_investment)) - 1).round(3)
|
53
51
|
end
|
54
52
|
|
53
|
+
# @param guess [Float]
|
54
|
+
# @param method [Symbol]
|
55
|
+
# @return [Float]
|
56
|
+
# Finds the XIRR according to the method provided. Default to Bisection
|
57
|
+
def xirr(guess = nil, method = Xirr.config.default_method)
|
58
|
+
method == :bisection ? Bisection.new(self).xirr(guess) : NewtonMethod.new(self).xirr(guess)
|
59
|
+
end
|
60
|
+
|
61
|
+
# First investment date
|
62
|
+
# @return [Time]
|
63
|
+
def min_date
|
64
|
+
@min_date ||= self.map(&:date).min
|
65
|
+
end
|
66
|
+
|
55
67
|
private
|
56
68
|
|
57
69
|
# @api private
|
@@ -69,7 +81,7 @@ module Xirr
|
|
69
81
|
# [100,100,-300] and [-100,-100,300] returns 1.5
|
70
82
|
# @api private
|
71
83
|
# @return [Float]
|
72
|
-
def multiple
|
84
|
+
def multiple
|
73
85
|
result = positives.sum(&:amount) / -negatives.sum(&:amount)
|
74
86
|
first_transaction_direction > 0 ? result : 1 / result
|
75
87
|
end
|
@@ -77,15 +89,9 @@ module Xirr
|
|
77
89
|
# @api private
|
78
90
|
# Counts how many years from first to last transaction in the cashflow
|
79
91
|
# @return
|
80
|
-
def years_of_investment
|
81
|
-
(max_date - min_date) / (365
|
82
|
-
|
83
|
-
|
84
|
-
# @api private
|
85
|
-
# First investment date
|
86
|
-
# @return [Time]
|
87
|
-
def min_date # :nodoc:
|
88
|
-
@min_date ||= self.map(&:date).min
|
92
|
+
def years_of_investment
|
93
|
+
(max_date - min_date) / (365).to_f
|
94
|
+
# (max_date - min_date) / (365 * 24 * 60 * 60).to_f
|
89
95
|
end
|
90
96
|
|
91
97
|
# @api private
|
@@ -93,7 +99,7 @@ module Xirr
|
|
93
99
|
# @see #negatives
|
94
100
|
# @see #split_transactions
|
95
101
|
# Finds all transactions income from Cashflow
|
96
|
-
def positives
|
102
|
+
def positives
|
97
103
|
split_transactions
|
98
104
|
@positives
|
99
105
|
end
|
@@ -103,7 +109,7 @@ module Xirr
|
|
103
109
|
# @see #positives
|
104
110
|
# @see #split_transactions
|
105
111
|
# Finds all transactions investments from Cashflow
|
106
|
-
def negatives
|
112
|
+
def negatives
|
107
113
|
split_transactions
|
108
114
|
@negatives
|
109
115
|
end
|
@@ -112,14 +118,14 @@ module Xirr
|
|
112
118
|
# @see #positives
|
113
119
|
# @see #negatives
|
114
120
|
# Uses partition to separate the investment transactions Negatives and the income transactions (Positives)
|
115
|
-
def split_transactions
|
121
|
+
def split_transactions
|
116
122
|
@negatives, @positives = self.partition { |x| x.amount >= 0 } # Inverted as negative amount is good
|
117
123
|
end
|
118
124
|
|
119
125
|
# @api private
|
120
126
|
# @return [String]
|
121
127
|
# Error message depending on the missing transaction
|
122
|
-
def invalid_message
|
128
|
+
def invalid_message
|
123
129
|
return 'No positive transaction' if positives.empty?
|
124
130
|
return 'No negatives transaction' if negatives.empty?
|
125
131
|
end
|
data/lib/xirr/config.rb
CHANGED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'bigdecimal/newton'
|
2
|
+
include Newton
|
3
|
+
|
4
|
+
module Xirr
|
5
|
+
# Class to calculate IRR using Newton Method
|
6
|
+
class NewtonMethod
|
7
|
+
include Base
|
8
|
+
|
9
|
+
# Base class for working with Newton's Method.
|
10
|
+
# @api private
|
11
|
+
class Function
|
12
|
+
values = {
|
13
|
+
eps: Xirr::EPS,
|
14
|
+
one: "1.0",
|
15
|
+
two: "2.0",
|
16
|
+
ten: "10.0",
|
17
|
+
zero: "0.0"
|
18
|
+
}
|
19
|
+
|
20
|
+
# define default values
|
21
|
+
values.each do |key, value|
|
22
|
+
define_method key do
|
23
|
+
BigDecimal.new(value, Xirr::PRECISION)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param transactions [Cashflow]
|
28
|
+
# @param function [Symbol]
|
29
|
+
# Initializes the Function with the Cashflow it will use as data source and the funcion to reduce it.
|
30
|
+
def initialize(transactions, function)
|
31
|
+
@transactions = transactions
|
32
|
+
@function = function
|
33
|
+
end
|
34
|
+
|
35
|
+
# Necessary for #nlsolve
|
36
|
+
# @param x [BigDecimal]
|
37
|
+
def values(x)
|
38
|
+
value = @transactions.send(@function, BigDecimal.new(x[0].to_s, Xirr::PRECISION))
|
39
|
+
[BigDecimal.new(value.to_s, Xirr::PRECISION)]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Calculates XIRR using Newton method
|
44
|
+
# @return [BigDecimal]
|
45
|
+
# @param guess [Float]
|
46
|
+
def xirr(guess=nil)
|
47
|
+
func = Function.new(self, :xnpv)
|
48
|
+
rate = [guess || cf.irr_guess.to_f]
|
49
|
+
nlsolve(func, rate)
|
50
|
+
rate[0].round Xirr::PRECISION
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/lib/xirr/transaction.rb
CHANGED
@@ -6,9 +6,12 @@ module Xirr
|
|
6
6
|
attr_accessor :date
|
7
7
|
|
8
8
|
# @example
|
9
|
-
# Transaction.new -1000, date:
|
9
|
+
# Transaction.new -1000, date: Date.now
|
10
|
+
# @param amount [Numeric]
|
11
|
+
# @param opts [Hash]
|
12
|
+
# @note Don't forget to add date: [Date] in the opts hash.
|
10
13
|
def initialize(amount, opts={})
|
11
|
-
@amount = amount
|
14
|
+
@amount = amount.to_f
|
12
15
|
|
13
16
|
# Set optional attributes..
|
14
17
|
opts.each do |key, value|
|
@@ -17,10 +20,10 @@ module Xirr
|
|
17
20
|
end
|
18
21
|
|
19
22
|
# Sets the amount
|
20
|
-
# @param value [
|
23
|
+
# @param value [Numeric]
|
21
24
|
# @return [Float]
|
22
25
|
def amount=(value)
|
23
|
-
@amount = value.to_f || 0
|
26
|
+
@amount = value.to_f || 0.0
|
24
27
|
end
|
25
28
|
|
26
29
|
# @return [String]
|
data/lib/xirr/version.rb
CHANGED
data/test/test_cashflow.rb
CHANGED
@@ -2,29 +2,76 @@ require_relative 'test_helper'
|
|
2
2
|
|
3
3
|
describe 'Cashflows' do
|
4
4
|
|
5
|
-
describe '
|
5
|
+
describe 'of a good investment' do
|
6
6
|
before(:all) do
|
7
|
-
@
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
7
|
+
@cf = Cashflow.new
|
8
|
+
@cf << Transaction.new(1000, date: '1985-01-01'.to_date)
|
9
|
+
@cf << Transaction.new(-600, date: '1990-01-01'.to_date)
|
10
|
+
@cf << Transaction.new(-6000, date: '1995-01-01'.to_date)
|
11
11
|
end
|
12
12
|
|
13
|
-
it '
|
14
|
-
assert_equal '0.225683'.to_f, @
|
13
|
+
it 'has an Internal Rate of Return on Bisection Method' do
|
14
|
+
assert_equal '0.225683'.to_f, @cf.xirr
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'has an Internal Rate of Return on Bisection Method using a Guess' do
|
18
|
+
assert_equal '0.225683'.to_f, @cf.xirr(0.15)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'has an Internal Rate of Return on Newton Method' do
|
22
|
+
assert_equal '0.225683'.to_f, @cf.xirr(nil, :newton_method)
|
15
23
|
end
|
16
24
|
|
17
25
|
it 'has an educated guess' do
|
18
|
-
assert_equal '0.
|
26
|
+
assert_equal '0.208'.to_f, @cf.irr_guess
|
27
|
+
end
|
28
|
+
end
|
29
|
+
describe 'an invalid array of Transactions' do
|
30
|
+
it 'should have an Internal Rate of Return' do
|
31
|
+
@cf = Cashflow.new
|
32
|
+
@cf << Transaction.new(-600, date: '1990-01-01'.to_date)
|
33
|
+
@cf << Transaction.new(-600, date: '1995-01-01'.to_date)
|
34
|
+
assert_raises(ArgumentError) { @cf.valid? }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
describe 'of a bad investment' do
|
38
|
+
before(:all) do
|
39
|
+
@cf = Cashflow.new
|
40
|
+
@cf << Transaction.new(1000, date: '1985-01-01'.to_date)
|
41
|
+
@cf << Transaction.new(-600, date: '1990-01-01'.to_date)
|
42
|
+
@cf << Transaction.new(-200, date: '1995-01-01'.to_date)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'has an Internal Rate of Return on Bisection Method' do
|
46
|
+
assert_equal '-0.034592'.to_f, @cf.xirr
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'has an Internal Rate of Return on Newton Method' do
|
50
|
+
assert true, @cf.xirr(nil, :newton_method).nan?
|
19
51
|
end
|
20
52
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
53
|
+
it 'has an educated guess' do
|
54
|
+
assert_equal -0.022, @cf.irr_guess
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
describe 'of a long investment' do
|
59
|
+
before(:all) do
|
60
|
+
@cf = Cashflow.new
|
61
|
+
@cf << Transaction.new(-1000, date: Date.new(1957, 1, 1))
|
62
|
+
@cf << Transaction.new(390000, date: Date.new(2013, 1, 1))
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'has an Internal Rate of Return on Bisection Method' do
|
66
|
+
assert_equal '0.112339'.to_f, @cf.xirr
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'has an Internal Rate of Return on Newton Method' do
|
70
|
+
assert_equal '0.112339'.to_f, @cf.xirr(nil, :newton_method)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'has an educated guess' do
|
74
|
+
assert_equal 0.112, @cf.irr_guess.round(6)
|
28
75
|
end
|
29
76
|
|
30
77
|
end
|
data/test/test_helper.rb
CHANGED
@@ -7,7 +7,9 @@ require 'minitest/spec'
|
|
7
7
|
require 'active_support/all'
|
8
8
|
|
9
9
|
require 'xirr/config.rb'
|
10
|
-
require 'xirr/
|
10
|
+
require 'xirr/base.rb'
|
11
|
+
require 'xirr/bisection.rb'
|
12
|
+
require 'xirr/newton_method.rb'
|
11
13
|
require 'xirr/cashflow.rb'
|
12
14
|
require 'xirr/transaction.rb'
|
13
15
|
include Xirr
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe 'Transaction' do
|
4
|
+
before(:all) do
|
5
|
+
@t = Transaction.new(1000, date: Date.today)
|
6
|
+
end
|
7
|
+
it 'converts amount to float' do
|
8
|
+
assert true, @t.amount.kind_of?(Float)
|
9
|
+
end
|
10
|
+
it 'retreives the date' do
|
11
|
+
assert_equal Date.today, @t.date
|
12
|
+
end
|
13
|
+
end
|
data/xirr.gemspec
CHANGED
@@ -4,26 +4,26 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'xirr/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name
|
8
|
-
spec.version
|
9
|
-
spec.authors
|
10
|
-
spec.email
|
11
|
-
spec.summary
|
12
|
-
spec.description
|
13
|
-
spec.homepage
|
14
|
-
spec.license
|
7
|
+
spec.name = 'xirr'
|
8
|
+
spec.version = Xirr::VERSION
|
9
|
+
spec.authors = ['tubedude']
|
10
|
+
spec.email = ['beto@trevisan.me']
|
11
|
+
spec.summary = %q{Calculates XIRR (Bisection and Newton method) of a cashflow}
|
12
|
+
spec.description = %q{Calculates IRR of a Cashflow, simillar to Excels, XIRR formula, but using the Bisection Method and Newton Method.}
|
13
|
+
spec.homepage = 'https://github.com/tubedude/xirr'
|
14
|
+
spec.license = 'MIT'
|
15
15
|
|
16
|
-
spec.files
|
17
|
-
spec.executables
|
18
|
-
spec.test_files
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
21
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
22
|
-
spec.add_development_dependency 'rake','~> 10'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10'
|
23
23
|
|
24
24
|
spec.required_ruby_version = '>=1.9'
|
25
25
|
spec.add_dependency 'activesupport', '~> 4.0'
|
26
|
-
spec.add_development_dependency 'minitest', '~> 4
|
27
|
-
spec.add_development_dependency 'coveralls','~> 0'
|
26
|
+
spec.add_development_dependency 'minitest', '~> 5.4'
|
27
|
+
spec.add_development_dependency 'coveralls', '~> 0'
|
28
28
|
|
29
29
|
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.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- tubedude
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-08-
|
11
|
+
date: 2014-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ~>
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '4
|
61
|
+
version: '5.4'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ~>
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '4
|
68
|
+
version: '5.4'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: coveralls
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,9 +80,8 @@ dependencies:
|
|
80
80
|
- - ~>
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
-
description:
|
84
|
-
|
85
|
-
and http://puneinvestor.wordpress.com/2013/10/01/calculate-xirr-in-ruby-bisection-method/. '
|
83
|
+
description: Calculates IRR of a Cashflow, simillar to Excels, XIRR formula, but using
|
84
|
+
the Bisection Method and Newton Method.
|
86
85
|
email:
|
87
86
|
- beto@trevisan.me
|
88
87
|
executables: []
|
@@ -92,18 +91,22 @@ files:
|
|
92
91
|
- .coveralls.yml
|
93
92
|
- .gitignore
|
94
93
|
- .travis.yml
|
94
|
+
- CHANGE_LOG.md
|
95
95
|
- Gemfile
|
96
96
|
- LICENSE.txt
|
97
97
|
- README.md
|
98
98
|
- Rakefile
|
99
99
|
- lib/xirr.rb
|
100
|
+
- lib/xirr/base.rb
|
101
|
+
- lib/xirr/bisection.rb
|
100
102
|
- lib/xirr/cashflow.rb
|
101
103
|
- lib/xirr/config.rb
|
102
|
-
- lib/xirr/
|
104
|
+
- lib/xirr/newton_method.rb
|
103
105
|
- lib/xirr/transaction.rb
|
104
106
|
- lib/xirr/version.rb
|
105
107
|
- test/test_cashflow.rb
|
106
108
|
- test/test_helper.rb
|
109
|
+
- test/test_transactions.rb
|
107
110
|
- xirr.gemspec
|
108
111
|
homepage: https://github.com/tubedude/xirr
|
109
112
|
licenses:
|
@@ -128,7 +131,8 @@ rubyforge_project:
|
|
128
131
|
rubygems_version: 2.0.14
|
129
132
|
signing_key:
|
130
133
|
specification_version: 4
|
131
|
-
summary: Calculates XIRR (Bisection method) of a cashflow
|
134
|
+
summary: Calculates XIRR (Bisection and Newton method) of a cashflow
|
132
135
|
test_files:
|
133
136
|
- test/test_cashflow.rb
|
134
137
|
- test/test_helper.rb
|
138
|
+
- test/test_transactions.rb
|
data/lib/xirr/main.rb
DELETED
@@ -1,81 +0,0 @@
|
|
1
|
-
require 'active_support/concern'
|
2
|
-
|
3
|
-
module Xirr
|
4
|
-
|
5
|
-
# Methods that will be included in Cashflow to calculate XIRR
|
6
|
-
module Main
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
# Calculates yearly Internal Rate of Return
|
10
|
-
# @return [Float]
|
11
|
-
# @param guess [Float] an initial guess rate that will override the {Cashflow#irr_guess}
|
12
|
-
def xirr(guess = nil)
|
13
|
-
|
14
|
-
# Raises error if Cashflow is not valid
|
15
|
-
self.valid?
|
16
|
-
|
17
|
-
# Bisection method finding the rate to zero nfv
|
18
|
-
|
19
|
-
# Initial values
|
20
|
-
days_in_year = Xirr.config.days_in_year.to_f
|
21
|
-
left = -0.99/days_in_year
|
22
|
-
right = 9.99/days_in_year
|
23
|
-
epsilon = Xirr.config.eps.to_f
|
24
|
-
guess = self.irr_guess.to_f
|
25
|
-
|
26
|
-
# Loops until difference is within error margin
|
27
|
-
while ((right-left).abs > 2 * epsilon) do
|
28
|
-
|
29
|
-
midpoint = guess || (right + left)/2
|
30
|
-
guess = nil
|
31
|
-
nfv_positive?(left, midpoint) ? left = midpoint : right = midpoint
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
return format_irr(left, right)
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
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
|
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
|
54
|
-
# Irr for daily cashflow (not in percentage format)
|
55
|
-
irr = (right+left) / 2
|
56
|
-
# Irr for daily cashflow multiplied by 365 to get yearly return
|
57
|
-
irr = irr * days_in_year
|
58
|
-
# Annualized yield (return) reflecting compounding effect of daily returns
|
59
|
-
irr = (1 + irr / days_in_year) ** days_in_year - 1
|
60
|
-
end
|
61
|
-
|
62
|
-
# Returns the Net future value of the flow given a Rate
|
63
|
-
# @param rate [Float]
|
64
|
-
# @return [Float]
|
65
|
-
def nfv(rate) # :nodoc:
|
66
|
-
self.inject(0) do |nfv,t|
|
67
|
-
nfv = nfv + t.amount * ((1 + rate) ** t_in_days(t.date))
|
68
|
-
end
|
69
|
-
end
|
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)
|
76
|
-
end
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
end
|
81
|
-
|