fast_xirr 0.1.0
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 +7 -0
- data/README.md +134 -0
- data/ext/fast_xirr/bisection.c +100 -0
- data/ext/fast_xirr/bisection.h +8 -0
- data/ext/fast_xirr/brent.c +152 -0
- data/ext/fast_xirr/brent.h +9 -0
- data/ext/fast_xirr/common.c +85 -0
- data/ext/fast_xirr/common.h +15 -0
- data/ext/fast_xirr/extconf.rb +6 -0
- data/ext/fast_xirr/xirr.c +12 -0
- data/lib/fast_xirr/version.rb +4 -0
- data/lib/fast_xirr.rb +19 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 164d5fa4cee3f5843f73e98ebb07c1b07b35c3bd76b2cb203729e4de0bbf117b
|
4
|
+
data.tar.gz: 8e39e9dd7b740f4bc5a50161f13b4022eccac626faf02e4d05148393ff1d67c5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1afc7e0a0c46fffb68036501eda4a431d935924b4e0c4cc32befaeb67ec5520acaf3221c87ff90ef2523936b13df061e914b68469d537ef1524b2ab63b0d30fc
|
7
|
+
data.tar.gz: 22f0d3670038bdabeacb097d17f52227dd6a79f8d5cf01ada768fde793ee3605f3c99bcd93fad3eec4688c4fd52dda1b46e4038d9509be5f24b6ab38e7d9237e
|
data/README.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# FastXirr
|
2
|
+
|
3
|
+
FastXirr is a high-performance Ruby gem for calculating the Extended Internal Rate of Return (XIRR). It leverages C under the hood for rapid calculations, making it suitable for performance-critical applications.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- Fast XIRR calculations using efficient algorithms
|
8
|
+
- Implemented in C for high performance
|
9
|
+
- Easy to use Ruby interface
|
10
|
+
- Includes both Brent's method and Bisection method for robust root-finding
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
### From RubyGems
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'fast_xirr', git: 'https://github.com/fintual-oss/fast-xirr.git'
|
20
|
+
```
|
21
|
+
|
22
|
+
And then execute:
|
23
|
+
```bash
|
24
|
+
bundle install
|
25
|
+
```
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
### Calculate XIRR
|
30
|
+
|
31
|
+
To calculate the XIRR for a series of cash flows, use the calculate method:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'fast_xirr'
|
35
|
+
require 'date'
|
36
|
+
|
37
|
+
cashflows = [
|
38
|
+
[1000, Date.new(1985, 1, 1)],
|
39
|
+
[-600, Date.new(1990, 1, 1)],
|
40
|
+
[-6000, Date.new(1995, 1, 1)]
|
41
|
+
]
|
42
|
+
|
43
|
+
result = FastXirr.calculate(cashflows: cashflows)
|
44
|
+
puts "XIRR: #{result}"
|
45
|
+
# => XIRR: 0.22568401743016633
|
46
|
+
```
|
47
|
+
|
48
|
+
If it is not possible to find a solution, the method will return `nan`.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
require 'fast_xirr'
|
52
|
+
require 'date'
|
53
|
+
|
54
|
+
result = FastXirr.calculate(cashflows: [[1000, Date.new(1985, 1, 1)]])
|
55
|
+
|
56
|
+
puts "XIRR: #{result}"
|
57
|
+
# => XIRR: NaN
|
58
|
+
|
59
|
+
result.nan?
|
60
|
+
# => true
|
61
|
+
```
|
62
|
+
|
63
|
+
Tolerance can be set to a custom value (default is 1e-10), as well as the maximum number of iterations (default is 100000000000000).
|
64
|
+
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
require 'fast_xirr'
|
68
|
+
require 'date'
|
69
|
+
|
70
|
+
cashflows = [
|
71
|
+
[1000, Date.new(1985, 1, 1)],
|
72
|
+
[-600, Date.new(1990, 1, 1)],
|
73
|
+
[-6000, Date.new(1995, 1, 1)]
|
74
|
+
]
|
75
|
+
|
76
|
+
result = FastXirr.calculate(cashflows: cashflows, tol: 1e-2, max_iter: 100)
|
77
|
+
puts "XIRR: #{result}"
|
78
|
+
# => XIRR: 0.22305878076614422
|
79
|
+
|
80
|
+
result = FastXirr.calculate(cashflows: cashflows, tol: 1e-8, max_iter: 2)
|
81
|
+
puts "XIRR: #{result}"
|
82
|
+
# => XIRR: NaN
|
83
|
+
```
|
84
|
+
|
85
|
+
## Build and test
|
86
|
+
|
87
|
+
### Building the Gem
|
88
|
+
|
89
|
+
To build the gem from the source code, follow these steps:
|
90
|
+
|
91
|
+
1. **Clone the Repository**:
|
92
|
+
|
93
|
+
```bash
|
94
|
+
git clone https://github.com/fintual-oss/fast-xirr.git
|
95
|
+
cd fast_xirr
|
96
|
+
```
|
97
|
+
|
98
|
+
2. **Build the Gem**:
|
99
|
+
|
100
|
+
```bash
|
101
|
+
gem build fast_xirr.gemspec
|
102
|
+
```
|
103
|
+
|
104
|
+
This will create a `.gem` file in the directory, such as `fast_xirr-0.1.0.gem`.
|
105
|
+
|
106
|
+
3. **Install the Gem Locally**:
|
107
|
+
|
108
|
+
```bash
|
109
|
+
gem install ./fast_xirr-0.1.0.gem
|
110
|
+
```
|
111
|
+
|
112
|
+
### Testing the Gem
|
113
|
+
|
114
|
+
To run the tests, follow these steps:
|
115
|
+
|
116
|
+
1. **Install Development Dependencies**:
|
117
|
+
|
118
|
+
```bash
|
119
|
+
bundle install
|
120
|
+
```
|
121
|
+
|
122
|
+
2. **Run the Tests**:
|
123
|
+
|
124
|
+
```bash
|
125
|
+
rake test
|
126
|
+
```
|
127
|
+
|
128
|
+
This will run the test suite using RSpec.
|
129
|
+
|
130
|
+
|
131
|
+
## Contributing
|
132
|
+
|
133
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/fintual-oss/fast-xirr. This project is intended to be a safe, welcoming space for collaboration.
|
134
|
+
|
@@ -0,0 +1,100 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <time.h>
|
3
|
+
#include <math.h>
|
4
|
+
#include "bisection.h"
|
5
|
+
#include "common.h"
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Bisection Method for finding the root of a function within a given interval.
|
9
|
+
* This method iteratively narrows the interval where the root is located by halving the interval.
|
10
|
+
*
|
11
|
+
* @param cashflows Array of CashFlow structures containing amount and date.
|
12
|
+
* @param count Number of elements in the cashflows array.
|
13
|
+
* @param tol Tolerance for convergence.
|
14
|
+
* @param max_iter Maximum number of iterations.
|
15
|
+
* @param low Lower bound of the initial interval.
|
16
|
+
* @param high Upper bound of the initial interval.
|
17
|
+
*
|
18
|
+
* @return The estimated root (XIRR) or NAN if it fails to converge.
|
19
|
+
*/
|
20
|
+
double bisection_method(CashFlow *cashflows, long count, double tol, long max_iter, double low, double high) {
|
21
|
+
double mid, f_low, f_mid, f_high;
|
22
|
+
|
23
|
+
// Calculate the NPV at the boundaries of the interval
|
24
|
+
f_low = npv(low, cashflows, count, cashflows[0].date);
|
25
|
+
f_high = npv(high, cashflows, count, cashflows[0].date);
|
26
|
+
|
27
|
+
// Ensure the root is bracketed
|
28
|
+
if (f_low * f_high > 0) {
|
29
|
+
return NAN; // Root is not bracketed
|
30
|
+
}
|
31
|
+
|
32
|
+
// Iteratively apply the bisection method
|
33
|
+
for (long iter = 0; iter < max_iter; iter++) {
|
34
|
+
mid = (low + high) / 2.0;
|
35
|
+
f_mid = npv(mid, cashflows, count, cashflows[0].date);
|
36
|
+
|
37
|
+
// Check for convergence
|
38
|
+
if (fabs(f_mid) < tol || fabs(high - low) < tol) {
|
39
|
+
return mid;
|
40
|
+
}
|
41
|
+
|
42
|
+
// Narrow the interval
|
43
|
+
if (f_low * f_mid < 0) {
|
44
|
+
high = mid;
|
45
|
+
f_high = f_mid;
|
46
|
+
} else {
|
47
|
+
low = mid;
|
48
|
+
f_low = f_mid;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
// If we reach here, it means we failed to converge
|
53
|
+
return NAN;
|
54
|
+
}
|
55
|
+
|
56
|
+
/**
|
57
|
+
* Ruby wrapper to calculate XIRR using the Bisection method.
|
58
|
+
*
|
59
|
+
* @param self Ruby object (self).
|
60
|
+
* @param rb_cashflows Ruby array of cash flows.
|
61
|
+
* @param rb_tol Ruby float for tolerance.
|
62
|
+
* @param rb_max_iter Ruby integer for maximum iterations.
|
63
|
+
*
|
64
|
+
* @return Ruby float with the calculated XIRR.
|
65
|
+
*/
|
66
|
+
VALUE calculate_xirr_with_bisection(VALUE self, VALUE rb_cashflows, VALUE rb_tol, VALUE rb_max_iter) {
|
67
|
+
// Get the number of cash flows
|
68
|
+
long count = RARRAY_LEN(rb_cashflows);
|
69
|
+
CashFlow cashflows[count];
|
70
|
+
|
71
|
+
// Convert Ruby cash flows array to C array
|
72
|
+
for (long i = 0; i < count; i++) {
|
73
|
+
VALUE rb_cashflow = rb_ary_entry(rb_cashflows, i);
|
74
|
+
cashflows[i].amount = NUM2DBL(rb_ary_entry(rb_cashflow, 0));
|
75
|
+
cashflows[i].date = (time_t)NUM2LONG(rb_ary_entry(rb_cashflow, 1));
|
76
|
+
}
|
77
|
+
|
78
|
+
// Convert tolerance and max iterations to C types
|
79
|
+
double tol = NUM2DBL(rb_tol);
|
80
|
+
long max_iter = NUM2LONG(rb_max_iter);
|
81
|
+
|
82
|
+
// Initial standard bracketing interval
|
83
|
+
double low = -0.999999, high = 100.0;
|
84
|
+
|
85
|
+
// Try Bisection method with the standard interval
|
86
|
+
double result = bisection_method(cashflows, count, tol, max_iter, low, high);
|
87
|
+
if (!isnan(result)) {
|
88
|
+
return rb_float_new(result);
|
89
|
+
}
|
90
|
+
|
91
|
+
// If the standard interval fails, try to find a better bracketing interval
|
92
|
+
low = -0.9999999; high = 1000.0;
|
93
|
+
if (find_bracketing_interval(cashflows, count, &low, &high)) {
|
94
|
+
result = bisection_method(cashflows, count, tol, max_iter, low, high);
|
95
|
+
return rb_float_new(result);
|
96
|
+
}
|
97
|
+
|
98
|
+
// If no interval is found, return NAN
|
99
|
+
return rb_float_new(NAN);
|
100
|
+
}
|
@@ -0,0 +1,152 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <time.h>
|
3
|
+
#include <math.h>
|
4
|
+
#include "brent.h"
|
5
|
+
#include "common.h"
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Brent's Method for finding the root of a function within a given interval.
|
9
|
+
* This method combines root bracketing, bisection, secant, and inverse quadratic interpolation.
|
10
|
+
*
|
11
|
+
* @param cashflows Array of CashFlow structures containing amount and date.
|
12
|
+
* @param count Number of elements in the cashflows array.
|
13
|
+
* @param tol Tolerance for convergence.
|
14
|
+
* @param max_iter Maximum number of iterations.
|
15
|
+
* @param low Lower bound of the initial interval.
|
16
|
+
* @param high Upper bound of the initial interval.
|
17
|
+
*
|
18
|
+
* @return The estimated root (XIRR) or NAN if it fails to converge.
|
19
|
+
*/
|
20
|
+
double brent_method(CashFlow *cashflows, long count, double tol, long max_iter, double low, double high) {
|
21
|
+
// Calculate the NPV at the boundaries of the interval
|
22
|
+
double fa = npv(low, cashflows, count, cashflows[0].date);
|
23
|
+
double fb = npv(high, cashflows, count, cashflows[0].date);
|
24
|
+
|
25
|
+
// Ensure the root is bracketed
|
26
|
+
if (fa * fb > 0) {
|
27
|
+
return NAN; // Root is not bracketed
|
28
|
+
}
|
29
|
+
|
30
|
+
double c = low, fc = fa, s, d = 0.0, e = 0.0;
|
31
|
+
|
32
|
+
// Iteratively apply Brent's method
|
33
|
+
for (long iter = 0; iter < max_iter; iter++) {
|
34
|
+
if (fb * fc > 0) {
|
35
|
+
// Adjust c to ensure that f(b) and f(c) have opposite signs
|
36
|
+
c = low;
|
37
|
+
fc = fa;
|
38
|
+
d = e = high - low;
|
39
|
+
}
|
40
|
+
if (fabs(fc) < fabs(fb)) {
|
41
|
+
// Swap low and high to make f(b) smaller in magnitude
|
42
|
+
low = high;
|
43
|
+
high = c;
|
44
|
+
c = low;
|
45
|
+
fa = fb;
|
46
|
+
fb = fc;
|
47
|
+
fc = fa;
|
48
|
+
}
|
49
|
+
|
50
|
+
// Tolerance calculation for convergence check
|
51
|
+
double tol1 = 2 * tol * fabs(high) + 0.5 * tol;
|
52
|
+
double m = 0.5 * (c - high);
|
53
|
+
|
54
|
+
// Check for convergence
|
55
|
+
if (fabs(m) <= tol1 || fb == 0.0) {
|
56
|
+
return high;
|
57
|
+
}
|
58
|
+
|
59
|
+
// Use inverse quadratic interpolation if conditions are met
|
60
|
+
if (fabs(e) >= tol1 && fabs(fa) > fabs(fb)) {
|
61
|
+
double p, q, r;
|
62
|
+
s = fb / fa;
|
63
|
+
if (low == c) {
|
64
|
+
// Use the secant method
|
65
|
+
p = 2 * m * s;
|
66
|
+
q = 1 - s;
|
67
|
+
} else {
|
68
|
+
// Use inverse quadratic interpolation
|
69
|
+
q = fa / fc;
|
70
|
+
r = fb / fc;
|
71
|
+
p = s * (2 * m * q * (q - r) - (high - low) * (r - 1));
|
72
|
+
q = (q - 1) * (r - 1) * (s - 1);
|
73
|
+
}
|
74
|
+
if (p > 0) q = -q;
|
75
|
+
p = fabs(p);
|
76
|
+
if (2 * p < fmin(3 * m * q - fabs(tol1 * q), fabs(e * q))) {
|
77
|
+
// Accept interpolation
|
78
|
+
e = d;
|
79
|
+
d = p / q;
|
80
|
+
} else {
|
81
|
+
// Use bisection
|
82
|
+
d = m;
|
83
|
+
e = m;
|
84
|
+
}
|
85
|
+
} else {
|
86
|
+
// Use bisection
|
87
|
+
d = m;
|
88
|
+
e = m;
|
89
|
+
}
|
90
|
+
|
91
|
+
// Update bounds
|
92
|
+
low = high;
|
93
|
+
fa = fb;
|
94
|
+
|
95
|
+
// Update the new high value
|
96
|
+
if (fabs(d) > tol1) {
|
97
|
+
high += d;
|
98
|
+
} else {
|
99
|
+
high += copysign(tol1, m);
|
100
|
+
}
|
101
|
+
fb = npv(high, cashflows, count, cashflows[0].date);
|
102
|
+
}
|
103
|
+
|
104
|
+
return NAN; // Failed to converge
|
105
|
+
}
|
106
|
+
|
107
|
+
/**
|
108
|
+
* Ruby wrapping to calculate XIRR using Brent's method.
|
109
|
+
*
|
110
|
+
* @param self Ruby object (self).
|
111
|
+
* @param rb_cashflows Ruby array of cash flows.
|
112
|
+
* @param rb_tol Ruby float for tolerance.
|
113
|
+
* @param rb_max_iter Ruby integer for maximum iterations.
|
114
|
+
*
|
115
|
+
* @return Ruby float with the calculated XIRR.
|
116
|
+
*/
|
117
|
+
VALUE calculate_xirr_with_brent(VALUE self, VALUE rb_cashflows, VALUE rb_tol, VALUE rb_max_iter) {
|
118
|
+
// Get the number of cash flows
|
119
|
+
long count = RARRAY_LEN(rb_cashflows);
|
120
|
+
CashFlow cashflows[count];
|
121
|
+
|
122
|
+
// Convert Ruby cash flows array to C array
|
123
|
+
for (long i = 0; i < count; i++) {
|
124
|
+
VALUE rb_cashflow = rb_ary_entry(rb_cashflows, i);
|
125
|
+
cashflows[i].amount = NUM2DBL(rb_ary_entry(rb_cashflow, 0));
|
126
|
+
cashflows[i].date = (time_t)NUM2LONG(rb_ary_entry(rb_cashflow, 1));
|
127
|
+
}
|
128
|
+
|
129
|
+
// Convert tolerance and max iterations to C types
|
130
|
+
double tol = NUM2DBL(rb_tol);
|
131
|
+
long max_iter = NUM2LONG(rb_max_iter);
|
132
|
+
|
133
|
+
double result;
|
134
|
+
|
135
|
+
// Try Brent's method with a standard bracketing interval
|
136
|
+
double low = -0.9999, high = 10.0;
|
137
|
+
result = brent_method(cashflows, count, tol, max_iter, low, high);
|
138
|
+
if (!isnan(result)) {
|
139
|
+
return rb_float_new(result);
|
140
|
+
}
|
141
|
+
|
142
|
+
// If the standard interval fails, try to find a better bracketing interval
|
143
|
+
low = -0.9999999; high = 10.0;
|
144
|
+
if (find_bracketing_interval(cashflows, count, &low, &high)) {
|
145
|
+
result = brent_method(cashflows, count, tol, max_iter, low, high);
|
146
|
+
return rb_float_new(result);
|
147
|
+
}
|
148
|
+
|
149
|
+
// If no interval is found, return NAN
|
150
|
+
return rb_float_new(NAN);
|
151
|
+
}
|
152
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#include "common.h"
|
2
|
+
#include <math.h>
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Calculate the Net Present Value (NPV) for a given rate.
|
6
|
+
*
|
7
|
+
* @param rate The discount rate.
|
8
|
+
* @param cashflows Array of CashFlow structures containing amount and date.
|
9
|
+
* @param count Number of elements in the cashflows array.
|
10
|
+
* @param min_date The earliest date in the cashflows array.
|
11
|
+
*
|
12
|
+
* @return The calculated NPV.
|
13
|
+
*/
|
14
|
+
double npv(double rate, CashFlow *cashflows, long count, time_t min_date) {
|
15
|
+
double npv_value = 0.0;
|
16
|
+
|
17
|
+
for (long i = 0; i < count; i++) {
|
18
|
+
// Calculate the number of days from the minimum date to the cash flow date
|
19
|
+
double days = difftime(cashflows[i].date, min_date) / (60 * 60 * 24);
|
20
|
+
|
21
|
+
// Calculate the discount factor and add the discounted amount to the NPV
|
22
|
+
npv_value += cashflows[i].amount / pow(1 + rate, days / 365.0);
|
23
|
+
}
|
24
|
+
|
25
|
+
return npv_value;
|
26
|
+
}
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
/**
|
31
|
+
* Find a bracketing interval for the root using the NPV function.
|
32
|
+
*
|
33
|
+
* @param cashflows Array of CashFlow structures containing amount and date.
|
34
|
+
* @param count Number of elements in the cashflows array.
|
35
|
+
* @param low Pointer to store the lower bound of the bracketing interval.
|
36
|
+
* @param high Pointer to store the upper bound of the bracketing interval.
|
37
|
+
*
|
38
|
+
* @return 1 if a bracketing interval is found, 0 otherwise.
|
39
|
+
*/
|
40
|
+
int find_bracketing_interval(CashFlow *cashflows, long count, double *low, double *high) {
|
41
|
+
double min_rate = -0.99999999, max_rate = 10.0;
|
42
|
+
double step = 0.0001;
|
43
|
+
time_t min_date = cashflows[0].date;
|
44
|
+
|
45
|
+
// Find the earliest date in the cashflows array
|
46
|
+
for (long i = 1; i < count; i++) {
|
47
|
+
if (cashflows[i].date < min_date) {
|
48
|
+
min_date = cashflows[i].date;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
// Calculate NPV at the minimum rate
|
53
|
+
double npv_min_rate = npv(min_rate, cashflows, count, min_date);
|
54
|
+
|
55
|
+
// Search for a bracketing interval within the initial range
|
56
|
+
for (double rate = min_rate + step; rate <= max_rate; rate += step) {
|
57
|
+
double npv_rate = npv(rate, cashflows, count, min_date);
|
58
|
+
|
59
|
+
// Check if the function values at consecutive rates have opposite signs
|
60
|
+
if (npv_min_rate * npv_rate <= 0) {
|
61
|
+
*low = rate - step;
|
62
|
+
*high = rate;
|
63
|
+
return 1;
|
64
|
+
}
|
65
|
+
npv_min_rate = npv_rate;
|
66
|
+
}
|
67
|
+
|
68
|
+
// Extend the search range if no interval is found within the initial range
|
69
|
+
step = 10.0;
|
70
|
+
for (double rate = max_rate + step; rate <= 10000.0; rate += step) {
|
71
|
+
double npv_rate = npv(rate, cashflows, count, min_date);
|
72
|
+
|
73
|
+
// Check if the function values at consecutive rates have opposite signs
|
74
|
+
if (npv_min_rate * npv_rate < 0) {
|
75
|
+
*low = rate - step;
|
76
|
+
*high = rate;
|
77
|
+
return 1;
|
78
|
+
}
|
79
|
+
npv_min_rate = npv_rate;
|
80
|
+
}
|
81
|
+
|
82
|
+
// If no bracketing interval is found, return 0
|
83
|
+
return 0;
|
84
|
+
}
|
85
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#ifndef COMMON_H
|
2
|
+
#define COMMON_H
|
3
|
+
|
4
|
+
#include <time.h>
|
5
|
+
|
6
|
+
typedef struct {
|
7
|
+
double amount;
|
8
|
+
time_t date;
|
9
|
+
} CashFlow;
|
10
|
+
|
11
|
+
double npv(double rate, CashFlow *cashflows, long count, time_t min_date);
|
12
|
+
|
13
|
+
int find_bracketing_interval(CashFlow *cashflows, long count, double *low, double *high);
|
14
|
+
|
15
|
+
#endif
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include "brent.h"
|
3
|
+
#include "bisection.h"
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Initialize the FastXirr module and define its methods.
|
7
|
+
*/
|
8
|
+
void Init_fast_xirr(void) {
|
9
|
+
VALUE XirrModule = rb_define_module("FastXirr");
|
10
|
+
rb_define_singleton_method(XirrModule, "_calculate_with_bisection", calculate_xirr_with_bisection, 3);
|
11
|
+
rb_define_singleton_method(XirrModule, "_calculate_with_brent", calculate_xirr_with_brent, 3);
|
12
|
+
}
|
data/lib/fast_xirr.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'fast_xirr/fast_xirr'
|
2
|
+
|
3
|
+
module FastXirr
|
4
|
+
def self.calculate(cashflows:, tol: 1e-7, max_iter: 1e10)
|
5
|
+
cashflows_with_timestamps = cashflows.map do |amount, date|
|
6
|
+
[amount, date.to_time.to_i]
|
7
|
+
end
|
8
|
+
result = _calculate_with_brent(cashflows_with_timestamps, tol, max_iter)
|
9
|
+
|
10
|
+
if result.nan?
|
11
|
+
result = _calculate_with_bisection(cashflows_with_timestamps, tol, max_iter)
|
12
|
+
puts "Brent failed, trying bisection" unless result.nan?
|
13
|
+
end
|
14
|
+
|
15
|
+
return result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fast_xirr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matias Martini
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-06-28 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: A gem to calculate the XIRR using a C extension for performance.
|
14
|
+
email:
|
15
|
+
- martini@fintual.com
|
16
|
+
executables: []
|
17
|
+
extensions:
|
18
|
+
- ext/fast_xirr/extconf.rb
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- README.md
|
22
|
+
- ext/fast_xirr/bisection.c
|
23
|
+
- ext/fast_xirr/bisection.h
|
24
|
+
- ext/fast_xirr/brent.c
|
25
|
+
- ext/fast_xirr/brent.h
|
26
|
+
- ext/fast_xirr/common.c
|
27
|
+
- ext/fast_xirr/common.h
|
28
|
+
- ext/fast_xirr/extconf.rb
|
29
|
+
- ext/fast_xirr/xirr.c
|
30
|
+
- lib/fast_xirr.rb
|
31
|
+
- lib/fast_xirr/version.rb
|
32
|
+
homepage: https://github.com/fintual-oss/fast-xirr
|
33
|
+
licenses:
|
34
|
+
- MIT
|
35
|
+
metadata:
|
36
|
+
homepage_uri: https://github.com/fintual-oss/fast-xirr
|
37
|
+
source_code_uri: https://github.com/fintual-oss/fast-xirr
|
38
|
+
changelog_uri: https://github.com/fintual-oss/fast-xirr/CHANGELOG.md
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.3.0
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
requirements: []
|
54
|
+
rubygems_version: 3.4.19
|
55
|
+
signing_key:
|
56
|
+
specification_version: 4
|
57
|
+
summary: XIRR calculation in Ruby with C extension
|
58
|
+
test_files: []
|