@@ -598,7 +598,7 @@ def _g_div_gp(r, n, p, x, y, w):
598598# where
599599# g(r) is the formula
600600# g'(r) is the derivative with respect to r.
601- def rate (nper , pmt , pv , fv , when = 'end' , guess = None , tol = None , maxiter = 100 ):
601+ def rate (nper , pmt , pv , fv , when = 'end' , guess = None , tol = None , maxiter = 100 , raise_exceptions = False ):
602602 """
603603 Compute the rate of interest per period.
604604
@@ -620,6 +620,11 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100):
620620 Required tolerance for the solution, default 1e-6
621621 maxiter : int, optional
622622 Maximum iterations in finding the solution
623+ raise_exceptions: bool, optional
624+ Flag to raise an exception when the at least one of the rates
625+ cannot be computed due to having reached the maximum number of
626+ iterations (IterationsExceededException). Set to False as default,
627+ thus returning NaNs for those rates.
623628
624629 Notes
625630 -----
@@ -664,17 +669,29 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100):
664669 iterator += 1
665670 rn = rnp1
666671
672+ # Define the custom Exceptions in case the flag raise_exceptions
673+ # is set to True
674+ if raise_exceptions :
675+ class IterationsExceededException (Exception ):
676+ "Raised when the maximum number of iterations is reached"
677+ pass
678+
667679 if not np .all (close ):
668680 if np .isscalar (rn ):
681+ if raise_exceptions :
682+ raise IterationsExceededException ('\n Maximum number of iterations exceeded.' )
669683 return default_type (np .nan )
670684 else :
671685 # Return nan's in array of the same shape as rn
672686 # where the solution is not close to tol.
687+ if raise_exceptions :
688+ raise IterationsExceededException (f'\n Maximum number of iterations exceeded in '
689+ f'{ len (close )- close .sum ()} rate(s).' )
673690 rn [~ close ] = np .nan
674691 return rn
675692
676693
677- def irr (values , guess = None , tol = 1e-12 , maxiter = 100 ):
694+ def irr (values , guess = None , tol = 1e-12 , maxiter = 100 , raise_exceptions = False ):
678695 """
679696 Return the Internal Rate of Return (IRR).
680697
@@ -699,6 +716,12 @@ def irr(values, guess=None, tol=1e-12, maxiter=100):
699716 Required tolerance to accept solution. Default is 1e-12.
700717 maxiter : int, optional
701718 Maximum iterations to perform in finding a solution. Default is 100.
719+ raise_exceptions: bool, optional
720+ Flag to raise an exception when the irr cannot be computed due to
721+ either having all cashflows of the same sign (NoRealSolutionException) or
722+ having reached the maximum number of iterations (IterationsExceededException).
723+ Set to False as default, thus returning NaNs in the two previous
724+ cases.
702725
703726 Returns
704727 -------
@@ -749,13 +772,23 @@ def irr(values, guess=None, tol=1e-12, maxiter=100):
749772 if values .ndim != 1 :
750773 raise ValueError ("Cashflows must be a rank-1 array" )
751774
775+ # Define the custom Exceptions in case the flag raise_exceptions
776+ # is set to True
777+ if raise_exceptions :
778+ class NoRealSolutionException (Exception ):
779+ "Raised when all the input cashflows are of the same sign"
780+ pass
781+ class IterationsExceededException (Exception ):
782+ "Raised when the maximum number of iterations is reached"
783+ pass
784+
752785 # If all values are of the same sign no solution exists
753786 # we don't perform any further calculations and exit early
754787 same_sign = np .all (values > 0 ) if values [0 ] > 0 else np .all (values < 0 )
755788 if same_sign :
756- print ( ' \n No solution exists for IRR since all '
757- 'cashflows are of the same sign. Returning '
758- 'np.nan \n ' )
789+ if raise_exceptions :
790+ raise NoRealSolutionException ( ' \n No real solution exists for IRR since all '
791+ 'cashflows are of the same sign. \n ' )
759792 return np .nan
760793
761794 # If no value is passed for `guess`, then make a heuristic estimate
@@ -792,6 +825,9 @@ def irr(values, guess=None, tol=1e-12, maxiter=100):
792825 return g - 1
793826 g -= delta
794827
828+ if raise_exceptions :
829+ raise IterationsExceededException ('\n Maximum number of iterations exceeded.' )
830+
795831 return np .nan
796832
797833
@@ -874,7 +910,7 @@ def npv(rate, values):
874910 return npv
875911
876912
877- def mirr (values , finance_rate , reinvest_rate ):
913+ def mirr (values , finance_rate , reinvest_rate , raise_exceptions = False ):
878914 """
879915 Modified internal rate of return.
880916
@@ -888,6 +924,11 @@ def mirr(values, finance_rate, reinvest_rate):
888924 Interest rate paid on the cash flows
889925 reinvest_rate : scalar
890926 Interest rate received on the cash flows upon reinvestment
927+ raise_exceptions: bool, optional
928+ Flag to raise an exception when the mirr cannot be computed due to
929+ having all cashflows of the same sign (NoRealSolutionException).
930+ Set to False as default, thus returning NaNs in the previous
931+ case.
891932
892933 Returns
893934 -------
@@ -898,6 +939,13 @@ def mirr(values, finance_rate, reinvest_rate):
898939 values = np .asarray (values )
899940 n = values .size
900941
942+ # Define the custom Exception in case the flag raise_exceptions
943+ # is set to True
944+ if raise_exceptions :
945+ class NoRealSolutionException (Exception ):
946+ "Raised when all the input cashflows are of the same sign"
947+ pass
948+
901949 # Without this explicit cast the 1/(n - 1) computation below
902950 # becomes a float, which causes TypeError when using Decimal
903951 # values.
@@ -907,6 +955,9 @@ def mirr(values, finance_rate, reinvest_rate):
907955 pos = values > 0
908956 neg = values < 0
909957 if not (pos .any () and neg .any ()):
958+ if raise_exceptions :
959+ raise NoRealSolutionException ('\n No real solution exists for IRR since'
960+ ' all cashflows are of the same sign.\n ' )
910961 return np .nan
911962 numer = np .abs (npv (reinvest_rate , values * pos ))
912963 denom = np .abs (npv (finance_rate , values * neg ))
0 commit comments