1
- """Some simple financial calculations
1
+ """Some simple financial calculations.
2
2
3
3
patterned after spreadsheet computations.
4
4
10
10
Functions support the :class:`decimal.Decimal` type unless
11
11
otherwise stated.
12
12
"""
13
- from __future__ import absolute_import , division , print_function
14
13
15
14
from decimal import Decimal
16
15
17
16
import numpy as np
18
17
19
18
__all__ = ['fv' , 'pmt' , 'nper' , 'ipmt' , 'ppmt' , 'pv' , 'rate' ,
20
19
'irr' , 'npv' , 'mirr' ,
21
- 'NoRealSolutionException ' , 'IterationsExceededException ' ]
20
+ 'NoRealSolutionError ' , 'IterationsExceededError ' ]
22
21
23
22
_when_to_num = {'end' : 0 , 'begin' : 1 ,
24
23
'e' : 0 , 'b' : 1 ,
28
27
'finish' : 0 }
29
28
30
29
31
- class NoRealSolutionException (Exception ):
32
- """ No real solution to the problem. """
33
- pass
30
+ class NoRealSolutionError (Exception ):
31
+ """No real solution to the problem."""
34
32
35
33
36
- class IterationsExceededException (Exception ):
37
- """ Maximum number of iterations reached. """
38
- pass
34
+ class IterationsExceededError (Exception ):
35
+ """Maximum number of iterations reached."""
39
36
40
37
41
38
def _convert_when (when ):
@@ -50,8 +47,7 @@ def _convert_when(when):
50
47
51
48
52
49
def fv (rate , nper , pmt , pv , when = 'end' ):
53
- """
54
- Compute the future value.
50
+ """Compute the future value.
55
51
56
52
Given:
57
53
* a present value, `pv`
@@ -143,11 +139,11 @@ def fv(rate, nper, pmt, pv, when='end'):
143
139
fv_array [zero ] = - (pv [zero ] + pmt [zero ] * nper [zero ])
144
140
145
141
rate_nonzero = rate [nonzero ]
146
- temp = (1 + rate_nonzero )** nper [nonzero ]
142
+ temp = (1 + rate_nonzero ) ** nper [nonzero ]
147
143
fv_array [nonzero ] = (
148
- - pv [nonzero ] * temp
149
- - pmt [nonzero ] * (1 + rate_nonzero * when [nonzero ]) / rate_nonzero
150
- * (temp - 1 )
144
+ - pv [nonzero ] * temp
145
+ - pmt [nonzero ] * (1 + rate_nonzero * when [nonzero ]) / rate_nonzero
146
+ * (temp - 1 )
151
147
)
152
148
153
149
if np .ndim (fv_array ) == 0 :
@@ -158,8 +154,7 @@ def fv(rate, nper, pmt, pv, when='end'):
158
154
159
155
160
156
def pmt (rate , nper , pv , fv = 0 , when = 'end' ):
161
- """
162
- Compute the payment against loan principal plus interest.
157
+ """Compute the payment against loan principal plus interest.
163
158
164
159
Given:
165
160
* a present value, `pv` (e.g., an amount borrowed)
@@ -244,17 +239,16 @@ def pmt(rate, nper, pv, fv=0, when='end'):
244
239
"""
245
240
when = _convert_when (when )
246
241
(rate , nper , pv , fv , when ) = map (np .array , [rate , nper , pv , fv , when ])
247
- temp = (1 + rate )** nper
242
+ temp = (1 + rate ) ** nper
248
243
mask = (rate == 0 )
249
244
masked_rate = np .where (mask , 1 , rate )
250
245
fact = np .where (mask != 0 , nper ,
251
- (1 + masked_rate * when )* (temp - 1 )/ masked_rate )
252
- return - (fv + pv * temp ) / fact
246
+ (1 + masked_rate * when ) * (temp - 1 ) / masked_rate )
247
+ return - (fv + pv * temp ) / fact
253
248
254
249
255
250
def nper (rate , pmt , pv , fv = 0 , when = 'end' ):
256
- """
257
- Compute the number of periodic payments.
251
+ """Compute the number of periodic payments.
258
252
259
253
:class:`decimal.Decimal` type is not supported.
260
254
@@ -321,8 +315,8 @@ def nper(rate, pmt, pv, fv=0, when='end'):
321
315
nonzero_rate = rate [nonzero ]
322
316
z = pmt [nonzero ] * (1 + nonzero_rate * when [nonzero ]) / nonzero_rate
323
317
nper_array [nonzero ] = (
324
- np .log ((- fv [nonzero ] + z ) / (pv [nonzero ] + z ))
325
- / np .log (1 + nonzero_rate )
318
+ np .log ((- fv [nonzero ] + z ) / (pv [nonzero ] + z ))
319
+ / np .log (1 + nonzero_rate )
326
320
)
327
321
328
322
return nper_array
@@ -332,13 +326,11 @@ def _value_like(arr, value):
332
326
entry = arr .item (0 )
333
327
if isinstance (entry , Decimal ):
334
328
return Decimal (value )
335
- else :
336
- return np .array (value , dtype = arr .dtype ).item (0 )
329
+ return np .array (value , dtype = arr .dtype ).item (0 )
337
330
338
331
339
332
def ipmt (rate , per , nper , pv , fv = 0 , when = 'end' ):
340
- """
341
- Compute the interest portion of a payment.
333
+ """Compute the interest portion of a payment.
342
334
343
335
Parameters
344
336
----------
@@ -439,7 +431,7 @@ def ipmt(rate, per, nper, pv, fv=0, when='end'):
439
431
# If paying at the beginning we need to discount by one period.
440
432
per_gt_1_and_begin = (when == 1 ) & (per > 1 )
441
433
ipmt_array [per_gt_1_and_begin ] = (
442
- ipmt_array [per_gt_1_and_begin ] / (1 + rate [per_gt_1_and_begin ])
434
+ ipmt_array [per_gt_1_and_begin ] / (1 + rate [per_gt_1_and_begin ])
443
435
)
444
436
445
437
if np .ndim (ipmt_array ) == 0 :
@@ -450,7 +442,8 @@ def ipmt(rate, per, nper, pv, fv=0, when='end'):
450
442
451
443
452
444
def _rbl (rate , per , pmt , pv , when ):
453
- """
445
+ """Remaining balance on loan.
446
+
454
447
This function is here to simply have a different name for the 'fv'
455
448
function to not interfere with the 'fv' keyword argument within the 'ipmt'
456
449
function. It is the 'remaining balance on loan' which might be useful as
@@ -460,8 +453,7 @@ def _rbl(rate, per, pmt, pv, when):
460
453
461
454
462
455
def ppmt (rate , per , nper , pv , fv = 0 , when = 'end' ):
463
- """
464
- Compute the payment against loan principal.
456
+ """Compute the payment against loan principle.
465
457
466
458
Parameters
467
459
----------
@@ -489,8 +481,7 @@ def ppmt(rate, per, nper, pv, fv=0, when='end'):
489
481
490
482
491
483
def pv (rate , nper , pmt , fv = 0 , when = 'end' ):
492
- """
493
- Compute the present value.
484
+ """Compute the present value.
494
485
495
486
Given:
496
487
* a future value, `fv`
@@ -579,9 +570,10 @@ def pv(rate, nper, pmt, fv=0, when='end'):
579
570
"""
580
571
when = _convert_when (when )
581
572
(rate , nper , pmt , fv , when ) = map (np .asarray , [rate , nper , pmt , fv , when ])
582
- temp = (1 + rate )** nper
583
- fact = np .where (rate == 0 , nper , (1 + rate * when )* (temp - 1 )/ rate )
584
- return - (fv + pmt * fact )/ temp
573
+ temp = (1 + rate ) ** nper
574
+ fact = np .where (rate == 0 , nper , (1 + rate * when ) * (temp - 1 ) / rate )
575
+ return - (fv + pmt * fact ) / temp
576
+
585
577
586
578
# Computed with Sage
587
579
# (y + (r + 1)^n*x + p*((r + 1)^n - 1)*(r*w + 1)/r)/(n*(r + 1)^(n - 1)*x -
@@ -592,13 +584,13 @@ def pv(rate, nper, pmt, fv=0, when='end'):
592
584
def _g_div_gp (r , n , p , x , y , w ):
593
585
# Evaluate g(r_n)/g'(r_n), where g =
594
586
# fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate * ((1+rate)**nper - 1)
595
- t1 = (r + 1 ) ** n
596
- t2 = (r + 1 ) ** ( n - 1 )
597
- g = y + t1 * x + p * (t1 - 1 ) * (r * w + 1 ) / r
598
- gp = (n * t2 * x
599
- - p * (t1 - 1 ) * (r * w + 1 ) / (r ** 2 )
600
- + n * p * t2 * (r * w + 1 ) / r
601
- + p * (t1 - 1 ) * w / r )
587
+ t1 = (r + 1 ) ** n
588
+ t2 = (r + 1 ) ** ( n - 1 )
589
+ g = y + t1 * x + p * (t1 - 1 ) * (r * w + 1 ) / r
590
+ gp = (n * t2 * x
591
+ - p * (t1 - 1 ) * (r * w + 1 ) / (r ** 2 )
592
+ + n * p * t2 * (r * w + 1 ) / r
593
+ + p * (t1 - 1 ) * w / r )
602
594
return g / gp
603
595
604
596
@@ -609,9 +601,18 @@ def _g_div_gp(r, n, p, x, y, w):
609
601
# where
610
602
# g(r) is the formula
611
603
# g'(r) is the derivative with respect to r.
612
- def rate (nper , pmt , pv , fv , when = 'end' , guess = None , tol = None , maxiter = 100 , * , raise_exceptions = False ):
613
- """
614
- Compute the rate of interest per period.
604
+ def rate (
605
+ nper ,
606
+ pmt ,
607
+ pv ,
608
+ fv ,
609
+ when = 'end' ,
610
+ guess = None ,
611
+ tol = None ,
612
+ maxiter = 100 ,
613
+ * ,
614
+ raise_exceptions = False ):
615
+ """Compute the rate of interest per period.
615
616
616
617
Parameters
617
618
----------
@@ -675,29 +676,28 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100, *, ra
675
676
close = False
676
677
while (iterator < maxiter ) and not np .all (close ):
677
678
rnp1 = rn - _g_div_gp (rn , nper , pmt , pv , fv , when )
678
- diff = abs (rnp1 - rn )
679
+ diff = abs (rnp1 - rn )
679
680
close = diff < tol
680
681
iterator += 1
681
682
rn = rnp1
682
683
683
684
if not np .all (close ):
684
685
if np .isscalar (rn ):
685
686
if raise_exceptions :
686
- raise IterationsExceededException ('Maximum number of iterations exceeded.' )
687
+ raise IterationsExceededError ('Maximum number of iterations exceeded.' )
687
688
return default_type (np .nan )
688
689
else :
689
690
# Return nan's in array of the same shape as rn
690
691
# where the solution is not close to tol.
691
692
if raise_exceptions :
692
- raise IterationsExceededException (f'Maximum number of iterations exceeded in '
693
- f'{ len (close )- close .sum ()} rate(s).' )
693
+ raise IterationsExceededError (f'Maximum iterations exceeded in '
694
+ f'{ len (close ) - close .sum ()} rate(s).' )
694
695
rn [~ close ] = np .nan
695
696
return rn
696
697
697
698
698
699
def irr (values , * , guess = None , tol = 1e-12 , maxiter = 100 , raise_exceptions = False ):
699
- """
700
- Return the Internal Rate of Return (IRR).
700
+ r"""Return the Internal Rate of Return (IRR).
701
701
702
702
This is the "average" periodically compounded rate of return
703
703
that gives a net present value of 0.0; for a more complete explanation,
@@ -781,8 +781,8 @@ def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
781
781
same_sign = np .all (values > 0 ) if values [0 ] > 0 else np .all (values < 0 )
782
782
if same_sign :
783
783
if raise_exceptions :
784
- raise NoRealSolutionException ('No real solution exists for IRR since all '
785
- 'cashflows are of the same sign.' )
784
+ raise NoRealSolutionError ('No real solution exists for IRR since all '
785
+ 'cashflows are of the same sign.' )
786
786
return np .nan
787
787
788
788
# If no value is passed for `guess`, then make a heuristic estimate
@@ -820,14 +820,13 @@ def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
820
820
g -= delta
821
821
822
822
if raise_exceptions :
823
- raise IterationsExceededException ('Maximum number of iterations exceeded.' )
823
+ raise IterationsExceededError ('Maximum number of iterations exceeded.' )
824
824
825
825
return np .nan
826
826
827
827
828
828
def npv (rate , values ):
829
- """
830
- Returns the NPV (Net Present Value) of a cash flow series.
829
+ r"""Return the NPV (Net Present Value) of a cash flow series.
831
830
832
831
Parameters
833
832
----------
@@ -905,8 +904,7 @@ def npv(rate, values):
905
904
906
905
907
906
def mirr (values , finance_rate , reinvest_rate , * , raise_exceptions = False ):
908
- """
909
- Modified internal rate of return.
907
+ r"""Return the modified internal rate of return.
910
908
911
909
Parameters
912
910
----------
@@ -943,9 +941,9 @@ def mirr(values, finance_rate, reinvest_rate, *, raise_exceptions=False):
943
941
neg = values < 0
944
942
if not (pos .any () and neg .any ()):
945
943
if raise_exceptions :
946
- raise NoRealSolutionException ('No real solution exists for MIRR since'
947
- ' all cashflows are of the same sign.' )
944
+ raise NoRealSolutionError ('No real solution exists for MIRR since'
945
+ ' all cashflows are of the same sign.' )
948
946
return np .nan
949
- numer = np .abs (npv (reinvest_rate , values * pos ))
950
- denom = np .abs (npv (finance_rate , values * neg ))
951
- return (numer / denom )** ( 1 / (n - 1 ))* (1 + reinvest_rate ) - 1
947
+ numer = np .abs (npv (reinvest_rate , values * pos ))
948
+ denom = np .abs (npv (finance_rate , values * neg ))
949
+ return (numer / denom ) ** ( 1 / (n - 1 )) * (1 + reinvest_rate ) - 1
0 commit comments