finrb 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|