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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 164d5fa4cee3f5843f73e98ebb07c1b07b35c3bd76b2cb203729e4de0bbf117b
4
- data.tar.gz: 8e39e9dd7b740f4bc5a50161f13b4022eccac626faf02e4d05148393ff1d67c5
3
+ metadata.gz: ffedd74eba94cee0cb84e526c4cfa95b130e4a825cf2c364b2d40ee76a4b2465
4
+ data.tar.gz: 645c4fa612bee6386e150f94d761567897c705aab9f6903c4e269828721a2f5d
5
5
  SHA512:
6
- metadata.gz: 1afc7e0a0c46fffb68036501eda4a431d935924b4e0c4cc32befaeb67ec5520acaf3221c87ff90ef2523936b13df061e914b68469d537ef1524b2ab63b0d30fc
7
- data.tar.gz: 22f0d3670038bdabeacb097d17f52227dd6a79f8d5cf01ada768fde793ee3605f3c99bcd93fad3eec4688c4fd52dda1b46e4038d9509be5f24b6ab38e7d9237e
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', git: 'https://github.com/fintual-oss/fast-xirr.git'
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-10), as well as the maximum number of iterations (default is 100000000000000).
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-0.1.0.gem`.
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-0.1.0.gem
115
+ gem install ./fast_xirr-1.0.0.gem
110
116
  ```
111
117
 
112
118
  ### Testing the Gem
@@ -1,5 +1,5 @@
1
1
  #include <ruby.h>
2
- #include <time.h>
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 = (time_t)NUM2LONG(rb_ary_entry(rb_cashflow, 1));
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 = NUM2LONG(rb_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;
@@ -1,5 +1,5 @@
1
1
  #include <ruby.h>
2
- #include <time.h>
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 = (time_t)NUM2LONG(rb_ary_entry(rb_cashflow, 1));
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 = NUM2LONG(rb_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);
@@ -11,13 +11,13 @@
11
11
  *
12
12
  * @return The calculated NPV.
13
13
  */
14
- double npv(double rate, CashFlow *cashflows, long count, time_t min_date) {
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 = difftime(cashflows[i].date, min_date) / (60 * 60 * 24);
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
- time_t min_date = cashflows[0].date;
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
  }
@@ -1,15 +1,15 @@
1
1
  #ifndef COMMON_H
2
2
  #define COMMON_H
3
3
 
4
- #include <time.h>
4
+ #include <stdint.h>
5
5
 
6
6
  typedef struct {
7
7
  double amount;
8
- time_t date;
8
+ int64_t date;
9
9
  } CashFlow;
10
10
 
11
- double npv(double rate, CashFlow *cashflows, long count, time_t min_date);
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
@@ -1,4 +1,4 @@
1
1
  module FastXirr
2
- VERSION = "0.1.0"
2
+ VERSION = "1.0.0"
3
3
  end
4
4
 
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.to_time.to_i]
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.0
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-06-28 00:00:00.000000000 Z
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: