finrb 0.0.1 → 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 +4 -4
- data/.github/dependabot.yml +7 -0
- data/.github/issue_template.md +15 -0
- data/.github/pull_request_template.md +17 -0
- data/.github/workflows/ci.yml +21 -0
- data/.github/workflows/codeql.yml +72 -0
- data/.github/workflows/rubocop.yml +24 -0
- data/.rubocop.yml +270 -5
- data/.semver +2 -2
- data/CHANGELOG.md +35 -26
- data/Gemfile +1 -1
- data/Gemfile.lock +5 -2
- data/README.md +5 -1
- data/Rakefile +10 -10
- data/docs/api.md +238 -256
- data/finrb.gemspec +24 -80
- data/lib/finrb/amortization.rb +7 -7
- data/lib/finrb/cashflows.rb +40 -41
- data/lib/finrb/config.rb +1 -1
- data/lib/finrb/decimal.rb +2 -2
- data/lib/finrb/rates.rb +5 -5
- data/lib/finrb/transaction.rb +1 -1
- data/lib/finrb/utils.rb +78 -77
- data/lib/finrb.rb +9 -9
- metadata +25 -63
- data/.travis.yml +0 -7
data/finrb.gemspec
CHANGED
@@ -2,97 +2,41 @@
|
|
2
2
|
|
3
3
|
SPEC =
|
4
4
|
Gem::Specification.new do |s|
|
5
|
-
s.name =
|
6
|
-
s.version =
|
7
|
-
s.authors = [
|
8
|
-
s.license =
|
9
|
-
s.email = [
|
5
|
+
s.name = "finrb"
|
6
|
+
s.version = "0.1.0"
|
7
|
+
s.authors = ["Nadir Cohen", "Martin Bjeldbak Madsen", "Bill Kranec"]
|
8
|
+
s.license = "LGPL-3.0"
|
9
|
+
s.email = ["nadircs11@gmail.com", "me@martinbjeldbak.com", "wkranec@gmail.com"]
|
10
10
|
s.platform = Gem::Platform::RUBY
|
11
|
-
s.summary =
|
11
|
+
s.summary = "Ruby gem for financial calculations/modeling"
|
12
12
|
|
13
13
|
s.description = <<~EOF
|
14
|
-
The finrb library (forked from the finance gem) provides a Ruby interface for financial calculations/modeling.
|
15
|
-
|
16
|
-
- Working with interest rates
|
17
|
-
- Mortgage amortization
|
18
|
-
- Cashflows (NPV, IRR, etc.)
|
19
|
-
- Computing bank discount yield (BDY) for a T-bill
|
20
|
-
- Computing money market yield (MMY) for a T-bill
|
21
|
-
- Cash ratio - Liquidity ratios measure the firm's ability to satisfy its short-term obligations as they come due.
|
22
|
-
- Computing Coefficient of variation
|
23
|
-
- Cost of goods sold and ending inventory under three methods (FIFO,LIFO,Weighted average)
|
24
|
-
- Current ratio - Liquidity ratios measure the firm's ability to satisfy its short-term obligations as they come due.
|
25
|
-
- 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.
|
26
|
-
- Debt ratio - Solvency ratios measure the firm's ability to satisfy its long-term obligations.
|
27
|
-
- Diluted Earnings Per Share
|
28
|
-
- Computing the rate of return for each period
|
29
|
-
- Convert stated annual rate to the effective annual rate
|
30
|
-
- Convert stated annual rate to the effective annual rate with continuous compounding
|
31
|
-
- Bond-equivalent yield (BEY), 2 x the semiannual discount rate
|
32
|
-
- Computing HPR, the holding period return
|
33
|
-
- Equivalent/proportional Interest Rates
|
34
|
-
- Basic Earnings Per Share
|
35
|
-
- Financial leverage - Solvency ratios measure the firm's ability to satisfy its long-term obligations.
|
36
|
-
- Estimate future value (fv)
|
37
|
-
- Estimate future value of an annuity
|
38
|
-
- Estimate future value (fv) of a single sum
|
39
|
-
- Computing the future value of an uneven cash flow series
|
40
|
-
- Geometric mean return
|
41
|
-
- Gross profit margin - Evaluate a company's financial performance
|
42
|
-
- Harmonic mean, average price
|
43
|
-
- Computing HPR, the holding period return
|
44
|
-
- Bond-equivalent yield (BEY), 2 x the semiannual discount rate
|
45
|
-
- Convert holding period return to the effective annual rate
|
46
|
-
- Computing money market yield (MMY) for a T-bill
|
47
|
-
- Computing IRR, the internal rate of return
|
48
|
-
- Calculate the net increase in common shares from the potential exercise of stock options or warrants
|
49
|
-
- Long-term debt-to-equity - Solvency ratios measure the firm's ability to satisfy its long-term obligations.
|
50
|
-
- Computing HPR, the holding period return
|
51
|
-
- Estimate the number of periods
|
52
|
-
- Net profit margin - Evaluate a company's financial performance
|
53
|
-
- Computing NPV, the PV of the cash flows less the initial (time = 0) outlay
|
54
|
-
- Estimate period payment
|
55
|
-
- Estimate present value (pv)
|
56
|
-
- Estimate present value (pv) of an annuity
|
57
|
-
- Estimate present value of a perpetuity
|
58
|
-
- Estimate present value (pv) of a single sum
|
59
|
-
- Computing the present value of an uneven cash flow series
|
60
|
-
- Quick ratio - Liquidity ratios measure the firm's ability to satisfy its short-term obligations as they come due.
|
61
|
-
- Convert a given norminal rate to a continuous compounded rate
|
62
|
-
- Convert a given continuous compounded rate to a norminal rate
|
63
|
-
- Rate of return for a perpetuity
|
64
|
-
- Computing Sampling error
|
65
|
-
- Computing Roy's safety-first ratio
|
66
|
-
- Computing Sharpe Ratio
|
67
|
-
- Depreciation Expense Recognition - Straight-line depreciation (SL) allocates an equal amount of depreciation each year over the asset's useful life
|
68
|
-
- Total debt-to-equity - Solvency ratios measure the firm's ability to satisfy its long-term obligations.
|
69
|
-
- Computing TWRR, the time-weighted rate of return
|
70
|
-
- Calculate weighted average shares - weighted average number of common shares
|
71
|
-
- Weighted mean as a portfolio return
|
14
|
+
The finrb library (forked from the finance gem) provides a Ruby interface for financial calculations/modeling. Working with interest rates, Mortgage amortization, Cashflows (NPV, IRR, etc.) and other basic utilities.
|
72
15
|
|
73
16
|
EOF
|
74
17
|
|
75
|
-
s.homepage =
|
18
|
+
s.homepage = "https://rubygems.org/gems/finrb"
|
76
19
|
|
77
|
-
s.required_ruby_version =
|
20
|
+
s.required_ruby_version = ">= 3.0"
|
78
21
|
|
79
|
-
s.add_dependency(
|
80
|
-
s.add_dependency(
|
81
|
-
s.add_dependency(
|
22
|
+
s.add_dependency("activesupport")
|
23
|
+
s.add_dependency("business_time")
|
24
|
+
s.add_dependency("flt")
|
82
25
|
|
83
|
-
s.add_development_dependency(
|
84
|
-
s.add_development_dependency(
|
85
|
-
s.add_development_dependency(
|
86
|
-
s.add_development_dependency(
|
87
|
-
s.add_development_dependency(
|
88
|
-
s.add_development_dependency(
|
89
|
-
s.add_development_dependency(
|
90
|
-
s.add_development_dependency(
|
91
|
-
s.add_development_dependency(
|
26
|
+
s.add_development_dependency("minitest")
|
27
|
+
s.add_development_dependency("pry")
|
28
|
+
s.add_development_dependency("rake")
|
29
|
+
s.add_development_dependency("rubocop")
|
30
|
+
s.add_development_dependency("rubocop-minitest")
|
31
|
+
s.add_development_dependency("rubocop-performance")
|
32
|
+
s.add_development_dependency("rubocop-packaging")
|
33
|
+
s.add_development_dependency("rubocop-rake")
|
34
|
+
s.add_development_dependency("semver")
|
35
|
+
s.add_development_dependency("solargraph")
|
92
36
|
|
93
37
|
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
94
38
|
|
95
|
-
s.extra_rdoc_files = [
|
39
|
+
s.extra_rdoc_files = ["README.md", "COPYING", "COPYING.LESSER", "CHANGELOG.md"]
|
96
40
|
|
97
|
-
s.metadata[
|
41
|
+
s.metadata["rubygems_mfa_required"] = "true"
|
98
42
|
end
|
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.
|
@@ -67,7 +67,7 @@ module Finrb
|
|
67
67
|
# amt.additional_payments #=> [DecNum('-100.00'), DecNum('-100.00'), ... ]
|
68
68
|
# @api public
|
69
69
|
def additional_payments
|
70
|
-
@transactions.
|
70
|
+
@transactions.filter_map { |trans| trans.difference if trans.payment? }
|
71
71
|
end
|
72
72
|
|
73
73
|
# amortize the balance of loan with the given interest rate
|
@@ -155,7 +155,7 @@ module Finrb
|
|
155
155
|
# amt.interest[0,6].sum #=> DecNum('5603.74')
|
156
156
|
# @api public
|
157
157
|
def interest
|
158
|
-
@transactions.
|
158
|
+
@transactions.filter_map { |trans| trans.amount if trans.interest? }
|
159
159
|
end
|
160
160
|
|
161
161
|
# @return [DecNum] the periodic payment due on a loan
|
@@ -167,7 +167,7 @@ module Finrb
|
|
167
167
|
# rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
|
168
168
|
# rate.duration #=> 360
|
169
169
|
# Amortization.payment(200000, rate.monthly, rate.duration) #=> DecNum('-926.23')
|
170
|
-
# @see
|
170
|
+
# @see https://en.wikipedia.org/wiki/Amortization_calculator
|
171
171
|
# @api public
|
172
172
|
def self.payment(principal, rate, periods)
|
173
173
|
if rate.zero?
|
@@ -185,7 +185,7 @@ module Finrb
|
|
185
185
|
# amt.payments.sum #=> DecNum('-500163.94')
|
186
186
|
# @api public
|
187
187
|
def payments
|
188
|
-
@transactions.
|
188
|
+
@transactions.filter_map { |trans| trans.amount if trans.payment? }
|
189
189
|
end
|
190
190
|
end
|
191
191
|
end
|
data/lib/finrb/cashflows.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
3
|
+
require_relative "decimal"
|
4
|
+
require_relative "rates"
|
5
5
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
6
|
+
require "bigdecimal"
|
7
|
+
require "bigdecimal/newton"
|
8
|
+
require "business_time"
|
9
9
|
include Newton
|
10
10
|
|
11
11
|
module Finrb
|
@@ -15,7 +15,7 @@ module Finrb
|
|
15
15
|
# Base class for working with Newton's Method.
|
16
16
|
# @api private
|
17
17
|
class Function
|
18
|
-
values = { eps: Finrb.config.eps, one:
|
18
|
+
values = { eps: Finrb.config.eps, one: "1.0", two: "2.0", ten: "10.0", zero: "0.0" }
|
19
19
|
|
20
20
|
values.each do |key, value|
|
21
21
|
define_method key do
|
@@ -43,12 +43,12 @@ module Finrb
|
|
43
43
|
# @param [Numeric] Initial guess rate, Defaults to 1.0
|
44
44
|
# @example
|
45
45
|
# [-4000,1200,1410,1875,1050].irr #=> 0.143
|
46
|
-
# @see
|
46
|
+
# @see https://en.wikipedia.org/wiki/Internal_rate_of_return
|
47
47
|
# @api public
|
48
48
|
def irr(guess = nil)
|
49
49
|
# Make sure we have a valid sequence of cash flows.
|
50
50
|
positives, negatives = partition { |i| i >= 0 }
|
51
|
-
raise(ArgumentError,
|
51
|
+
raise(ArgumentError, "Calculation does not converge.") if positives.empty? || negatives.empty?
|
52
52
|
|
53
53
|
func = Function.new(self, :npv)
|
54
54
|
rate = [valid(guess)]
|
@@ -57,7 +57,7 @@ module Finrb
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def method_missing(name, *args, &block)
|
60
|
-
return sum if name.to_s ==
|
60
|
+
return sum if name.to_s == "sum"
|
61
61
|
|
62
62
|
super
|
63
63
|
end
|
@@ -67,7 +67,7 @@ module Finrb
|
|
67
67
|
# @param [Numeric] rate the discount rate to be applied
|
68
68
|
# @example
|
69
69
|
# [-100.0, 60, 60, 60].npv(0.1) #=> 49.211
|
70
|
-
# @see
|
70
|
+
# @see https://en.wikipedia.org/wiki/Net_present_value
|
71
71
|
# @api public
|
72
72
|
def npv(rate)
|
73
73
|
cashflows = map { |entry| Flt::DecNum.new(entry.to_s) }
|
@@ -97,7 +97,7 @@ module Finrb
|
|
97
97
|
if positives.empty? || negatives.empty?
|
98
98
|
raise(
|
99
99
|
ArgumentError,
|
100
|
-
|
100
|
+
"Calculation does not converge. Cashflow needs to have a least one positive and one negative value."
|
101
101
|
)
|
102
102
|
end
|
103
103
|
|
@@ -125,44 +125,43 @@ module Finrb
|
|
125
125
|
end
|
126
126
|
|
127
127
|
private
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
134
|
end
|
135
|
-
end
|
136
135
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
136
|
+
def days_in_period
|
137
|
+
if Finrb.config.periodic_compound && Finrb.config.business_days
|
138
|
+
start.to_date.business_days_until(stop).to_f
|
139
|
+
else
|
140
|
+
Flt::DecNum.new(365.days.to_s)
|
141
|
+
end
|
142
142
|
end
|
143
|
-
end
|
144
143
|
|
145
|
-
|
146
|
-
|
147
|
-
|
144
|
+
def start
|
145
|
+
@start ||= self[0].date
|
146
|
+
end
|
148
147
|
|
149
|
-
|
150
|
-
|
151
|
-
|
148
|
+
def stop
|
149
|
+
@stop ||= self[-1].date.to_date
|
150
|
+
end
|
152
151
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
152
|
+
def valid(guess)
|
153
|
+
if guess.nil?
|
154
|
+
unless Finrb.config.guess.is_a?(Numeric)
|
155
|
+
raise(ArgumentError, "Invalid Guess. Default guess should be a [Numeric] value.")
|
156
|
+
end
|
158
157
|
|
159
|
-
|
160
|
-
|
161
|
-
|
158
|
+
Finrb.config.guess
|
159
|
+
else
|
160
|
+
raise(ArgumentError, "Invalid Guess. Use a [Numeric] value.") unless guess.is_a?(Numeric)
|
162
161
|
|
163
|
-
|
164
|
-
|
165
|
-
|
162
|
+
guess
|
163
|
+
end.to_f
|
164
|
+
end
|
166
165
|
end
|
167
166
|
end
|
168
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
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.
|
@@ -17,8 +17,8 @@ module Finrb
|
|
17
17
|
# @option opts [String] :compounds (:monthly) the number of compounding periods per year
|
18
18
|
# @example create a 3.5% APR rate
|
19
19
|
# Rate.new(0.035, :apr) #=> Rate(0.035, :apr)
|
20
|
-
# @see
|
21
|
-
# @see
|
20
|
+
# @see https://en.wikipedia.org/wiki/Effective_interest_rate
|
21
|
+
# @see https://en.wikipedia.org/wiki/Nominal_interest_rate
|
22
22
|
# @api public
|
23
23
|
def initialize(rate, type, opts = {})
|
24
24
|
# Default monthly compounding.
|
@@ -38,7 +38,7 @@ module Finrb
|
|
38
38
|
end
|
39
39
|
|
40
40
|
# Accepted rate types
|
41
|
-
TYPES = { apr:
|
41
|
+
TYPES = { apr: "effective", apy: "effective", effective: "effective", nominal: "nominal" }.freeze
|
42
42
|
|
43
43
|
# @return [Integer] the duration for which the rate is valid, in months
|
44
44
|
# @api public
|
@@ -149,7 +149,7 @@ module Finrb
|
|
149
149
|
# @param [Numeric] periods the number of compounding periods per year
|
150
150
|
# @example
|
151
151
|
# Rate.to_nominal(0.06, 365) #=> DecNum('0.05827')
|
152
|
-
# @see
|
152
|
+
# @see https://www.miniwebtool.com/nominal-interest-rate-calculator/
|
153
153
|
# @api public
|
154
154
|
def self.to_nominal(rate, periods)
|
155
155
|
rate = Flt::DecNum.new(rate.to_s)
|