17
17
import numpy as np
18
18
19
19
__all__ = ['fv' , 'pmt' , 'nper' , 'ipmt' , 'ppmt' , 'pv' , 'rate' ,
20
- 'irr' , 'npv' , 'mirr' ]
20
+ 'irr' , 'npv' , 'mirr' ,
21
+ 'NoRealSolutionException' , 'IterationsExceededException' ]
21
22
22
23
_when_to_num = {'end' : 0 , 'begin' : 1 ,
23
24
'e' : 0 , 'b' : 1 ,
26
27
'start' : 1 ,
27
28
'finish' : 0 }
28
29
30
+ # Define custom Exceptions
31
+
32
+ class NoRealSolutionException (Exception ):
33
+ """ No real solution to the problem. """
34
+
35
+ pass
36
+
37
+ class IterationsExceededException (Exception ):
38
+ """ Maximum number of iterations reached. """
39
+
40
+ pass
41
+
29
42
30
43
def _convert_when (when ):
31
44
# Test to see if when has already been converted to ndarray
@@ -598,7 +611,7 @@ def _g_div_gp(r, n, p, x, y, w):
598
611
# where
599
612
# g(r) is the formula
600
613
# g'(r) is the derivative with respect to r.
601
- def rate (nper , pmt , pv , fv , when = 'end' , guess = None , tol = None , maxiter = 100 ):
614
+ def rate (nper , pmt , pv , fv , when = 'end' , guess = None , tol = None , maxiter = 100 , * , raise_exceptions = False ):
602
615
"""
603
616
Compute the rate of interest per period.
604
617
@@ -620,6 +633,11 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100):
620
633
Required tolerance for the solution, default 1e-6
621
634
maxiter : int, optional
622
635
Maximum iterations in finding the solution
636
+ raise_exceptions: bool, optional
637
+ Flag to raise an exception when at least one of the rates
638
+ cannot be computed due to having reached the maximum number of
639
+ iterations (IterationsExceededException). Set to False as default,
640
+ thus returning NaNs for those rates.
623
641
624
642
Notes
625
643
-----
@@ -666,15 +684,20 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100):
666
684
667
685
if not np .all (close ):
668
686
if np .isscalar (rn ):
687
+ if raise_exceptions :
688
+ raise IterationsExceededException ('Maximum number of iterations exceeded.' )
669
689
return default_type (np .nan )
670
690
else :
671
691
# Return nan's in array of the same shape as rn
672
692
# where the solution is not close to tol.
693
+ if raise_exceptions :
694
+ raise IterationsExceededException (f'Maximum number of iterations exceeded in '
695
+ f'{ len (close )- close .sum ()} rate(s).' )
673
696
rn [~ close ] = np .nan
674
697
return rn
675
698
676
699
677
- def irr (values , guess = None , tol = 1e-12 , maxiter = 100 ):
700
+ def irr (values , guess = None , * , tol = 1e-12 , maxiter = 100 , raise_exceptions = False ):
678
701
"""
679
702
Return the Internal Rate of Return (IRR).
680
703
@@ -699,6 +722,12 @@ def irr(values, guess=None, tol=1e-12, maxiter=100):
699
722
Required tolerance to accept solution. Default is 1e-12.
700
723
maxiter : int, optional
701
724
Maximum iterations to perform in finding a solution. Default is 100.
725
+ raise_exceptions: bool, optional
726
+ Flag to raise an exception when the irr cannot be computed due to
727
+ either having all cashflows of the same sign (NoRealSolutionException) or
728
+ having reached the maximum number of iterations (IterationsExceededException).
729
+ Set to False as default, thus returning NaNs in the two previous
730
+ cases.
702
731
703
732
Returns
704
733
-------
@@ -753,6 +782,9 @@ def irr(values, guess=None, tol=1e-12, maxiter=100):
753
782
# we don't perform any further calculations and exit early
754
783
same_sign = np .all (values > 0 ) if values [0 ] > 0 else np .all (values < 0 )
755
784
if same_sign :
785
+ if raise_exceptions :
786
+ raise NoRealSolutionException ('No real solution exists for IRR since all '
787
+ 'cashflows are of the same sign.' )
756
788
return np .nan
757
789
758
790
# If no value is passed for `guess`, then make a heuristic estimate
@@ -789,6 +821,9 @@ def irr(values, guess=None, tol=1e-12, maxiter=100):
789
821
return g - 1
790
822
g -= delta
791
823
824
+ if raise_exceptions :
825
+ raise IterationsExceededException ('Maximum number of iterations exceeded.' )
826
+
792
827
return np .nan
793
828
794
829
@@ -871,7 +906,7 @@ def npv(rate, values):
871
906
return npv
872
907
873
908
874
- def mirr (values , finance_rate , reinvest_rate ):
909
+ def mirr (values , finance_rate , reinvest_rate , * , raise_exceptions = False ):
875
910
"""
876
911
Modified internal rate of return.
877
912
@@ -885,6 +920,11 @@ def mirr(values, finance_rate, reinvest_rate):
885
920
Interest rate paid on the cash flows
886
921
reinvest_rate : scalar
887
922
Interest rate received on the cash flows upon reinvestment
923
+ raise_exceptions: bool, optional
924
+ Flag to raise an exception when the mirr cannot be computed due to
925
+ having all cashflows of the same sign (NoRealSolutionException).
926
+ Set to False as default, thus returning NaNs in the previous
927
+ case.
888
928
889
929
Returns
890
930
-------
@@ -904,6 +944,9 @@ def mirr(values, finance_rate, reinvest_rate):
904
944
pos = values > 0
905
945
neg = values < 0
906
946
if not (pos .any () and neg .any ()):
947
+ if raise_exceptions :
948
+ raise NoRealSolutionException ('No real solution exists for MIRR since'
949
+ ' all cashflows are of the same sign.' )
907
950
return np .nan
908
951
numer = np .abs (npv (reinvest_rate , values * pos ))
909
952
denom = np .abs (npv (finance_rate , values * neg ))
0 commit comments