finrb 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +117 -63
- data/lib/finrb/amortization.rb +24 -24
- data/lib/finrb/cashflows.rb +41 -41
- data/lib/finrb/config.rb +1 -1
- data/lib/finrb/decimal.rb +3 -3
- data/lib/finrb/rates.rb +41 -42
- data/lib/finrb/transaction.rb +1 -1
- data/lib/finrb/utils.rb +383 -457
- data/lib/finrb.rb +9 -9
- metadata +18 -42
- data/.dockerignore +0 -2
- data/.gitattributes +0 -83
- data/.github/dependabot.yml +0 -7
- data/.github/issue_template.md +0 -15
- data/.github/pull_request_template.md +0 -17
- data/.github/workflows/ci.yml +0 -21
- data/.github/workflows/codeql.yml +0 -72
- data/.github/workflows/rubocop.yml +0 -24
- data/.gitignore +0 -114
- data/.rubocop.yml +0 -284
- data/.ruby-version +0 -1
- data/.semver +0 -5
- data/.yardopts +0 -1
- data/Dockerfile +0 -35
- data/Gemfile +0 -5
- data/Gemfile.lock +0 -119
- data/Rakefile +0 -33
- data/docs/.gitkeep +0 -0
- data/docs/api.md +0 -1086
- data/finrb.gemspec +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b999dd6faadf24d9020be497526bbf8a2614fbcc994232e84b8d2cc693eb283f
|
4
|
+
data.tar.gz: 5611c3ab6cc8abc69e95fdb58e3d5913c1e47eaaf599f0aa097ba82f1377893b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a631f3777e169081228e91e08f94835780e479a63604828a89c649cf8ca0303d2ef2a9ad11de60376c14bc14d2652e700455523d232d45d4581ddb06b430e8c4
|
7
|
+
data.tar.gz: 74d40ad091e7cc2756e58371ea53bf10ade3b5e6573be78813cfadfb90c67e54d7595bc57947d4b9f5b5b74fb9423dc835a10541144a65b9747c0017f6cb297f
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -4,6 +4,18 @@
|
|
4
4
|
[![CodeQL](https://github.com/ncs1/finrb/actions/workflows/codeql.yml/badge.svg)](https://github.com/ncs1/finrb/actions/workflows/codeql.yml)
|
5
5
|
[![RuboCop](https://github.com/ncs1/finrb/actions/workflows/rubocop.yml/badge.svg)](https://github.com/ncs1/finrb/actions/workflows/rubocop.yml)
|
6
6
|
|
7
|
+
<!-- TOC depthfrom:2 -->
|
8
|
+
|
9
|
+
- [Overview](#overview)
|
10
|
+
- [Features](#features)
|
11
|
+
- [Configuration](#configuration)
|
12
|
+
- [API and examples](#api-and-examples)
|
13
|
+
- [Resources](#resources)
|
14
|
+
- [Acknowledgements](#acknowledgements)
|
15
|
+
- [License](#license)
|
16
|
+
|
17
|
+
<!-- /TOC -->
|
18
|
+
|
7
19
|
Ruby gem for financial calculations/modeling.
|
8
20
|
|
9
21
|
finrb forked from the ruby [finance](https://github.com/Edward-Intelligence/finance) gem.
|
@@ -14,66 +26,103 @@ finrb forked from the ruby [finance](https://github.com/Edward-Intelligence/fina
|
|
14
26
|
|
15
27
|
Currently implemented features include:
|
16
28
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
29
|
+
- Uses the [flt](https://github.com/jgoizueta/flt) gem to ensure precision decimal arithmetic in all calculations.
|
30
|
+
- Fixed-rate mortgage amortization (30/360).
|
31
|
+
- Interest rates
|
32
|
+
- Various cash flow computations, such as NPV and IRR.
|
33
|
+
- Adjustable rate mortgage amortization.
|
34
|
+
- Payment modifications (i.e., how does paying an additional $75 per month affect the amortization?)
|
35
|
+
- Utils class provides basic financial calculation utilities (ported from R's [FinCal](https://github.com/felixfan/FinCal) library):
|
36
|
+
|
37
|
+
- Basic Earnings Per Share
|
38
|
+
|
39
|
+
- Bond-equivalent yield (BEY), 2 x the semiannual discount rate
|
40
|
+
|
41
|
+
- Calculate the net increase in common shares from the potential exercise of stock options or warrants
|
42
|
+
|
43
|
+
- Calculate weighted average shares - weighted average number of common shares
|
44
|
+
|
45
|
+
- Cash ratio - Liquidity ratios measure the firm's ability to satisfy its short-term obligations as they come due.
|
46
|
+
|
47
|
+
- Computing Coefficient of variation
|
48
|
+
|
49
|
+
- Computing HPR, the holding period return
|
50
|
+
|
51
|
+
- Computing IRR, the internal rate of return
|
52
|
+
|
53
|
+
- Computing NPV, the PV of the cash flows less the initial (time = 0) outlay
|
54
|
+
|
55
|
+
- Computing Roy's safety-first ratio
|
56
|
+
|
57
|
+
- Computing Sampling error
|
58
|
+
|
59
|
+
- Computing Sharpe Ratio
|
60
|
+
|
61
|
+
- Computing TWRR, the time-weighted rate of return
|
62
|
+
|
63
|
+
- Computing bank discount yield (BDY) for a T-bill
|
64
|
+
|
65
|
+
- Computing money market yield (MMY) for a T-bill
|
66
|
+
|
67
|
+
- Computing the future value of an uneven cash flow series
|
68
|
+
|
69
|
+
- Computing the present value of an uneven cash flow series
|
70
|
+
|
71
|
+
- Computing the rate of return for each period
|
72
|
+
|
73
|
+
- Convert a given continuous compounded rate to a norminal rate
|
74
|
+
|
75
|
+
- Convert a given norminal rate to a continuous compounded rate
|
76
|
+
|
77
|
+
- Convert holding period return to the effective annual rate
|
78
|
+
|
79
|
+
- Convert stated annual rate to the effective annual rate (with continuous compounding)
|
80
|
+
|
81
|
+
- Cost of goods sold and ending inventory under three methods (FIFO,LIFO,Weighted average)
|
82
|
+
|
83
|
+
- Current ratio - Liquidity ratios measure the firm's ability to satisfy its short-term obligations as they come due.
|
84
|
+
|
85
|
+
- Debt ratio - Solvency ratios measure the firm's ability to satisfy its long-term obligations.
|
86
|
+
|
87
|
+
- Depreciation Expense Recognition - Straight-line depreciation (SL) allocates an equal amount of depreciation each year over the asset's useful life
|
88
|
+
|
89
|
+
- Depreciation Expense Recognition - double-declining balance (DDB), the most common declining balance method, which applies two times the straight-line rate to the declining balance.
|
90
|
+
|
91
|
+
- Diluted Earnings Per Share
|
92
|
+
|
93
|
+
- Equivalent/proportional Interest Rates
|
94
|
+
|
95
|
+
- Estimate future value (fv) (of a single sum)
|
96
|
+
|
97
|
+
- Estimate future value of an annuity
|
98
|
+
|
99
|
+
- Estimate period payment
|
100
|
+
|
101
|
+
- Estimate present value (pv) (of a single sum) (of an annuity)
|
102
|
+
|
103
|
+
- Estimate present value of a perpetuity
|
104
|
+
|
105
|
+
- Estimate the number of periods
|
106
|
+
|
107
|
+
- Financial leverage - Solvency ratios measure the firm's ability to satisfy its long-term obligations.
|
108
|
+
|
109
|
+
- Geometric mean return
|
110
|
+
|
111
|
+
- Gross profit margin - Evaluate a company's financial performance
|
112
|
+
|
113
|
+
- Harmonic mean, average price
|
114
|
+
|
115
|
+
- Long-term debt-to-equity - Solvency ratios measure the firm's ability to satisfy its long-term obligations.
|
116
|
+
|
117
|
+
- Net profit margin - Evaluate a company's financial performance
|
118
|
+
|
119
|
+
- Quick ratio - Liquidity ratios measure the firm's ability to satisfy its short-term obligations as they come due.
|
120
|
+
|
121
|
+
- Rate of return for a perpetuity
|
122
|
+
|
123
|
+
- Total debt-to-equity - Solvency ratios measure the firm's ability to satisfy its long-term obligations.
|
124
|
+
|
125
|
+
- Weighted mean as a portfolio return
|
77
126
|
|
78
127
|
### Configuration
|
79
128
|
|
@@ -94,9 +143,14 @@ See [api.md](docs/api.md)
|
|
94
143
|
|
95
144
|
## Resources
|
96
145
|
|
97
|
-
|
98
|
-
|
99
|
-
|
146
|
+
- [RubyGems Page](https://rubygems.org/gems/finrb)
|
147
|
+
- [Source Code](https://github.com/ncs1/finrb)
|
148
|
+
- [Bug Tracker](https://github.com/ncs1/finrb/issues)
|
149
|
+
|
150
|
+
## Acknowledgements
|
151
|
+
|
152
|
+
- Martin Bjeldbak Madsen (@martinbjeldbak), Bill Kranec (@wkranec) - original [finance](https://github.com/Edward-Intelligence/finance) gem maintainers.
|
153
|
+
- Yanhui Fan (@felixfan) - maintainer of [FinCal](https://github.com/felixfan/FinCal) library.
|
100
154
|
|
101
155
|
## License
|
102
156
|
|
data/lib/finrb/amortization.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
3
|
+
require_relative 'cashflows'
|
4
|
+
require_relative 'decimal'
|
5
|
+
require_relative 'transaction'
|
6
6
|
|
7
7
|
module Finrb
|
8
8
|
# the Amortization class provides an interface for working with loan amortizations.
|
@@ -34,6 +34,26 @@ module Finrb
|
|
34
34
|
# @api public
|
35
35
|
attr_reader :rates
|
36
36
|
|
37
|
+
# @return [DecNum] the periodic payment due on a loan
|
38
|
+
# @param [DecNum] principal the initial amount of the loan or investment
|
39
|
+
# @param [Rate] rate the applicable interest rate (per period)
|
40
|
+
# @param [Integer] periods the number of periods needed for repayment
|
41
|
+
# @note in most cases, you will probably want to use rate.monthly when calling this function outside of an Amortization instance.
|
42
|
+
# @example
|
43
|
+
# rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
|
44
|
+
# rate.duration #=> 360
|
45
|
+
# Amortization.payment(200000, rate.monthly, rate.duration) #=> DecNum('-926.23')
|
46
|
+
# @see https://en.wikipedia.org/wiki/Amortization_calculator
|
47
|
+
# @api public
|
48
|
+
def self.payment(principal, rate, periods)
|
49
|
+
if rate.zero?
|
50
|
+
# simplified formula to avoid division-by-zero when interest rate is zero
|
51
|
+
-(principal / periods).round(2)
|
52
|
+
else
|
53
|
+
-(principal * (rate + (rate / (((rate + 1)**periods) - 1)))).round(2)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
37
57
|
# create a new Amortization instance
|
38
58
|
# @return [Amortization]
|
39
59
|
# @param [DecNum] principal the initial amount of the loan or investment
|
@@ -54,7 +74,7 @@ module Finrb
|
|
54
74
|
|
55
75
|
# compare two Amortization instances
|
56
76
|
# @return [Numeric] -1, 0, or +1
|
57
|
-
# @param [Amortization]
|
77
|
+
# @param [Amortization] other
|
58
78
|
# @api public
|
59
79
|
def ==(other)
|
60
80
|
(principal == other.principal) && (rates == other.rates) && (payments == other.payments)
|
@@ -158,26 +178,6 @@ module Finrb
|
|
158
178
|
@transactions.filter_map { |trans| trans.amount if trans.interest? }
|
159
179
|
end
|
160
180
|
|
161
|
-
# @return [DecNum] the periodic payment due on a loan
|
162
|
-
# @param [DecNum] principal the initial amount of the loan or investment
|
163
|
-
# @param [Rate] rate the applicable interest rate (per period)
|
164
|
-
# @param [Integer] periods the number of periods needed for repayment
|
165
|
-
# @note in most cases, you will probably want to use rate.monthly when calling this function outside of an Amortization instance.
|
166
|
-
# @example
|
167
|
-
# rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
|
168
|
-
# rate.duration #=> 360
|
169
|
-
# Amortization.payment(200000, rate.monthly, rate.duration) #=> DecNum('-926.23')
|
170
|
-
# @see https://en.wikipedia.org/wiki/Amortization_calculator
|
171
|
-
# @api public
|
172
|
-
def self.payment(principal, rate, periods)
|
173
|
-
if rate.zero?
|
174
|
-
# simplified formula to avoid division-by-zero when interest rate is zero
|
175
|
-
-(principal / periods).round(2)
|
176
|
-
else
|
177
|
-
-(principal * (rate + (rate / (((1 + rate)**periods) - 1)))).round(2)
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
181
|
# @return [Array] the amount of the payment in each period
|
182
182
|
# @example find the total payments for a loan
|
183
183
|
# rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
|
data/lib/finrb/cashflows.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
3
|
+
require_relative 'config'
|
4
|
+
require_relative 'decimal'
|
5
|
+
require_relative 'rates'
|
5
6
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
7
|
+
require 'bigdecimal'
|
8
|
+
require 'bigdecimal/newton'
|
9
|
+
require 'business_time'
|
9
10
|
include Newton
|
10
11
|
|
11
12
|
module Finrb
|
@@ -15,7 +16,7 @@ module Finrb
|
|
15
16
|
# Base class for working with Newton's Method.
|
16
17
|
# @api private
|
17
18
|
class Function
|
18
|
-
values = { eps: Finrb.config.eps, one:
|
19
|
+
values = { eps: Finrb.config.eps, one: '1.0', two: '2.0', ten: '10.0', zero: '0.0' }
|
19
20
|
|
20
21
|
values.each do |key, value|
|
21
22
|
define_method key do
|
@@ -40,7 +41,7 @@ module Finrb
|
|
40
41
|
|
41
42
|
# calculate the internal rate of return for a sequence of cash flows
|
42
43
|
# @return [DecNum] the internal rate of return
|
43
|
-
# @param [Numeric] Initial guess rate, Defaults to 1.0
|
44
|
+
# @param [Numeric] guess Initial guess rate, Defaults to 1.0
|
44
45
|
# @example
|
45
46
|
# [-4000,1200,1410,1875,1050].irr #=> 0.143
|
46
47
|
# @see https://en.wikipedia.org/wiki/Internal_rate_of_return
|
@@ -48,7 +49,7 @@ module Finrb
|
|
48
49
|
def irr(guess = nil)
|
49
50
|
# Make sure we have a valid sequence of cash flows.
|
50
51
|
positives, negatives = partition { |i| i >= 0 }
|
51
|
-
raise(ArgumentError,
|
52
|
+
raise(ArgumentError, 'Calculation does not converge.') if positives.empty? || negatives.empty?
|
52
53
|
|
53
54
|
func = Function.new(self, :npv)
|
54
55
|
rate = [valid(guess)]
|
@@ -57,7 +58,7 @@ module Finrb
|
|
57
58
|
end
|
58
59
|
|
59
60
|
def method_missing(name, *args, &block)
|
60
|
-
return sum if name.to_s ==
|
61
|
+
return sum if name.to_s == 'sum'
|
61
62
|
|
62
63
|
super
|
63
64
|
end
|
@@ -75,7 +76,7 @@ module Finrb
|
|
75
76
|
rate = Flt::DecNum.new(rate.to_s)
|
76
77
|
total = Flt::DecNum.new(0.to_s)
|
77
78
|
cashflows.each_with_index do |cashflow, index|
|
78
|
-
total += cashflow / ((
|
79
|
+
total += cashflow / ((rate + 1)**index)
|
79
80
|
end
|
80
81
|
|
81
82
|
total
|
@@ -97,7 +98,7 @@ module Finrb
|
|
97
98
|
if positives.empty? || negatives.empty?
|
98
99
|
raise(
|
99
100
|
ArgumentError,
|
100
|
-
|
101
|
+
'Calculation does not converge. Cashflow needs to have a least one positive and one negative value.'
|
101
102
|
)
|
102
103
|
end
|
103
104
|
|
@@ -120,48 +121,47 @@ module Finrb
|
|
120
121
|
rate = Flt::DecNum.new(rate.to_s)
|
121
122
|
|
122
123
|
sum do |t|
|
123
|
-
t.amount / ((
|
124
|
+
t.amount / ((rate + 1)**(date_diff(start, t.date) / days_in_period))
|
124
125
|
end
|
125
126
|
end
|
126
127
|
|
127
128
|
private
|
128
|
-
def date_diff(from, to)
|
129
|
-
if Finrb.config.business_days
|
130
|
-
from.to_date.business_days_until(to)
|
131
|
-
else
|
132
|
-
to - from
|
133
|
-
end
|
134
|
-
end
|
135
129
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
end
|
130
|
+
def date_diff(from, to)
|
131
|
+
if Finrb.config.business_days
|
132
|
+
from.to_date.business_days_until(to)
|
133
|
+
else
|
134
|
+
to - from
|
142
135
|
end
|
136
|
+
end
|
143
137
|
|
144
|
-
|
145
|
-
|
138
|
+
def days_in_period
|
139
|
+
if Finrb.config.periodic_compound && Finrb.config.business_days
|
140
|
+
start.to_date.business_days_until(stop).to_f
|
141
|
+
else
|
142
|
+
Flt::DecNum.new(365.days.to_s)
|
146
143
|
end
|
144
|
+
end
|
147
145
|
|
148
|
-
|
149
|
-
|
150
|
-
|
146
|
+
def start
|
147
|
+
@start ||= self[0].date
|
148
|
+
end
|
151
149
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
raise(ArgumentError, "Invalid Guess. Default guess should be a [Numeric] value.")
|
156
|
-
end
|
150
|
+
def stop
|
151
|
+
@stop ||= self[-1].date.to_date
|
152
|
+
end
|
157
153
|
|
158
|
-
|
159
|
-
|
160
|
-
|
154
|
+
def valid(guess)
|
155
|
+
if guess.nil?
|
156
|
+
raise(ArgumentError, 'Invalid Guess. Default guess should be a [Numeric] value.') unless Finrb.config.guess.is_a?(Numeric)
|
161
157
|
|
162
|
-
|
163
|
-
|
164
|
-
|
158
|
+
Finrb.config.guess
|
159
|
+
else
|
160
|
+
raise(ArgumentError, 'Invalid Guess. Use a [Numeric] value.') unless guess.is_a?(Numeric)
|
161
|
+
|
162
|
+
guess
|
163
|
+
end.to_f
|
164
|
+
end
|
165
165
|
end
|
166
166
|
end
|
167
167
|
|
data/lib/finrb/config.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Finrb
|
4
4
|
include ActiveSupport::Configurable
|
5
5
|
|
6
|
-
default_values = { eps:
|
6
|
+
default_values = { eps: '1.0e-16', guess: 1.0, business_days: false, periodic_compound: false }
|
7
7
|
|
8
8
|
default_values.each do |key, value|
|
9
9
|
config.send("#{key.to_sym}=", value)
|
data/lib/finrb/decimal.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'flt'
|
4
|
+
require 'rubygems'
|
5
5
|
include Flt
|
6
6
|
|
7
7
|
DecNum.context.define_conversion_from(BigDecimal) do |x, _context|
|
@@ -13,7 +13,7 @@ DecNum.context.define_conversion_to(BigDecimal) do |x|
|
|
13
13
|
end
|
14
14
|
|
15
15
|
class Numeric
|
16
|
-
def
|
16
|
+
def to_dec
|
17
17
|
if instance_of?(DecNum)
|
18
18
|
self
|
19
19
|
else
|
data/lib/finrb/rates.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative 'decimal'
|
4
4
|
|
5
5
|
module Finrb
|
6
6
|
# the Rate class provides an interface for working with interest rates.
|
@@ -8,6 +8,45 @@ module Finrb
|
|
8
8
|
# @api public
|
9
9
|
class Rate
|
10
10
|
include Comparable
|
11
|
+
# Accepted rate types
|
12
|
+
TYPES = { apr: 'effective', apy: 'effective', effective: 'effective', nominal: 'nominal' }.freeze
|
13
|
+
# convert a nominal interest rate to an effective interest rate
|
14
|
+
# @return [DecNum] the effective interest rate
|
15
|
+
# @param [Numeric] rate the nominal interest rate
|
16
|
+
# @param [Numeric] periods the number of compounding periods per year
|
17
|
+
# @example
|
18
|
+
# Rate.to_effective(0.05, 4) #=> DecNum('0.05095')
|
19
|
+
# @api public
|
20
|
+
def self.to_effective(rate, periods)
|
21
|
+
rate = Flt::DecNum.new(rate.to_s)
|
22
|
+
periods = Flt::DecNum.new(periods.to_s)
|
23
|
+
|
24
|
+
if periods.infinite?
|
25
|
+
rate.exp - 1
|
26
|
+
else
|
27
|
+
(((rate / periods) + 1)**periods) - 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# convert an effective interest rate to a nominal interest rate
|
32
|
+
# @return [DecNum] the nominal interest rate
|
33
|
+
# @param [Numeric] rate the effective interest rate
|
34
|
+
# @param [Numeric] periods the number of compounding periods per year
|
35
|
+
# @example
|
36
|
+
# Rate.to_nominal(0.06, 365) #=> DecNum('0.05827')
|
37
|
+
# @see https://www.miniwebtool.com/nominal-interest-rate-calculator/
|
38
|
+
# @api public
|
39
|
+
def self.to_nominal(rate, periods)
|
40
|
+
rate = Flt::DecNum.new(rate.to_s)
|
41
|
+
periods = Flt::DecNum.new(periods.to_s)
|
42
|
+
|
43
|
+
if periods.infinite?
|
44
|
+
(rate + 1).log
|
45
|
+
else
|
46
|
+
periods * (((rate + 1)**(1.to_f / periods)) - 1)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
11
50
|
# create a new Rate instance
|
12
51
|
# @return [Rate]
|
13
52
|
# @param [Numeric] rate the decimal value of the interest rate
|
@@ -37,9 +76,6 @@ module Finrb
|
|
37
76
|
end
|
38
77
|
end
|
39
78
|
|
40
|
-
# Accepted rate types
|
41
|
-
TYPES = { apr: "effective", apy: "effective", effective: "effective", nominal: "nominal" }.freeze
|
42
|
-
|
43
79
|
# @return [Integer] the duration for which the rate is valid, in months
|
44
80
|
# @api public
|
45
81
|
attr_accessor :duration
|
@@ -52,7 +88,7 @@ module Finrb
|
|
52
88
|
|
53
89
|
# compare two Rates, using the effective rate
|
54
90
|
# @return [Numeric] one of -1, 0, +1
|
55
|
-
# @param [Rate]
|
91
|
+
# @param [Rate] other the comparison Rate
|
56
92
|
# @example Which is better, a nominal rate of 15% compounded monthly, or 15.5% compounded semiannually?
|
57
93
|
# r1 = Rate.new(0.15, :nominal) #=> Rate.new(0.160755, :apr)
|
58
94
|
# r2 = Rate.new(0.155, :nominal, :compounds => :semiannually) #=> Rate.new(0.161006, :apr)
|
@@ -125,43 +161,6 @@ module Finrb
|
|
125
161
|
@effective = Rate.to_effective(rate, @periods)
|
126
162
|
end
|
127
163
|
|
128
|
-
# convert a nominal interest rate to an effective interest rate
|
129
|
-
# @return [DecNum] the effective interest rate
|
130
|
-
# @param [Numeric] rate the nominal interest rate
|
131
|
-
# @param [Numeric] periods the number of compounding periods per year
|
132
|
-
# @example
|
133
|
-
# Rate.to_effective(0.05, 4) #=> DecNum('0.05095')
|
134
|
-
# @api public
|
135
|
-
def self.to_effective(rate, periods)
|
136
|
-
rate = Flt::DecNum.new(rate.to_s)
|
137
|
-
periods = Flt::DecNum.new(periods.to_s)
|
138
|
-
|
139
|
-
if periods.infinite?
|
140
|
-
rate.exp - 1
|
141
|
-
else
|
142
|
-
((1 + (rate / periods))**periods) - 1
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
# convert an effective interest rate to a nominal interest rate
|
147
|
-
# @return [DecNum] the nominal interest rate
|
148
|
-
# @param [Numeric] rate the effective interest rate
|
149
|
-
# @param [Numeric] periods the number of compounding periods per year
|
150
|
-
# @example
|
151
|
-
# Rate.to_nominal(0.06, 365) #=> DecNum('0.05827')
|
152
|
-
# @see https://www.miniwebtool.com/nominal-interest-rate-calculator/
|
153
|
-
# @api public
|
154
|
-
def self.to_nominal(rate, periods)
|
155
|
-
rate = Flt::DecNum.new(rate.to_s)
|
156
|
-
periods = Flt::DecNum.new(periods.to_s)
|
157
|
-
|
158
|
-
if periods.infinite?
|
159
|
-
(rate + 1).log
|
160
|
-
else
|
161
|
-
periods * (((1 + rate)**(1 / periods)) - 1)
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
164
|
private :compounds=, :effective=, :nominal=
|
166
165
|
end
|
167
166
|
end
|