fast_xirr 0.1.0 → 1.0.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/README.md +10 -4
- data/ext/fast_xirr/bisection.c +12 -7
- data/ext/fast_xirr/brent.c +14 -7
- data/ext/fast_xirr/common.c +7 -7
- data/ext/fast_xirr/common.h +4 -4
- data/lib/fast_xirr/version.rb +1 -1
- data/lib/fast_xirr.rb +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f24d77d9ffd418ab7083fa4920995dc0976349a683265662abb481b6e264a2a2
|
4
|
+
data.tar.gz: 5d9a9feeacf8a6c436c68322763bf0a5177227cb3bab131cfe041e6853888df2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa37a038365b015b4f879e9057ee7ac664c02475c56879e5cccaabef4f11a5847dc529e4963d127f2807d4ed074b0b62b2e83848a463bd9505dcd40044e2190d
|
7
|
+
data.tar.gz: 679fe4163ae248a8e03256e8a98da5b4abb6e1e88653703b9a218a74d03fe7b11681e4ec83386706ead27b8ec47ccaf8df29336bdf4367a32021314ef80c4f46
|
data/README.md
CHANGED
@@ -16,7 +16,7 @@ FastXirr is a high-performance Ruby gem for calculating the Extended Internal Ra
|
|
16
16
|
Add this line to your application's Gemfile:
|
17
17
|
|
18
18
|
```ruby
|
19
|
-
gem 'fast_xirr'
|
19
|
+
gem 'fast_xirr'
|
20
20
|
```
|
21
21
|
|
22
22
|
And then execute:
|
@@ -24,6 +24,12 @@ And then execute:
|
|
24
24
|
bundle install
|
25
25
|
```
|
26
26
|
|
27
|
+
### From CLI
|
28
|
+
|
29
|
+
```bash
|
30
|
+
gem install fast_xirr
|
31
|
+
```
|
32
|
+
|
27
33
|
## Usage
|
28
34
|
|
29
35
|
### Calculate XIRR
|
@@ -60,7 +66,7 @@ result.nan?
|
|
60
66
|
# => true
|
61
67
|
```
|
62
68
|
|
63
|
-
Tolerance can be set to a custom value (default is 1e-
|
69
|
+
Tolerance can be set to a custom value (default is 1e-7), as well as the maximum number of iterations (default is 1e10).
|
64
70
|
|
65
71
|
|
66
72
|
```ruby
|
@@ -101,12 +107,12 @@ To build the gem from the source code, follow these steps:
|
|
101
107
|
gem build fast_xirr.gemspec
|
102
108
|
```
|
103
109
|
|
104
|
-
This will create a `.gem` file in the directory, such as `fast_xirr-
|
110
|
+
This will create a `.gem` file in the directory, such as `fast_xirr-1.x.x.gem`.
|
105
111
|
|
106
112
|
3. **Install the Gem Locally**:
|
107
113
|
|
108
114
|
```bash
|
109
|
-
gem install ./fast_xirr
|
115
|
+
gem install ./fast_xirr*.gem
|
110
116
|
```
|
111
117
|
|
112
118
|
### Testing the Gem
|
data/ext/fast_xirr/bisection.c
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#include <ruby.h>
|
2
|
-
#include <
|
2
|
+
#include <stdint.h>
|
3
3
|
#include <math.h>
|
4
4
|
#include "bisection.h"
|
5
5
|
#include "common.h"
|
@@ -17,9 +17,14 @@
|
|
17
17
|
*
|
18
18
|
* @return The estimated root (XIRR) or NAN if it fails to converge.
|
19
19
|
*/
|
20
|
-
double bisection_method(CashFlow *cashflows, long count, double tol, long max_iter, double low, double high) {
|
20
|
+
double bisection_method(CashFlow *cashflows, long long count, double tol, long long max_iter, double low, double high) {
|
21
21
|
double mid, f_low, f_mid, f_high;
|
22
22
|
|
23
|
+
//If cashflows are empty, return 0
|
24
|
+
if (count == 0) {
|
25
|
+
return 0.0;
|
26
|
+
}
|
27
|
+
|
23
28
|
// Calculate the NPV at the boundaries of the interval
|
24
29
|
f_low = npv(low, cashflows, count, cashflows[0].date);
|
25
30
|
f_high = npv(high, cashflows, count, cashflows[0].date);
|
@@ -30,7 +35,7 @@ double bisection_method(CashFlow *cashflows, long count, double tol, long max_it
|
|
30
35
|
}
|
31
36
|
|
32
37
|
// Iteratively apply the bisection method
|
33
|
-
for (long iter = 0; iter < max_iter; iter++) {
|
38
|
+
for (long long iter = 0; iter < max_iter; iter++) {
|
34
39
|
mid = (low + high) / 2.0;
|
35
40
|
f_mid = npv(mid, cashflows, count, cashflows[0].date);
|
36
41
|
|
@@ -65,19 +70,19 @@ double bisection_method(CashFlow *cashflows, long count, double tol, long max_it
|
|
65
70
|
*/
|
66
71
|
VALUE calculate_xirr_with_bisection(VALUE self, VALUE rb_cashflows, VALUE rb_tol, VALUE rb_max_iter) {
|
67
72
|
// Get the number of cash flows
|
68
|
-
long count = RARRAY_LEN(rb_cashflows);
|
73
|
+
long long count = RARRAY_LEN(rb_cashflows);
|
69
74
|
CashFlow cashflows[count];
|
70
75
|
|
71
76
|
// Convert Ruby cash flows array to C array
|
72
|
-
for (long i = 0; i < count; i++) {
|
77
|
+
for (long long i = 0; i < count; i++) {
|
73
78
|
VALUE rb_cashflow = rb_ary_entry(rb_cashflows, i);
|
74
79
|
cashflows[i].amount = NUM2DBL(rb_ary_entry(rb_cashflow, 0));
|
75
|
-
cashflows[i].date = (
|
80
|
+
cashflows[i].date = (int64_t)NUM2LL(rb_ary_entry(rb_cashflow, 1));
|
76
81
|
}
|
77
82
|
|
78
83
|
// Convert tolerance and max iterations to C types
|
79
84
|
double tol = NUM2DBL(rb_tol);
|
80
|
-
long max_iter =
|
85
|
+
long long max_iter = NUM2LL(rb_max_iter);
|
81
86
|
|
82
87
|
// Initial standard bracketing interval
|
83
88
|
double low = -0.999999, high = 100.0;
|
data/ext/fast_xirr/brent.c
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#include <ruby.h>
|
2
|
-
#include <
|
2
|
+
#include <stdint.h>
|
3
3
|
#include <math.h>
|
4
4
|
#include "brent.h"
|
5
5
|
#include "common.h"
|
@@ -17,7 +17,12 @@
|
|
17
17
|
*
|
18
18
|
* @return The estimated root (XIRR) or NAN if it fails to converge.
|
19
19
|
*/
|
20
|
-
double brent_method(CashFlow *cashflows, long count, double tol, long max_iter, double low, double high) {
|
20
|
+
double brent_method(CashFlow *cashflows, long long count, double tol, long long max_iter, double low, double high) {
|
21
|
+
//If cashflows are empty, return 0
|
22
|
+
if (count == 0) {
|
23
|
+
return 0.0;
|
24
|
+
}
|
25
|
+
|
21
26
|
// Calculate the NPV at the boundaries of the interval
|
22
27
|
double fa = npv(low, cashflows, count, cashflows[0].date);
|
23
28
|
double fb = npv(high, cashflows, count, cashflows[0].date);
|
@@ -30,7 +35,7 @@ double brent_method(CashFlow *cashflows, long count, double tol, long max_iter,
|
|
30
35
|
double c = low, fc = fa, s, d = 0.0, e = 0.0;
|
31
36
|
|
32
37
|
// Iteratively apply Brent's method
|
33
|
-
for (long iter = 0; iter < max_iter; iter++) {
|
38
|
+
for (long long iter = 0; iter < max_iter; iter++) {
|
34
39
|
if (fb * fc > 0) {
|
35
40
|
// Adjust c to ensure that f(b) and f(c) have opposite signs
|
36
41
|
c = low;
|
@@ -116,24 +121,26 @@ double brent_method(CashFlow *cashflows, long count, double tol, long max_iter,
|
|
116
121
|
*/
|
117
122
|
VALUE calculate_xirr_with_brent(VALUE self, VALUE rb_cashflows, VALUE rb_tol, VALUE rb_max_iter) {
|
118
123
|
// Get the number of cash flows
|
119
|
-
long count = RARRAY_LEN(rb_cashflows);
|
124
|
+
long long count = (long long)RARRAY_LEN(rb_cashflows);
|
120
125
|
CashFlow cashflows[count];
|
121
126
|
|
122
127
|
// Convert Ruby cash flows array to C array
|
123
|
-
for (long i = 0; i < count; i++) {
|
128
|
+
for (long long i = 0; i < count; i++) {
|
124
129
|
VALUE rb_cashflow = rb_ary_entry(rb_cashflows, i);
|
125
130
|
cashflows[i].amount = NUM2DBL(rb_ary_entry(rb_cashflow, 0));
|
126
|
-
cashflows[i].date = (
|
131
|
+
cashflows[i].date = (int64_t)NUM2LL(rb_ary_entry(rb_cashflow, 1));
|
127
132
|
}
|
128
133
|
|
134
|
+
|
129
135
|
// Convert tolerance and max iterations to C types
|
130
136
|
double tol = NUM2DBL(rb_tol);
|
131
|
-
long max_iter =
|
137
|
+
long long max_iter = NUM2LL(rb_max_iter);
|
132
138
|
|
133
139
|
double result;
|
134
140
|
|
135
141
|
// Try Brent's method with a standard bracketing interval
|
136
142
|
double low = -0.9999, high = 10.0;
|
143
|
+
|
137
144
|
result = brent_method(cashflows, count, tol, max_iter, low, high);
|
138
145
|
if (!isnan(result)) {
|
139
146
|
return rb_float_new(result);
|
data/ext/fast_xirr/common.c
CHANGED
@@ -11,13 +11,13 @@
|
|
11
11
|
*
|
12
12
|
* @return The calculated NPV.
|
13
13
|
*/
|
14
|
-
double npv(double rate, CashFlow *cashflows, long count,
|
14
|
+
double npv(double rate, CashFlow *cashflows, long long count, int64_t min_date) {
|
15
15
|
double npv_value = 0.0;
|
16
16
|
|
17
|
-
for (long i = 0; i < count; i++) {
|
17
|
+
for (long long i = 0; i < count; i++) {
|
18
18
|
// Calculate the number of days from the minimum date to the cash flow date
|
19
|
-
double days =
|
20
|
-
|
19
|
+
double days = (double)(cashflows[i].date - min_date) / (60 * 60 * 24);
|
20
|
+
|
21
21
|
// Calculate the discount factor and add the discounted amount to the NPV
|
22
22
|
npv_value += cashflows[i].amount / pow(1 + rate, days / 365.0);
|
23
23
|
}
|
@@ -37,13 +37,13 @@ double npv(double rate, CashFlow *cashflows, long count, time_t min_date) {
|
|
37
37
|
*
|
38
38
|
* @return 1 if a bracketing interval is found, 0 otherwise.
|
39
39
|
*/
|
40
|
-
int find_bracketing_interval(CashFlow *cashflows, long count, double *low, double *high) {
|
40
|
+
int find_bracketing_interval(CashFlow *cashflows, long long count, double *low, double *high) {
|
41
41
|
double min_rate = -0.99999999, max_rate = 10.0;
|
42
42
|
double step = 0.0001;
|
43
|
-
|
43
|
+
int64_t min_date = cashflows[0].date;
|
44
44
|
|
45
45
|
// Find the earliest date in the cashflows array
|
46
|
-
for (long i = 1; i < count; i++) {
|
46
|
+
for (long long i = 1; i < count; i++) {
|
47
47
|
if (cashflows[i].date < min_date) {
|
48
48
|
min_date = cashflows[i].date;
|
49
49
|
}
|
data/ext/fast_xirr/common.h
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
#ifndef COMMON_H
|
2
2
|
#define COMMON_H
|
3
3
|
|
4
|
-
#include <
|
4
|
+
#include <stdint.h>
|
5
5
|
|
6
6
|
typedef struct {
|
7
7
|
double amount;
|
8
|
-
|
8
|
+
int64_t date;
|
9
9
|
} CashFlow;
|
10
10
|
|
11
|
-
double npv(double rate, CashFlow *cashflows, long count,
|
11
|
+
double npv(double rate, CashFlow *cashflows, long long count, int64_t min_date);
|
12
12
|
|
13
|
-
int find_bracketing_interval(CashFlow *cashflows, long count, double *low, double *high);
|
13
|
+
int find_bracketing_interval(CashFlow *cashflows, long long count, double *low, double *high);
|
14
14
|
|
15
15
|
#endif
|
data/lib/fast_xirr/version.rb
CHANGED
data/lib/fast_xirr.rb
CHANGED
@@ -3,13 +3,13 @@ require 'fast_xirr/fast_xirr'
|
|
3
3
|
module FastXirr
|
4
4
|
def self.calculate(cashflows:, tol: 1e-7, max_iter: 1e10)
|
5
5
|
cashflows_with_timestamps = cashflows.map do |amount, date|
|
6
|
-
[amount, date.
|
7
|
-
end
|
6
|
+
[amount, Time.utc(date.year, date.month, date.day).to_i]
|
7
|
+
end.sort_by { |_amount, timestamp| timestamp }
|
8
|
+
|
8
9
|
result = _calculate_with_brent(cashflows_with_timestamps, tol, max_iter)
|
9
10
|
|
10
11
|
if result.nan?
|
11
12
|
result = _calculate_with_bisection(cashflows_with_timestamps, tol, max_iter)
|
12
|
-
puts "Brent failed, trying bisection" unless result.nan?
|
13
13
|
end
|
14
14
|
|
15
15
|
return result
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fast_xirr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matias Martini
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-07-11 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: A gem to calculate the XIRR using a C extension for performance.
|
14
14
|
email:
|