finrb 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/array/wrap'
4
+ require_relative 'decimal'
5
+ require 'bigdecimal'
6
+ require 'bigdecimal/newton'
7
+
8
+ include Newton
9
+
10
+ module Finrb
11
+ class Utils
12
+ class NlFunctionStub
13
+ attr_accessor :func
14
+
15
+ values = { eps: Finrb.config.eps, one: '1.0', two: '2.0', ten: '10.0', zero: '0.0' }
16
+
17
+ values.each do |key, value|
18
+ define_method key do
19
+ BigDecimal(value)
20
+ end
21
+ end
22
+
23
+ def values(x)
24
+ @func.call(x)
25
+ end
26
+ end
27
+
28
+ # Computing bank discount yield (BDY) for a T-bill
29
+ #
30
+ # @param d the dollar discount, which is equal to the difference between the face value of the bill and the purchase price
31
+ # @param f the face value (par value) of the bill
32
+ # @param t number of days remaining until maturity
33
+ # @export
34
+ # @examples
35
+ # bdy(d=1500,f=100000,t=120)
36
+ def self.bdy(d, f, t)
37
+ d = d.to_d
38
+ f = f.to_d
39
+ t = t.to_d
40
+
41
+ (360 * d / f / t)
42
+ end
43
+
44
+ # Computing money market yield (MMY) for a T-bill
45
+ #
46
+ # @param bdy bank discount yield
47
+ # @param t number of days remaining until maturity
48
+ # @export
49
+ # @examples
50
+ # bdy2mmy(bdy=0.045,t=120)
51
+ def self.bdy2mmy(bdy, t)
52
+ bdy = bdy.to_d
53
+ t = t.to_d
54
+
55
+ (360 * bdy / (360 - (t * bdy)))
56
+ end
57
+
58
+ # cash ratio -- Liquidity ratios measure the firm's ability to satisfy its short-term obligations as they come due.
59
+ #
60
+ # @param cash cash
61
+ # @param ms marketable securities
62
+ # @param cl current liabilities
63
+ # @export
64
+ # @examples
65
+ # Finrb::Utils.cash_ratio(cash=3000,ms=2000,cl=2000)
66
+ def self.cash_ratio(cash, ms, cl)
67
+ cash = cash.to_d
68
+ ms = ms.to_d
69
+ cl = cl.to_d
70
+
71
+ ((cash + ms) / cl)
72
+ end
73
+
74
+ # Computing Coefficient of variation
75
+ #
76
+ # @param sd standard deviation
77
+ # @param avg average value
78
+ # @export
79
+ # @examples
80
+ # Finrb::Utils.coefficient_variation(sd=0.15,avg=0.39)
81
+ def self.coefficient_variation(sd, avg)
82
+ sd = sd.to_d
83
+ avg = avg.to_d
84
+
85
+ (sd / avg)
86
+ end
87
+
88
+ # Cost of goods sold and ending inventory under three methods (FIFO,LIFO,Weighted average)
89
+ #
90
+ # @param uinv units of beginning inventory
91
+ # @param pinv prince of beginning inventory
92
+ # @param units nx1 vector of inventory units. inventory purchased ordered by time (from first to last)
93
+ # @param price nx1 vector of inventory price. same order as units
94
+ # @param sinv units of sold inventory
95
+ # @param method inventory methods: FIFO (first in first out, permitted under both US and IFRS), LIFO (late in first out, US only), WAC (weighted average cost,US and IFRS)
96
+ # @export
97
+ # @examples
98
+ # cogs(uinv=2,pinv=2,units=c(3,5),price=c(3,5),sinv=7,method="FIFO")
99
+ #
100
+ # cogs(uinv=2,pinv=2,units=c(3,5),price=c(3,5),sinv=7,method="LIFO")
101
+ #
102
+ # cogs(uinv=2,pinv=2,units=c(3,5),price=c(3,5),sinv=7,method="WAC")
103
+ def self.cogs(uinv, pinv, units, price, sinv, method = 'FIFO')
104
+ uinv = uinv.to_d
105
+ pinv = pinv.to_d
106
+ units = Array.wrap(units).map(&:to_d)
107
+ price = Array.wrap(price).map(&:to_d)
108
+ sinv = sinv.to_d
109
+ method = method.to_s
110
+
111
+ n = units.size
112
+ m = price.size
113
+ costOfGoods = 0
114
+ endingInventory = 0
115
+ if m == n
116
+ case method
117
+ when 'FIFO'
118
+ if sinv <= uinv
119
+ costOfGoods = sinv * pinv
120
+ endingInventory = (uinv - sinv) * pinv
121
+ (0...n).each do |i|
122
+ endingInventory += (units[i] * price[i])
123
+ end
124
+ else
125
+ costOfGoods = uinv * pinv
126
+ sinv -= uinv
127
+ (0...n).each do |i|
128
+ if sinv <= units[i]
129
+ costOfGoods += (sinv * price[i])
130
+ endingInventory = (units[i] - sinv) * price[i]
131
+ if i < n
132
+ temp = i + 1
133
+ (temp...n).each do |j|
134
+ endingInventory += (units[j] * price[j])
135
+ end
136
+ end
137
+ sinv = 0
138
+ next
139
+ else
140
+ costOfGoods += (units[i] * price[i])
141
+ sinv -= units[i]
142
+ end
143
+ end
144
+ raise(FinrbError, "Inventory is not enough to sell\n") if sinv.positive?
145
+ end
146
+ when 'WAC'
147
+ endingInventory = uinv * pinv
148
+ tu = uinv
149
+ (0...n).each do |i|
150
+ endingInventory += (units[i] * price[i])
151
+ tu += units[i]
152
+ end
153
+ if tu >= sinv
154
+ costOfGoods = endingInventory / tu * sinv
155
+ endingInventory = endingInventory / tu * (tu - sinv)
156
+ else
157
+ raise(FinrbError, "Inventory is not enough to sell\n")
158
+ end
159
+
160
+ when 'LIFO'
161
+ (n - 1).downto(0).each do |i|
162
+ if sinv <= units[i]
163
+ costOfGoods += (sinv * price[i])
164
+ endingInventory = (units[i] - sinv) * price[i]
165
+ if i > 1
166
+ temp = i - 1
167
+ (temp).downto(0).each do |j|
168
+ endingInventory += (units[j] * price[j])
169
+ end
170
+ end
171
+ endingInventory += (uinv * pinv)
172
+ sinv = 0
173
+ next
174
+ else
175
+ costOfGoods += (units[i] * price[i])
176
+ sinv -= units[i]
177
+ end
178
+ end
179
+ if sinv.positive?
180
+ if sinv <= uinv
181
+ costOfGoods += (sinv * pinv)
182
+ endingInventory += ((uinv - sinv) * pinv)
183
+ else
184
+ raise(FinrbError, "Inventory is not enough to sell\n")
185
+ end
186
+ end
187
+ end
188
+
189
+ else
190
+ raise(FinrbError, "length of units and price are not the same\n")
191
+ end
192
+
193
+ [costOfGoods, endingInventory]
194
+ end
195
+
196
+ # current ratio -- Liquidity ratios measure the firm's ability to satisfy its short-term obligations as they come due.
197
+ #
198
+ # @param ca current assets
199
+ # @param cl current liabilities
200
+ # @export
201
+ # @examples
202
+ # Finrb::Utils.current_ratio(ca=8000,cl=2000)
203
+ def self.current_ratio(ca, cl)
204
+ ca = ca.to_d
205
+ cl = cl.to_d
206
+
207
+ (ca / cl)
208
+ end
209
+
210
+ # 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.
211
+ #
212
+ # @param cost cost of long-lived assets
213
+ # @param rv residual value of the long-lived assets at the end of its useful life. DDB does not explicitly use the asset's residual value in the calculations, but depreciation ends once the estimated residual value has been reached. If the asset is expected to have no residual value, the DB method will never fully depreciate it, so the DB method is typically changed to straight-line at some point in the asset's life.
214
+ # @param t length of the useful life
215
+ # @export
216
+ # @examples
217
+ # ddb(cost=1200,rv=200,t=5)
218
+ def self.ddb(cost, rv, t)
219
+ cost = cost.to_d
220
+ rv = rv.to_d
221
+ t = t.to_d
222
+
223
+ raise(FinrbError, 't should be larger than 1') if t < 2
224
+
225
+ ddb = [0] * t
226
+ ddb[0] = 2 * cost / t
227
+ if cost - ddb[0] <= rv
228
+ ddb[0] = cost - rv
229
+ else
230
+ cost -= ddb[0]
231
+ (1...t).each do |i|
232
+ ddb[i] = 2 * cost / t
233
+ if cost - ddb[i] <= rv
234
+ ddb[i] = cost - rv
235
+ break
236
+ else
237
+ cost -= ddb[i]
238
+ end
239
+ end
240
+ end
241
+ { t: (0...t).to_a, ddb: }
242
+ end
243
+
244
+ # debt ratio -- Solvency ratios measure the firm's ability to satisfy its long-term obligations.
245
+ #
246
+ # @param td total debt
247
+ # @param ta total assets
248
+ # @export
249
+ # @examples
250
+ # Finrb::Utils.debt_ratio(td=6000,ta=20000)
251
+ def self.debt_ratio(td, ta)
252
+ td = td.to_d
253
+ ta = ta.to_d
254
+
255
+ (td / ta)
256
+ end
257
+
258
+ # diluted Earnings Per Share
259
+ #
260
+ # @param ni net income
261
+ # @param pd preferred dividends
262
+ # @param cpd dividends on convertible preferred stock
263
+ # @param cdi interest on convertible debt
264
+ # @param tax tax rate
265
+ # @param w weighted average number of common shares outstanding
266
+ # @param cps shares from conversion of convertible preferred stock
267
+ # @param cds shares from conversion of convertible debt
268
+ # @param iss shares issuable from stock options
269
+ # @export
270
+ # @examples
271
+ # Finrb::Utils.diluted_eps(ni=115600,pd=10000,cdi=42000,tax=0.4,w=200000,cds=60000)
272
+ #
273
+ # Finrb::Utils.diluted_eps(ni=115600,pd=10000,cpd=10000,w=200000,cps=40000)
274
+ #
275
+ # Finrb::Utils.diluted_eps(ni=115600,pd=10000,w=200000,iss=2500)
276
+ #
277
+ # Finrb::Utils.diluted_eps(ni=115600,pd=10000,cpd=10000,cdi=42000,tax=0.4,w=200000,cps=40000,cds=60000,iss=2500)
278
+ def self.diluted_eps(ni, pd, w, cpd = 0, cdi = 0, tax = 0, cps = 0, cds = 0, iss = 0)
279
+ ni = ni.to_d
280
+ pd = pd.to_d
281
+ w = w.to_d
282
+ cpd = cpd.to_d
283
+ cdi = cdi.to_d
284
+ tax = tax.to_d
285
+ cps = cps.to_d
286
+ cds = cds.to_d
287
+ iss = iss.to_d
288
+
289
+ basic = (ni - pd) / w
290
+ diluted = (ni - pd + cpd + (cdi * (1 - tax))) / (w + cps + cds + iss)
291
+ diluted = (ni - pd + cpd) / (w + cps + iss) if diluted > basic
292
+ diluted
293
+ end
294
+
295
+ # Computing the rate of return for each period
296
+ #
297
+ # @param n number of periods
298
+ # @param pv present value
299
+ # @param fv future value
300
+ # @param pmt payment per period
301
+ # @param type payments occur at the end of each period (type=0); payments occur at the beginning of each period (type=1)
302
+ # @param lower the lower end points of the rate of return to be searched.
303
+ # @param upper the upper end points of the rate of return to be searched.
304
+ # @importFrom stats uniroot
305
+ # @export
306
+ # @examples
307
+ # Finrb::Utils.discount_rate(n=5,pv=0,fv=600,pmt=-100,type=0)
308
+ def self.discount_rate(n, pv, fv, pmt, type = 0, lower = 0.0001, upper = 100)
309
+ n = n.to_d
310
+ pv = pv.to_d
311
+ fv = fv.to_d
312
+ pmt = pmt.to_d
313
+ type = type.to_d
314
+ lower = lower.to_d
315
+ upper = upper.to_d
316
+
317
+ nlfunc = NlFunctionStub.new
318
+ nlfunc.func = lambda do |x|
319
+ BigDecimal((Finrb::Utils.fv_simple(x[0],n,pv) + Finrb::Utils.fv_annuity(x[0],n,pmt,type) - fv).to_s)
320
+ end
321
+
322
+ root = [(upper - lower) / 2]
323
+ nlsolve(nlfunc, root)
324
+ root[0]
325
+ end
326
+
327
+ # Convert stated annual rate to the effective annual rate
328
+ #
329
+ # @param r stated annual rate
330
+ # @param m number of compounding periods per year
331
+ # @export
332
+ # @examples
333
+ # ear(r=0.12,m=12)
334
+ #
335
+ # ear(0.04,365)
336
+ def self.ear(r, m)
337
+ r = r.to_d
338
+ m = m.to_d
339
+
340
+ (((1 + (r / m))**m) - 1)
341
+ end
342
+
343
+ # Convert stated annual rate to the effective annual rate with continuous compounding
344
+ #
345
+ # @param r stated annual rate
346
+ # @export
347
+ # @examples
348
+ # Finrb::Utils.ear_continuous(r=0.1)
349
+ #
350
+ # Finrb::Utils.ear_continuous(0.03)
351
+ def self.ear_continuous(r)
352
+ r = r.to_d
353
+
354
+ (r.to_d.exp - 1)
355
+ end
356
+
357
+ # bond-equivalent yield (BEY), 2 x the semiannual discount rate
358
+ #
359
+ # @param ear effective annual rate
360
+ # @export
361
+ # @examples
362
+ # ear2bey(ear=0.08)
363
+ def self.ear2bey(ear)
364
+ ear = ear.to_d
365
+
366
+ ((((1 + ear)**0.5) - 1) * 2)
367
+ end
368
+
369
+ # Computing HPR, the holding period return
370
+ #
371
+ # @param ear effective annual rate
372
+ # @param t number of days remaining until maturity
373
+ # @export
374
+ # @examples
375
+ # ear2hpr(ear=0.05039,t=150)
376
+ def self.ear2hpr(ear, t)
377
+ ear = ear.to_d
378
+ t = t.to_d
379
+
380
+ (((1 + ear)**(t / 365)) - 1)
381
+ end
382
+
383
+ # Equivalent/proportional Interest Rates
384
+ # @description An interest rate to be applied n times p.a. can be converted to an equivalent rate to be applied p times p.a.
385
+ # @param r interest rate to be applied n times per year (r is annual rate!)
386
+ # @param n times that the interest rate r were compounded per year
387
+ # @param p times that the equivalent rate were compounded per year
388
+ # @param type equivalent interest rates ('e',default) or proportional interest rates ('p')
389
+ # @export
390
+ # @examples
391
+ # # monthly interest rat equivalent to 5% compounded per year
392
+ # Finrb::Utils.eir(r=0.05,n=1,p=12)
393
+ #
394
+ # # monthly interest rat equivalent to 5% compounded per half year
395
+ # Finrb::Utils.eir(r=0.05,n=2,p=12)
396
+ #
397
+ # # monthly interest rat equivalent to 5% compounded per quarter
398
+ # Finrb::Utils.eir(r=0.05,n=4,p=12)
399
+ #
400
+ # # annual interest rate equivalent to 5% compounded per month
401
+ # Finrb::Utils.eir(r=0.05,n=12,p=1)
402
+ # # this is equivalent to
403
+ # ear(r=0.05,m=12)
404
+ #
405
+ # # quarter interest rate equivalent to 5% compounded per year
406
+ # Finrb::Utils.eir(r=0.05,n=1,p=4)
407
+ #
408
+ # # quarter interest rate equivalent to 5% compounded per month
409
+ # Finrb::Utils.eir(r=0.05,n=12,p=4)
410
+ #
411
+ # # monthly proportional interest rate which is equivalent to a simple annual interest
412
+ # Finrb::Utils.eir(r=0.05,p=12,type='p')
413
+ def self.eir(r, n = 1, p = 12, type = 'e')
414
+ r = r.to_d
415
+ n = n.to_d
416
+ p = p.to_d
417
+ type = type.to_s
418
+
419
+ case type
420
+ when 'e'
421
+ eir = ((1 + (r / n))**(n / p)) - 1
422
+ when 'p'
423
+ eir = r / p
424
+ else
425
+ raise(FinrbError, "type must be 'e' or 'p'")
426
+ end
427
+ eir
428
+ end
429
+
430
+ # Basic Earnings Per Share
431
+ #
432
+ # @param ni net income
433
+ # @param pd preferred dividends
434
+ # @param w weighted average number of common shares outstanding
435
+ # @export
436
+ # @examples
437
+ # Finrb::Utils.eps(ni=10000,pd=1000,w=11000)
438
+ def self.eps(ni, pd, w)
439
+ ni = ni.to_d
440
+ pd = pd.to_d
441
+ w = w.to_d
442
+
443
+ ((ni - pd) / w)
444
+ end
445
+
446
+ # financial leverage -- Solvency ratios measure the firm's ability to satisfy its long-term obligations.
447
+ #
448
+ # @param te total equity
449
+ # @param ta total assets
450
+ # @export
451
+ # @examples
452
+ # Finrb::Utils.financial_leverage(te=16000,ta=20000)
453
+ def self.financial_leverage(te, ta)
454
+ te = te.to_d
455
+ ta = ta.to_d
456
+
457
+ (ta / te)
458
+ end
459
+
460
+ # Estimate future value (fv)
461
+ #
462
+ # @param r discount rate, or the interest rate at which the amount will be compounded each period
463
+ # @param n number of periods
464
+ # @param pv present value
465
+ # @param pmt payment per period
466
+ # @param type payments occur at the end of each period (type=0); payments occur at the beginning of each period (type=1)
467
+ # @export
468
+ # @examples
469
+ # fv(r=0.07,n=10,pv=1000,pmt=10)
470
+ def self.fv(r, n, pv = 0, pmt = 0, type = 0)
471
+ r = r.to_d
472
+ n = n.to_d
473
+ pv = pv.to_d
474
+ pmt = pmt.to_d
475
+ type = type.to_d
476
+
477
+ if type != 0 && type != 1
478
+ raise(FinrbError, 'Error: type should be 0 or 1!')
479
+ else
480
+ (Finrb::Utils.fv_simple(r, n, pv) + Finrb::Utils.fv_annuity(r, n, pmt, type))
481
+ end
482
+ end
483
+
484
+ # Estimate future value of an annuity
485
+ #
486
+ # @param r discount rate, or the interest rate at which the amount will be compounded each period
487
+ # @param n number of periods
488
+ # @param pmt payment per period
489
+ # @param type payments occur at the end of each period (type=0); payments occur at the beginning of each period (type=1)
490
+ # @export
491
+ # @examples
492
+ # Finrb::Utils.fv_annuity(0.03,12,-1000)
493
+ #
494
+ # Finrb::Utils.fv_annuity(r=0.03,n=12,pmt=-1000,type=1)
495
+ def self.fv_annuity(r, n, pmt, type = 0)
496
+ r = r.to_d
497
+ n = n.to_d
498
+ pmt = pmt.to_d
499
+ type = type.to_d
500
+
501
+ if type != 0 && type != 1
502
+ raise(FinrbError, 'Error: type should be 0 or 1!')
503
+ else
504
+ (pmt / r * (((1 + r)**n) - 1)) * ((1 + r)**type) * -1
505
+
506
+ end
507
+ end
508
+
509
+ # Estimate future value (fv) of a single sum
510
+ #
511
+ # @param r discount rate, or the interest rate at which the amount will be compounded each period
512
+ # @param n number of periods
513
+ # @param pv present value
514
+ # @export
515
+ # @examples
516
+ # Finrb::Utils.fv_simple(0.08,10,-300)
517
+ #
518
+ # Finrb::Utils.fv_simple(r=0.04,n=20,pv=-50000)
519
+ def self.fv_simple(r, n, pv)
520
+ r = r.to_d
521
+ n = n.to_d
522
+ pv = pv.to_d
523
+
524
+ ((pv * ((1 + r)**n)) * -1)
525
+ end
526
+
527
+ # Computing the future value of an uneven cash flow series
528
+ #
529
+ # @param r stated annual rate
530
+ # @param cf uneven cash flow
531
+ # @export
532
+ # @examples
533
+ # Finrb::Utils.fv_uneven(r=0.1, cf=c(-1000, -500, 0, 4000, 3500, 2000))
534
+ def self.fv_uneven(r, cf)
535
+ r = r.to_d
536
+ cf = Array.wrap(cf).map(&:to_d)
537
+
538
+ m = cf.size
539
+ sum = 0
540
+ (0...m).each do |i|
541
+ n = m - i
542
+ sum += Finrb::Utils.fv_simple(r, n, cf[i])
543
+ end
544
+ sum
545
+ end
546
+
547
+ # Geometric mean return
548
+ #
549
+ # @param r returns over multiple periods
550
+ # @export
551
+ # @examples
552
+ # Finrb::Utils.geometric_mean(r=c(-0.0934, 0.2345, 0.0892))
553
+ def self.geometric_mean(r)
554
+ r = Array.wrap(r).map(&:to_d)
555
+
556
+ rs = r + 1
557
+ ((rs.reduce(:*)**(1 / rs.size)) - 1)
558
+ end
559
+
560
+ # gross profit margin -- Evaluate a company's financial performance
561
+ #
562
+ # @param gp gross profit, equal to revenue minus cost of goods sold (cogs)
563
+ # @param rv revenue (sales)
564
+ # @export
565
+ # @examples
566
+ # gpm(gp=1000,rv=20000)
567
+ def self.gpm(gp, rv)
568
+ gp = gp.to_d
569
+ rv = rv.to_d
570
+
571
+ (gp / rv)
572
+ end
573
+
574
+ # harmonic mean, average price
575
+ # @param p price over multiple periods
576
+ # @export
577
+ # @examples
578
+ # Finrb::Utils.harmonic_mean(p=c(8,9,10))
579
+ def self.harmonic_mean(p)
580
+ p = Array.wrap(p).map(&:to_d)
581
+
582
+ (1 / (p.sum { |val| 1 / val } / p.size))
583
+ end
584
+
585
+ # Computing HPR, the holding period return
586
+ #
587
+ # @param ev ending value
588
+ # @param bv beginning value
589
+ # @param cfr cash flow received
590
+ # @export
591
+ # @examples
592
+ # hpr(ev=33,bv=30,cfr=0.5)
593
+ def self.hpr(ev, bv, cfr = 0)
594
+ ev = ev.to_d
595
+ bv = bv.to_d
596
+ cfr = cfr.to_d
597
+
598
+ ((ev - bv + cfr) / bv)
599
+ end
600
+
601
+ # bond-equivalent yield (BEY), 2 x the semiannual discount rate
602
+ #
603
+ # @param hpr holding period return
604
+ # @param t number of month remaining until maturity
605
+ # @export
606
+ # @examples
607
+ # hpr2bey(hpr=0.02,t=3)
608
+ def self.hpr2bey(hpr, t)
609
+ hpr = hpr.to_d
610
+ t = t.to_d
611
+
612
+ ((((1 + hpr)**(6 / t)) - 1) * 2)
613
+ end
614
+
615
+ # Convert holding period return to the effective annual rate
616
+ #
617
+ # @param hpr holding period return
618
+ # @param t number of days remaining until maturity
619
+ # @export
620
+ # @examples
621
+ # hpr2ear(hpr=0.015228,t=120)
622
+ def self.hpr2ear(hpr, t)
623
+ hpr = hpr.to_d
624
+ t = t.to_d
625
+
626
+ (((1 + hpr)**(365 / t)) - 1)
627
+ end
628
+
629
+ # Computing money market yield (MMY) for a T-bill
630
+ #
631
+ # @param hpr holding period return
632
+ # @param t number of days remaining until maturity
633
+ # @export
634
+ # @examples
635
+ # hpr2mmy(hpr=0.01523,t=120)
636
+ def self.hpr2mmy(hpr, t)
637
+ hpr = hpr.to_d
638
+ t = t.to_d
639
+
640
+ (360 * hpr / t)
641
+ end
642
+
643
+ # Computing IRR, the internal rate of return
644
+ #
645
+ # @param cf cash flow,the first cash flow is the initial outlay
646
+ # @importFrom stats uniroot
647
+ # @export
648
+ # @examples
649
+ # irr(cf=c(-5, 1.6, 2.4, 2.8))
650
+ def self.irr(cf)
651
+ cf = Array.wrap(cf).map(&:to_d)
652
+
653
+ n = cf.size
654
+ subcf = cf.drop(1)
655
+
656
+ nlfunc = NlFunctionStub.new
657
+ nlfunc.func = lambda do |x|
658
+ BigDecimal((-1 * Finrb::Utils.pv_uneven(x[0], subcf) + cf[0]).to_s)
659
+ end
660
+
661
+ root = [0]
662
+ nlsolve(nlfunc, root)
663
+ root[0]
664
+ end
665
+
666
+ # Computing IRR, the internal rate of return
667
+ # @description This function is the same as irr but can calculate negative value. This function may take a very long time. You can use larger cutoff and larger step to get a less precision irr first. Then based on the result, change from and to, to narrow down the interval, and use a smaller step to get a more precision irr.
668
+ # @param cf cash flow,the first cash flow is the initial outlay
669
+ # @param cutoff threshold to take npv as zero
670
+ # @param from smallest irr to try
671
+ # @param to largest irr to try
672
+ # @param step increment of the irr
673
+ # @export
674
+ # @examples
675
+ # irr2(cf=c(-5, 1.6, 2.4, 2.8))
676
+ # irr2(cf=c(-200, 50, 60, -70, 30, 20))
677
+ def self.irr2(cf, cutoff = 0.1, from = -1, to = 10, step = 0.000001)
678
+ cf = Array.wrap(cf).map(&:to_d)
679
+ cutoff = cutoff.to_d
680
+ from = from.to_d
681
+ to = to.to_d
682
+ step = step.to_d
683
+
684
+ r0 = nil
685
+ n = cf.size
686
+ from.step((to - 1), step).each do |r|
687
+ npv = cf[0]
688
+ (1...n).each do |i|
689
+ npv += (cf[i] / ((1 + r)**(i - 1)))
690
+ end
691
+ next if npv.nil?
692
+
693
+ if npv.abs < cutoff
694
+ r0 = r
695
+ break
696
+ end
697
+ end
698
+
699
+ if r0.nil?
700
+ raise(
701
+ FinrbError,
702
+ 'can not find irr in the given interval, you can try smaller step, and/or larger to, and/or larger cutoff'
703
+ )
704
+ end
705
+
706
+ r0
707
+ end
708
+
709
+ # calculate the net increase in common shares from the potential exercise of stock options or warrants
710
+ #
711
+ # @param amp average market price over the year
712
+ # @param ep exercise price of the options or warrants
713
+ # @param n number of common shares that the options and warrants can be convened into
714
+ # @export
715
+ # @examples
716
+ # iss(amp=20,ep=15,n=10000)
717
+ def self.iss(amp, ep, n)
718
+ amp = amp.to_d
719
+ ep = ep.to_d
720
+ n = n.to_d
721
+
722
+ if amp > ep
723
+ ((amp - ep) * n / amp)
724
+ else
725
+ raise(FinrbError, 'amp must larger than ep')
726
+ end
727
+ end
728
+
729
+ # long-term debt-to-equity -- Solvency ratios measure the firm's ability to satisfy its long-term obligations.
730
+ #
731
+ # @param ltd long-term debt
732
+ # @param te total equity
733
+ # @export
734
+ # @examples
735
+ # Finrb::Utils.lt_d2e(ltd=8000,te=20000)
736
+ def self.lt_d2e(ltd, te)
737
+ ltd = ltd.to_d
738
+ te = te.to_d
739
+
740
+ (ltd / te)
741
+ end
742
+
743
+ # Computing HPR, the holding period return
744
+ #
745
+ # @param mmy money market yield
746
+ # @param t number of days remaining until maturity
747
+ # @export
748
+ # @examples
749
+ # mmy2hpr(mmy=0.04898,t=150)
750
+ def self.mmy2hpr(mmy, t)
751
+ mmy = mmy.to_d
752
+ t = t.to_d
753
+
754
+ (mmy * t / 360)
755
+ end
756
+
757
+ # Estimate the number of periods
758
+ #
759
+ # @param r discount rate, or the interest rate at which the amount will be compounded each period
760
+ # @param pv present value
761
+ # @param fv future value
762
+ # @param pmt payment per period
763
+ # @param type payments occur at the end of each period (type=0); payments occur at the beginning of each period (type=1)
764
+ # @export
765
+ # @examples
766
+ # Finrb::Utils.n_period(0.1,-10000,60000000,-50000,0)
767
+ #
768
+ # Finrb::Utils.n_period(r=0.1,pv=-10000,fv=60000000,pmt=-50000,type=1)
769
+ def self.n_period(r, pv, fv, pmt, type = 0)
770
+ r = r.to_d
771
+ pv = pv.to_d
772
+ fv = fv.to_d
773
+ pmt = pmt.to_d
774
+ type = type.to_d
775
+
776
+ if type != 0 && type != 1
777
+ raise(FinrbError, 'Error: type should be 0 or 1!')
778
+ else
779
+ (-1 * ((fv * r) - (pmt * ((1 + r)**type))) / ((pv * r) + (pmt * ((1 + r)**type)))).to_d.log / (1 + r).to_d.log
780
+
781
+ end
782
+ end
783
+
784
+ # net profit margin -- Evaluate a company's financial performance
785
+ #
786
+ # @param ni net income
787
+ # @param rv revenue (sales)
788
+ # @export
789
+ # @examples
790
+ # npm(ni=8000,rv=20000)
791
+ def self.npm(ni, rv)
792
+ ni = ni.to_d
793
+ rv = rv.to_d
794
+
795
+ (ni / rv)
796
+ end
797
+
798
+ # Computing NPV, the PV of the cash flows less the initial (time = 0) outlay
799
+ #
800
+ # @param r discount rate, or the interest rate at which the amount will be compounded each period
801
+ # @param cf cash flow,the first cash flow is the initial outlay
802
+ # @export
803
+ # @examples
804
+ # npv(r=0.12, cf=c(-5, 1.6, 2.4, 2.8))
805
+ def self.npv(r, cf)
806
+ r = r.to_d
807
+ cf = Array.wrap(cf).map(&:to_d)
808
+
809
+ n = cf.size
810
+ subcf = cf.drop(1)
811
+ ((-1 * Finrb::Utils.pv_uneven(r, subcf)) + cf[0])
812
+ end
813
+
814
+ # Estimate period payment
815
+ #
816
+ # @param r discount rate, or the interest rate at which the amount will be compounded each period
817
+ # @param n number of periods
818
+ # @param pv present value
819
+ # @param fv future value
820
+ # @param type payments occur at the end of each period (type=0); payments occur at the beginning of each period (type=1)
821
+ # @export
822
+ # @examples
823
+ # pmt(0.08,10,-1000,10)
824
+ #
825
+ # pmt(r=0.08,n=10,pv=-1000,fv=0)
826
+ #
827
+ # pmt(0.08,10,-1000,10,1)
828
+ def self.pmt(r, n, pv, fv, type = 0)
829
+ r = r.to_d
830
+ n = n.to_d
831
+ pv = pv.to_d
832
+ fv = fv.to_d
833
+ type = type.to_d
834
+
835
+ if type != 0 && type != 1
836
+ raise(FinrbError, 'Error: type should be 0 or 1!')
837
+ else
838
+ (pv + (fv / ((1 + r)**n))) * r / (1 - (1 / ((1 + r)**n))) * -1 * ((1 + r)**(-1 * type))
839
+
840
+ end
841
+ end
842
+
843
+ # Estimate present value (pv)
844
+ #
845
+ # @param r discount rate, or the interest rate at which the amount will be compounded each period
846
+ # @param n number of periods
847
+ # @param fv future value
848
+ # @param pmt payment per period
849
+ # @param type payments occur at the end of each period (type=0); payments occur at the beginning of each period (type=1)
850
+ # @export
851
+ # @examples
852
+ # pv(0.07,10,1000,10)
853
+ #
854
+ # pv(r=0.05,n=20,fv=1000,pmt=10,type=1)
855
+ def self.pv(r, n, fv = 0, pmt = 0, type = 0)
856
+ r = r.to_d
857
+ n = n.to_d
858
+ fv = fv.to_d
859
+ pmt = pmt.to_d
860
+ type = type.to_d
861
+
862
+ if type != 0 && type != 1
863
+ raise(FinrbError, 'Error: type should be 0 or 1!')
864
+ else
865
+ Finrb::Utils.pv_simple(r, n, fv) + Finrb::Utils.pv_annuity(r, n, pmt, type)
866
+
867
+ end
868
+ end
869
+
870
+ # Estimate present value (pv) of an annuity
871
+ #
872
+ # @param r discount rate, or the interest rate at which the amount will be compounded each period
873
+ # @param n number of periods
874
+ # @param pmt payment per period
875
+ # @param type payments occur at the end of each period (type=0); payments occur at the beginning of each period (type=1)
876
+ # @export
877
+ # @examples
878
+ # Finrb::Utils.pv_annuity(0.03,12,1000)
879
+ #
880
+ # Finrb::Utils.pv_annuity(r=0.0425,n=3,pmt=30000)
881
+ def self.pv_annuity(r, n, pmt, type = 0)
882
+ r = r.to_d
883
+ n = n.to_d
884
+ pmt = pmt.to_d
885
+ type = type.to_d
886
+
887
+ if type != 0 && type != 1
888
+ raise(FinrbError, 'Error: type should be 0 or 1!')
889
+ else
890
+ (pmt / r * (1 - (1 / ((1 + r)**n)))) * ((1 + r)**type) * -1
891
+
892
+ end
893
+ end
894
+
895
+ # Estimate present value of a perpetuity
896
+ #
897
+ # @param r discount rate, or the interest rate at which the amount will be compounded each period
898
+ # @param g growth rate of perpetuity
899
+ # @param pmt payment per period
900
+ # @param type payments occur at the end of each period (type=0); payments occur at the beginning of each period (type=1)
901
+ # @export
902
+ # @examples
903
+ # Finrb::Utils.pv_perpetuity(r=0.1,pmt=1000,g=0.02)
904
+ #
905
+ # Finrb::Utils.pv_perpetuity(r=0.1,pmt=1000,type=1)
906
+ #
907
+ # Finrb::Utils.pv_perpetuity(r=0.1,pmt=1000)
908
+ def self.pv_perpetuity(r, pmt, g = 0, type = 0)
909
+ r = r.to_d
910
+ pmt = pmt.to_d
911
+ g = g.to_d
912
+ type = type.to_d
913
+
914
+ if type != 0 && type != 1
915
+ raise(FinrbError, 'Error: type should be 0 or 1!')
916
+ elsif g >= r
917
+ raise(FinrbError, 'Error: g is not smaller than r!')
918
+ else
919
+ (pmt / (r - g)) * ((1 + r)**type) * -1
920
+
921
+ end
922
+ end
923
+
924
+ # Estimate present value (pv) of a single sum
925
+ #
926
+ # @param r discount rate, or the interest rate at which the amount will be compounded each period
927
+ # @param n number of periods
928
+ # @param fv future value
929
+ # @export
930
+ # @examples
931
+ # Finrb::Utils.pv_simple(0.07,10,100)
932
+ #
933
+ # Finrb::Utils.pv_simple(r=0.03,n=3,fv=1000)
934
+ def self.pv_simple(r, n, fv)
935
+ r = r.to_d
936
+ n = n.to_d
937
+ fv = fv.to_d
938
+
939
+ ((fv / ((1 + r)**n)) * -1)
940
+ end
941
+
942
+ # Computing the present value of an uneven cash flow series
943
+ #
944
+ # @param r discount rate, or the interest rate at which the amount will be compounded each period
945
+ # @param cf uneven cash flow
946
+ # @export
947
+ # @examples
948
+ # Finrb::Utils.pv_uneven(r=0.1, cf=c(-1000, -500, 0, 4000, 3500, 2000))
949
+ def self.pv_uneven(r, cf)
950
+ r = r.to_d
951
+ cf = Array.wrap(cf).map(&:to_d)
952
+
953
+ n = cf.size
954
+ sum = 0
955
+ (0...n).each do |i|
956
+ sum += Finrb::Utils.pv_simple(r, i, cf[i])
957
+ end
958
+ sum
959
+ end
960
+
961
+ # quick ratio -- Liquidity ratios measure the firm's ability to satisfy its short-term obligations as they come due.
962
+ #
963
+ # @param cash cash
964
+ # @param ms marketable securities
965
+ # @param rc receivables
966
+ # @param cl current liabilities
967
+ # @export
968
+ # @examples
969
+ # Finrb::Utils.quick_ratio(cash=3000,ms=2000,rc=1000,cl=2000)
970
+ def self.quick_ratio(cash, ms, rc, cl)
971
+ cash = cash.to_d
972
+ ms = ms.to_d
973
+ rc = rc.to_d
974
+ cl = cl.to_d
975
+
976
+ ((cash + ms + rc) / cl)
977
+ end
978
+
979
+ # Convert a given norminal rate to a continuous compounded rate
980
+ #
981
+ # @param r norminal rate
982
+ # @param m number of times compounded each year
983
+ # @export
984
+ # @examples
985
+ # Finrb::Utils.r_continuous(r=0.03,m=4)
986
+ def self.r_continuous(r, m)
987
+ r = r.to_d
988
+ m = m.to_d
989
+
990
+ (m * (1 + (r / m)).to_d.log)
991
+ end
992
+
993
+ # Convert a given continuous compounded rate to a norminal rate
994
+ #
995
+ # @param rc continuous compounded rate
996
+ # @param m number of desired times compounded each year
997
+ # @export
998
+ # @examples
999
+ # Finrb::Utils.r_norminal(0.03,1)
1000
+ #
1001
+ # Finrb::Utils.r_norminal(rc=0.03,m=4)
1002
+ def self.r_norminal(rc, m)
1003
+ rc = rc.to_d
1004
+ m = m.to_d
1005
+
1006
+ (m * ((rc / m).to_d.exp - 1))
1007
+ end
1008
+
1009
+ # Rate of return for a perpetuity
1010
+ #
1011
+ # @param pmt payment per period
1012
+ # @param pv present value
1013
+ # @export
1014
+ # @examples
1015
+ # Finrb::Utils.r_perpetuity(pmt=4.5,pv=-75)
1016
+ def self.r_perpetuity(pmt, pv)
1017
+ pmt = pmt.to_d
1018
+ pv = pv.to_d
1019
+
1020
+ (-1 * pmt / pv)
1021
+ end
1022
+
1023
+ # Computing Sampling error
1024
+ #
1025
+ # @param sm sample mean
1026
+ # @param mu population mean
1027
+ # @export
1028
+ # @examples
1029
+ # Finrb::Utils.sampling_error(sm=0.45, mu=0.5)
1030
+ def self.sampling_error(sm, mu)
1031
+ sm = sm.to_d
1032
+ mu = mu.to_d
1033
+
1034
+ (sm - mu)
1035
+ end
1036
+
1037
+ # Computing Roy's safety-first ratio
1038
+ #
1039
+ # @param rp portfolio return
1040
+ # @param rl threshold level return
1041
+ # @param sd standard deviation of portfolio retwns
1042
+ # @export
1043
+ # @examples
1044
+ # Finrb::Utils.sf_ratio(rp=0.09,rl=0.03,sd=0.12)
1045
+ def self.sf_ratio(rp, rl, sd)
1046
+ rp = rp.to_d
1047
+ rl = rl.to_d
1048
+ sd = sd.to_d
1049
+
1050
+ ((rp - rl) / sd)
1051
+ end
1052
+
1053
+ # Computing Sharpe Ratio
1054
+ #
1055
+ # @param rp portfolio return
1056
+ # @param rf risk-free return
1057
+ # @param sd standard deviation of portfolio retwns
1058
+ # @export
1059
+ # @examples
1060
+ # Finrb::Utils.sharpe_ratio(rp=0.038,rf=0.015,sd=0.07)
1061
+ def self.sharpe_ratio(rp, rf, sd)
1062
+ rp = rp.to_d
1063
+ rf = rf.to_d
1064
+ sd = sd.to_d
1065
+
1066
+ ((rp - rf) / sd)
1067
+ end
1068
+
1069
+ # Depreciation Expense Recognition -- Straight-line depreciation (SL) allocates an equal amount of depreciation each year over the asset's useful life
1070
+ #
1071
+ # @param cost cost of long-lived assets
1072
+ # @param rv residual value of the long-lived assets at the end of its useful life
1073
+ # @param t length of the useful life
1074
+ # @export
1075
+ # @examples
1076
+ # slde(cost=1200,rv=200,t=5)
1077
+ def self.slde(cost, rv, t)
1078
+ cost = cost.to_d
1079
+ rv = rv.to_d
1080
+ t = t.to_d
1081
+
1082
+ ((cost - rv) / t)
1083
+ end
1084
+
1085
+ # total debt-to-equity -- Solvency ratios measure the firm's ability to satisfy its long-term obligations.
1086
+ #
1087
+ # @param td total debt
1088
+ # @param te total equity
1089
+ # @export
1090
+ # @examples
1091
+ # Finrb::Utils.total_d2e(td=6000,te=20000)
1092
+ def self.total_d2e(td, te)
1093
+ td = td.to_d
1094
+ te = te.to_d
1095
+
1096
+ (td / te)
1097
+ end
1098
+
1099
+ # Computing TWRR, the time-weighted rate of return
1100
+ #
1101
+ # @param ev ordered ending value list
1102
+ # @param bv ordered beginning value list
1103
+ # @param cfr ordered cash flow received list
1104
+ # @export
1105
+ # @examples
1106
+ # twrr(ev=c(120,260),bv=c(100,240),cfr=c(2,4))
1107
+ def self.twrr(ev, bv, cfr)
1108
+ ev = Array.wrap(ev).map(&:to_d)
1109
+ bv = Array.wrap(bv).map(&:to_d)
1110
+ cfr = Array.wrap(cfr).map(&:to_d)
1111
+
1112
+ r = ev.size
1113
+ s = bv.size
1114
+ t = cfr.size
1115
+ wr = 1
1116
+ if r != s || r != t || s != t
1117
+ raise(FinrbError, 'Different number of values!')
1118
+ else
1119
+ (0...r).each do |i|
1120
+ wr *= (hpr(ev[i], bv[i], cfr[i]) + 1)
1121
+ end
1122
+ ((wr**(1 / r)) - 1)
1123
+ end
1124
+ end
1125
+
1126
+ # calculate weighted average shares -- weighted average number of common shares
1127
+ #
1128
+ # @param ns n x 1 vector vector of number of shares
1129
+ # @param nm n x 1 vector vector of number of months relate to ns
1130
+ # @export
1131
+ # @examples
1132
+ # s=c(10000,2000);m=c(12,6);was(ns=s,nm=m)
1133
+ #
1134
+ # s=c(11000,4400,-3000);m=c(12,9,4);was(ns=s,nm=m)
1135
+ def self.was(ns, nm)
1136
+ ns = Array.wrap(ns).map(&:to_d)
1137
+ nm = Array.wrap(nm).map(&:to_d)
1138
+
1139
+ m = ns.size
1140
+ n = nm.size
1141
+ sum = 0
1142
+ if m == n
1143
+ (0...m).each do |i|
1144
+ sum += (ns[i] * nm[i])
1145
+ end
1146
+ else
1147
+ raise(FinrbError, 'length of ns and nm must be equal')
1148
+ end
1149
+ sum /= 12
1150
+ sum
1151
+ end
1152
+
1153
+ # Weighted mean as a portfolio return
1154
+ #
1155
+ # @param r returns of the individual assets in the portfolio
1156
+ # @param w corresponding weights associated with each of the individual assets
1157
+ # @export
1158
+ # @examples
1159
+ # wpr(r=c(0.12, 0.07, 0.03),w=c(0.5,0.4,0.1))
1160
+ def self.wpr(r, w)
1161
+ r = Array.wrap(r).map(&:to_d)
1162
+ w = Array.wrap(w).map(&:to_d)
1163
+
1164
+ if w.sum != 1
1165
+ puts('sum of weights is NOT equal to 1!') # TODO: need to change
1166
+ else
1167
+ end
1168
+ r.zip(w).sum { |arr| arr.reduce(:*) }
1169
+ end
1170
+ end
1171
+ end