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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Finrb
4
- VERSION = '1.1.0'
4
+ VERSION = '1.2.0'
5
5
  public_constant :VERSION
6
6
  end
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 = Flt::DecNum(d.to_s)
18
- f = Flt::DecNum(f.to_s)
19
- t = Flt::DecNum(t.to_s)
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 = Flt::DecNum(bdy.to_s)
32
- t = Flt::DecNum(t.to_s)
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 / (360 - (t * bdy)))
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 = Flt::DecNum(r.to_s)
48
- m = Flt::DecNum(m.to_s)
50
+ r = Validation.decimal(r, name: 'stated annual rate')
51
+ m = Validation.positive_decimal(m, name: 'compounding periods', error: DomainError)
49
52
 
50
- ((((r / m) + 1)**m) - 1)
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 = Flt::DecNum(r.to_s)
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 = Flt::DecNum(ear.to_s)
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 = Flt::DecNum(ear.to_s)
86
- t = Flt::DecNum(t.to_s)
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 = Flt::DecNum(r.to_s)
128
- n = Flt::DecNum(n.to_s)
129
- p = Flt::DecNum(p.to_s)
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 = (((r / n) + 1)**(n / p)) - 1
137
+ eir = (compounding_base(r, n)**(n / p)) - 1
135
138
  when 'p'
136
139
  eir = r / p
137
140
  else
138
- raise(Error, "type must be 'e' or 'p'")
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 = Flt::DecNum(hpr.to_s)
151
- t = Flt::DecNum(t.to_s)
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 = Flt::DecNum(hpr.to_s)
164
- t = Flt::DecNum(t.to_s)
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 = Flt::DecNum(hpr.to_s)
177
- t = Flt::DecNum(t.to_s)
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 = Flt::DecNum(mmy.to_s)
190
- t = Flt::DecNum(t.to_s)
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 = Flt::DecNum(r.to_s)
203
- m = Flt::DecNum(m.to_s)
205
+ r = Validation.decimal(r, name: 'nominal rate')
206
+ m = Validation.positive_decimal(m, name: 'compounding periods', error: DomainError)
204
207
 
205
- (m * ((r / m) + 1).log)
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 = Flt::DecNum(rc.to_s)
219
- m = Flt::DecNum(m.to_s)
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, ?upper: number) -> decimal
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.1.0
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