finrb 1.1.0 → 1.2.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/CHANGELOG.md +25 -0
- data/CONTRIBUTING.md +89 -0
- data/README.md +4 -2
- data/SECURITY.md +41 -0
- data/lib/finrb/accounting.rb +93 -74
- data/lib/finrb/amortization.rb +6 -10
- data/lib/finrb/cashflows.rb +4 -7
- data/lib/finrb/config.rb +5 -5
- data/lib/finrb/rates.rb +3 -7
- data/lib/finrb/ratios.rb +41 -40
- data/lib/finrb/returns.rb +49 -52
- data/lib/finrb/tvm.rb +154 -52
- data/lib/finrb/validation.rb +42 -0
- data/lib/finrb/version.rb +1 -1
- data/lib/finrb/yields.rb +42 -31
- data/sig/finrb.rbs +2 -2
- metadata +17 -1
data/lib/finrb/validation.rb
CHANGED
|
@@ -23,5 +23,47 @@ module Finrb
|
|
|
23
23
|
|
|
24
24
|
value
|
|
25
25
|
end
|
|
26
|
+
|
|
27
|
+
def positive_decimal(value, name:, error: ArgumentError, message: nil)
|
|
28
|
+
decimal = decimal(value, name:)
|
|
29
|
+
raise(error, message || "#{name} must be greater than zero.") unless decimal.positive?
|
|
30
|
+
|
|
31
|
+
decimal
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def non_negative_decimal(value, name:, error: ArgumentError, message: nil)
|
|
35
|
+
decimal = decimal(value, name:)
|
|
36
|
+
raise(error, message || "#{name} must be greater than or equal to zero.") if decimal.negative?
|
|
37
|
+
|
|
38
|
+
decimal
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def decimal_greater_than(value, minimum:, name:, error: ArgumentError)
|
|
42
|
+
decimal = decimal(value, name:)
|
|
43
|
+
raise(error, "#{name} must be greater than #{minimum}.") if decimal <= minimum
|
|
44
|
+
|
|
45
|
+
decimal
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def decimal_at_least(value, minimum:, name:, error: ArgumentError)
|
|
49
|
+
decimal = decimal(value, name:)
|
|
50
|
+
raise(error, "#{name} must be greater than or equal to #{minimum}.") if decimal < minimum
|
|
51
|
+
|
|
52
|
+
decimal
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def non_zero_decimal(value, name:, error: ArgumentError)
|
|
56
|
+
decimal = decimal(value, name:)
|
|
57
|
+
raise(error, "#{name} must be non-zero.") if decimal.zero?
|
|
58
|
+
|
|
59
|
+
decimal
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def decimal_between(value, minimum:, maximum:, name:, error: ArgumentError)
|
|
63
|
+
decimal = decimal(value, name:)
|
|
64
|
+
raise(error, "#{name} must be between #{minimum} and #{maximum}, inclusive.") unless decimal.between?(minimum, maximum)
|
|
65
|
+
|
|
66
|
+
decimal
|
|
67
|
+
end
|
|
26
68
|
end
|
|
27
69
|
end
|
data/lib/finrb/version.rb
CHANGED
data/lib/finrb/yields.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'decimal'
|
|
4
4
|
require_relative 'errors'
|
|
5
|
+
require_relative 'validation'
|
|
5
6
|
|
|
6
7
|
module Finrb
|
|
7
8
|
# Money-market yield and interest-rate conversion calculations.
|
|
@@ -14,9 +15,9 @@ module Finrb
|
|
|
14
15
|
# @example
|
|
15
16
|
# Finrb::Yields.bdy(d=1500,f=100000,t=120)
|
|
16
17
|
def self.bdy(d:, f:, t:)
|
|
17
|
-
d =
|
|
18
|
-
f =
|
|
19
|
-
t =
|
|
18
|
+
d = Validation.decimal(d, name: 'dollar discount')
|
|
19
|
+
f = Validation.positive_decimal(f, name: 'face value', error: DomainError)
|
|
20
|
+
t = Validation.positive_decimal(t, name: 'time to maturity', error: DomainError)
|
|
20
21
|
|
|
21
22
|
(d * 360 / f / t)
|
|
22
23
|
end
|
|
@@ -28,10 +29,12 @@ module Finrb
|
|
|
28
29
|
# @example
|
|
29
30
|
# Finrb::Yields.bdy2mmy(bdy=0.045,t=120)
|
|
30
31
|
def self.bdy2mmy(bdy:, t:)
|
|
31
|
-
bdy =
|
|
32
|
-
t =
|
|
32
|
+
bdy = Validation.decimal(bdy, name: 'bank discount yield')
|
|
33
|
+
t = Validation.positive_decimal(t, name: 'time to maturity', error: DomainError)
|
|
34
|
+
denominator = 360 - (t * bdy)
|
|
35
|
+
raise(DomainError, 'Bank discount yield and time to maturity must imply a positive purchase price.') unless denominator.positive?
|
|
33
36
|
|
|
34
|
-
(bdy * 360 /
|
|
37
|
+
(bdy * 360 / denominator)
|
|
35
38
|
end
|
|
36
39
|
|
|
37
40
|
# Convert stated annual rate to the effective annual rate
|
|
@@ -44,10 +47,10 @@ module Finrb
|
|
|
44
47
|
# @example
|
|
45
48
|
# Finrb::Yields.ear(0.04,365)
|
|
46
49
|
def self.ear(r:, m:)
|
|
47
|
-
r =
|
|
48
|
-
m =
|
|
50
|
+
r = Validation.decimal(r, name: 'stated annual rate')
|
|
51
|
+
m = Validation.positive_decimal(m, name: 'compounding periods', error: DomainError)
|
|
49
52
|
|
|
50
|
-
(((
|
|
53
|
+
((compounding_base(r, m)**m) - 1)
|
|
51
54
|
end
|
|
52
55
|
|
|
53
56
|
# Convert stated annual rate to the effective annual rate with continuous compounding
|
|
@@ -59,7 +62,7 @@ module Finrb
|
|
|
59
62
|
# @example
|
|
60
63
|
# Finrb::Yields.ear_continuous(0.03)
|
|
61
64
|
def self.ear_continuous(r:)
|
|
62
|
-
r =
|
|
65
|
+
r = Validation.decimal(r, name: 'stated annual rate')
|
|
63
66
|
|
|
64
67
|
(r.exp - 1)
|
|
65
68
|
end
|
|
@@ -70,7 +73,7 @@ module Finrb
|
|
|
70
73
|
# @example
|
|
71
74
|
# Finrb::Yields.ear2bey(ear=0.08)
|
|
72
75
|
def self.ear2bey(ear:)
|
|
73
|
-
ear =
|
|
76
|
+
ear = Validation.decimal_at_least(ear, minimum: -1, name: 'effective annual rate', error: DomainError)
|
|
74
77
|
|
|
75
78
|
(((ear + 1).sqrt - 1) * 2)
|
|
76
79
|
end
|
|
@@ -82,8 +85,8 @@ module Finrb
|
|
|
82
85
|
# @example
|
|
83
86
|
# Finrb::Yields.ear2hpr(ear=0.05039,t=150)
|
|
84
87
|
def self.ear2hpr(ear:, t:)
|
|
85
|
-
ear =
|
|
86
|
-
t =
|
|
88
|
+
ear = Validation.decimal_at_least(ear, minimum: -1, name: 'effective annual rate', error: DomainError)
|
|
89
|
+
t = Validation.positive_decimal(t, name: 'time to maturity', error: DomainError)
|
|
87
90
|
|
|
88
91
|
(((ear + 1)**(t / 365)) - 1)
|
|
89
92
|
end
|
|
@@ -124,18 +127,18 @@ module Finrb
|
|
|
124
127
|
# # monthly proportional interest rate which is equivalent to a simple annual interest
|
|
125
128
|
# Finrb::Yields.eir(r=0.05,p=12,type='p')
|
|
126
129
|
def self.eir(r:, n: 1, p: 12, type: 'e')
|
|
127
|
-
r =
|
|
128
|
-
n =
|
|
129
|
-
p =
|
|
130
|
+
r = Validation.decimal(r, name: 'annual rate')
|
|
131
|
+
n = Validation.positive_decimal(n, name: 'source compounding periods', error: DomainError)
|
|
132
|
+
p = Validation.positive_decimal(p, name: 'target compounding periods', error: DomainError)
|
|
130
133
|
type = type.to_s
|
|
131
134
|
|
|
132
135
|
case type
|
|
133
136
|
when 'e'
|
|
134
|
-
eir = ((
|
|
137
|
+
eir = (compounding_base(r, n)**(n / p)) - 1
|
|
135
138
|
when 'p'
|
|
136
139
|
eir = r / p
|
|
137
140
|
else
|
|
138
|
-
raise(
|
|
141
|
+
raise(ArgumentError, "conversion type must be 'e' (equivalent) or 'p' (proportional)")
|
|
139
142
|
end
|
|
140
143
|
eir
|
|
141
144
|
end
|
|
@@ -147,8 +150,8 @@ module Finrb
|
|
|
147
150
|
# @example
|
|
148
151
|
# Finrb::Yields.hpr2bey(hpr=0.02,t=3)
|
|
149
152
|
def self.hpr2bey(hpr:, t:)
|
|
150
|
-
hpr =
|
|
151
|
-
t =
|
|
153
|
+
hpr = Validation.decimal_at_least(hpr, minimum: -1, name: 'holding period return', error: DomainError)
|
|
154
|
+
t = Validation.positive_decimal(t, name: 'time to maturity', error: DomainError)
|
|
152
155
|
|
|
153
156
|
((((hpr + 1)**(6 / t)) - 1) * 2)
|
|
154
157
|
end
|
|
@@ -160,8 +163,8 @@ module Finrb
|
|
|
160
163
|
# @example
|
|
161
164
|
# Finrb::Yields.hpr2ear(hpr=0.015228,t=120)
|
|
162
165
|
def self.hpr2ear(hpr:, t:)
|
|
163
|
-
hpr =
|
|
164
|
-
t =
|
|
166
|
+
hpr = Validation.decimal_at_least(hpr, minimum: -1, name: 'holding period return', error: DomainError)
|
|
167
|
+
t = Validation.positive_decimal(t, name: 'time to maturity', error: DomainError)
|
|
165
168
|
|
|
166
169
|
(((hpr + 1)**(365 / t)) - 1)
|
|
167
170
|
end
|
|
@@ -173,8 +176,8 @@ module Finrb
|
|
|
173
176
|
# @example
|
|
174
177
|
# Finrb::Yields.hpr2mmy(hpr=0.01523,t=120)
|
|
175
178
|
def self.hpr2mmy(hpr:, t:)
|
|
176
|
-
hpr =
|
|
177
|
-
t =
|
|
179
|
+
hpr = Validation.decimal(hpr, name: 'holding period return')
|
|
180
|
+
t = Validation.positive_decimal(t, name: 'time to maturity', error: DomainError)
|
|
178
181
|
|
|
179
182
|
(hpr * 360 / t)
|
|
180
183
|
end
|
|
@@ -186,8 +189,8 @@ module Finrb
|
|
|
186
189
|
# @example
|
|
187
190
|
# Finrb::Yields.mmy2hpr(mmy=0.04898,t=150)
|
|
188
191
|
def self.mmy2hpr(mmy:, t:)
|
|
189
|
-
mmy =
|
|
190
|
-
t =
|
|
192
|
+
mmy = Validation.decimal(mmy, name: 'money market yield')
|
|
193
|
+
t = Validation.positive_decimal(t, name: 'time to maturity', error: DomainError)
|
|
191
194
|
|
|
192
195
|
(mmy * t / 360)
|
|
193
196
|
end
|
|
@@ -199,10 +202,10 @@ module Finrb
|
|
|
199
202
|
# @example
|
|
200
203
|
# Finrb::Yields.r_continuous(r=0.03,m=4)
|
|
201
204
|
def self.r_continuous(r:, m:)
|
|
202
|
-
r =
|
|
203
|
-
m =
|
|
205
|
+
r = Validation.decimal(r, name: 'nominal rate')
|
|
206
|
+
m = Validation.positive_decimal(m, name: 'compounding periods', error: DomainError)
|
|
204
207
|
|
|
205
|
-
(m * (
|
|
208
|
+
(m * compounding_base(r, m).log)
|
|
206
209
|
end
|
|
207
210
|
|
|
208
211
|
# Convert a given continuous compounded rate to a norminal rate
|
|
@@ -215,10 +218,18 @@ module Finrb
|
|
|
215
218
|
# @example
|
|
216
219
|
# Finrb::Yields.r_norminal(rc=0.03,m=4)
|
|
217
220
|
def self.r_norminal(rc:, m:)
|
|
218
|
-
rc =
|
|
219
|
-
m =
|
|
221
|
+
rc = Validation.decimal(rc, name: 'continuously compounded rate')
|
|
222
|
+
m = Validation.positive_decimal(m, name: 'compounding periods', error: DomainError)
|
|
220
223
|
|
|
221
224
|
(m * ((rc / m).exp - 1))
|
|
222
225
|
end
|
|
226
|
+
|
|
227
|
+
def self.compounding_base(rate, periods)
|
|
228
|
+
base = (rate / periods) + 1
|
|
229
|
+
raise(DomainError, 'The rate per compounding period must be greater than -1.') unless base.positive?
|
|
230
|
+
|
|
231
|
+
base
|
|
232
|
+
end
|
|
233
|
+
private_class_method :compounding_base
|
|
223
234
|
end
|
|
224
235
|
end
|
data/sig/finrb.rbs
CHANGED
|
@@ -150,7 +150,7 @@ module Finrb
|
|
|
150
150
|
end
|
|
151
151
|
|
|
152
152
|
module TVM
|
|
153
|
-
def self.discount_rate: (n: number, pv: number, fv: number, pmt: number, ?type: Integer, ?lower: number
|
|
153
|
+
def self.discount_rate: (n: number, pv: number, fv: number, pmt: number, ?type: Integer, ?guess: number?, ?lower: number?, ?upper: number?) -> decimal
|
|
154
154
|
def self.fv: (r: number, n: number, ?pv: number, ?pmt: number, ?type: Integer) -> decimal
|
|
155
155
|
def self.fv_annuity: (r: number, n: number, pmt: number, ?type: Integer) -> decimal
|
|
156
156
|
def self.fv_simple: (r: number, n: number, pv: number) -> decimal
|
|
@@ -169,7 +169,7 @@ module Finrb
|
|
|
169
169
|
module Accounting
|
|
170
170
|
type inventory_result = { cost_of_goods: decimal, ending_inventory: decimal }
|
|
171
171
|
|
|
172
|
-
def self.cogs: (uinv: number, pinv: number, units: number | numbers, price: number | numbers, sinv: number, ?method: String) -> inventory_result
|
|
172
|
+
def self.cogs: (uinv: number, pinv: number, units: number | numbers | nil, price: number | numbers | nil, sinv: number, ?method: String | Symbol) -> inventory_result
|
|
173
173
|
def self.ddb: (cost: number, rv: number, t: Integer) -> { t: Array[Integer], ddb: Array[decimal] }
|
|
174
174
|
def self.slde: (cost: number, rv: number, t: number) -> decimal
|
|
175
175
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: finrb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nadir Cohen
|
|
@@ -65,6 +65,20 @@ dependencies:
|
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: benchmark-ips
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
68
82
|
- !ruby/object:Gem::Dependency
|
|
69
83
|
name: bundler-audit
|
|
70
84
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -248,10 +262,12 @@ extra_rdoc_files:
|
|
|
248
262
|
- README.md
|
|
249
263
|
files:
|
|
250
264
|
- CHANGELOG.md
|
|
265
|
+
- CONTRIBUTING.md
|
|
251
266
|
- COPYING
|
|
252
267
|
- COPYING.LESSER
|
|
253
268
|
- NOTICE.md
|
|
254
269
|
- README.md
|
|
270
|
+
- SECURITY.md
|
|
255
271
|
- lib/finrb.rb
|
|
256
272
|
- lib/finrb/accounting.rb
|
|
257
273
|
- lib/finrb/amortization.rb
|