fast_xirr 0.1.0 → 1.0.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 +4 -4
- data/README.md +10 -4
- data/ext/fast_xirr/bisection.c +7 -7
- data/ext/fast_xirr/brent.c +9 -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: ffedd74eba94cee0cb84e526c4cfa95b130e4a825cf2c364b2d40ee76a4b2465
|
4
|
+
data.tar.gz: 645c4fa612bee6386e150f94d761567897c705aab9f6903c4e269828721a2f5d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f9d2c6941ad9147f16f3bc60b1b8c21e4387531d42601caa4ac9e8bf311d2a3103e5d894883829e61f439f2144e28be4486b580d01099fc9a73906817bacf53
|
7
|
+
data.tar.gz: 6e75a9452dd542a4f766e1252929e43dce5502b173b7fb70a37e4ccbb0b4b89bb76e16f042ec1fe20c6f3c03d57050afa1be76649df468f07103365935872f36
|
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.0.0.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-1.0.0.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,7 +17,7 @@
|
|
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
23
|
// Calculate the NPV at the boundaries of the interval
|
@@ -30,7 +30,7 @@ double bisection_method(CashFlow *cashflows, long count, double tol, long max_it
|
|
30
30
|
}
|
31
31
|
|
32
32
|
// Iteratively apply the bisection method
|
33
|
-
for (long iter = 0; iter < max_iter; iter++) {
|
33
|
+
for (long long iter = 0; iter < max_iter; iter++) {
|
34
34
|
mid = (low + high) / 2.0;
|
35
35
|
f_mid = npv(mid, cashflows, count, cashflows[0].date);
|
36
36
|
|
@@ -65,19 +65,19 @@ double bisection_method(CashFlow *cashflows, long count, double tol, long max_it
|
|
65
65
|
*/
|
66
66
|
VALUE calculate_xirr_with_bisection(VALUE self, VALUE rb_cashflows, VALUE rb_tol, VALUE rb_max_iter) {
|
67
67
|
// Get the number of cash flows
|
68
|
-
long count = RARRAY_LEN(rb_cashflows);
|
68
|
+
long long count = RARRAY_LEN(rb_cashflows);
|
69
69
|
CashFlow cashflows[count];
|
70
70
|
|
71
71
|
// Convert Ruby cash flows array to C array
|
72
|
-
for (long i = 0; i < count; i++) {
|
72
|
+
for (long long i = 0; i < count; i++) {
|
73
73
|
VALUE rb_cashflow = rb_ary_entry(rb_cashflows, i);
|
74
74
|
cashflows[i].amount = NUM2DBL(rb_ary_entry(rb_cashflow, 0));
|
75
|
-
cashflows[i].date = (
|
75
|
+
cashflows[i].date = (int64_t)NUM2LL(rb_ary_entry(rb_cashflow, 1));
|
76
76
|
}
|
77
77
|
|
78
78
|
// Convert tolerance and max iterations to C types
|
79
79
|
double tol = NUM2DBL(rb_tol);
|
80
|
-
long max_iter =
|
80
|
+
long long max_iter = NUM2LL(rb_max_iter);
|
81
81
|
|
82
82
|
// Initial standard bracketing interval
|
83
83
|
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,7 @@
|
|
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
21
|
// Calculate the NPV at the boundaries of the interval
|
22
22
|
double fa = npv(low, cashflows, count, cashflows[0].date);
|
23
23
|
double fb = npv(high, cashflows, count, cashflows[0].date);
|
@@ -30,7 +30,7 @@ double brent_method(CashFlow *cashflows, long count, double tol, long max_iter,
|
|
30
30
|
double c = low, fc = fa, s, d = 0.0, e = 0.0;
|
31
31
|
|
32
32
|
// Iteratively apply Brent's method
|
33
|
-
for (long iter = 0; iter < max_iter; iter++) {
|
33
|
+
for (long long iter = 0; iter < max_iter; iter++) {
|
34
34
|
if (fb * fc > 0) {
|
35
35
|
// Adjust c to ensure that f(b) and f(c) have opposite signs
|
36
36
|
c = low;
|
@@ -116,24 +116,26 @@ double brent_method(CashFlow *cashflows, long count, double tol, long max_iter,
|
|
116
116
|
*/
|
117
117
|
VALUE calculate_xirr_with_brent(VALUE self, VALUE rb_cashflows, VALUE rb_tol, VALUE rb_max_iter) {
|
118
118
|
// Get the number of cash flows
|
119
|
-
long count = RARRAY_LEN(rb_cashflows);
|
119
|
+
long long count = (long long)RARRAY_LEN(rb_cashflows);
|
120
120
|
CashFlow cashflows[count];
|
121
121
|
|
122
122
|
// Convert Ruby cash flows array to C array
|
123
|
-
for (long i = 0; i < count; i++) {
|
123
|
+
for (long long i = 0; i < count; i++) {
|
124
124
|
VALUE rb_cashflow = rb_ary_entry(rb_cashflows, i);
|
125
125
|
cashflows[i].amount = NUM2DBL(rb_ary_entry(rb_cashflow, 0));
|
126
|
-
cashflows[i].date = (
|
126
|
+
cashflows[i].date = (int64_t)NUM2LL(rb_ary_entry(rb_cashflow, 1));
|
127
127
|
}
|
128
128
|
|
129
|
+
|
129
130
|
// Convert tolerance and max iterations to C types
|
130
131
|
double tol = NUM2DBL(rb_tol);
|
131
|
-
long max_iter =
|
132
|
+
long long max_iter = NUM2LL(rb_max_iter);
|
132
133
|
|
133
134
|
double result;
|
134
135
|
|
135
136
|
// Try Brent's method with a standard bracketing interval
|
136
137
|
double low = -0.9999, high = 10.0;
|
138
|
+
|
137
139
|
result = brent_method(cashflows, count, tol, max_iter, low, high);
|
138
140
|
if (!isnan(result)) {
|
139
141
|
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:
|
4
|
+
version: 1.0.0
|
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-03 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:
|