1717import numpy as np
1818
1919__all__ = ['fv' , 'pmt' , 'nper' , 'ipmt' , 'ppmt' , 'pv' , 'rate' ,
20- 'irr' , 'npv' , 'mirr' ]
20+ 'irr' , 'npv' , 'mirr' ,
21+ 'NoRealSolutionException' , 'IterationsExceededException' ]
2122
2223_when_to_num = {'end' : 0 , 'begin' : 1 ,
2324 'e' : 0 , 'b' : 1 ,
2627 'start' : 1 ,
2728 'finish' : 0 }
2829
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+
2942
3043def _convert_when (when ):
3144 # 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):
598611# where
599612# g(r) is the formula
600613# 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 ):
602615 """
603616 Compute the rate of interest per period.
604617
@@ -620,6 +633,11 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100):
620633 Required tolerance for the solution, default 1e-6
621634 maxiter : int, optional
622635 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.
623641
624642 Notes
625643 -----
@@ -666,15 +684,20 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100):
666684
667685 if not np .all (close ):
668686 if np .isscalar (rn ):
687+ if raise_exceptions :
688+ raise IterationsExceededException ('Maximum number of iterations exceeded.' )
669689 return default_type (np .nan )
670690 else :
671691 # Return nan's in array of the same shape as rn
672692 # 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).' )
673696 rn [~ close ] = np .nan
674697 return rn
675698
676699
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 ):
678701 """
679702 Return the Internal Rate of Return (IRR).
680703
@@ -699,6 +722,12 @@ def irr(values, guess=None, tol=1e-12, maxiter=100):
699722 Required tolerance to accept solution. Default is 1e-12.
700723 maxiter : int, optional
701724 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.
702731
703732 Returns
704733 -------
@@ -753,6 +782,9 @@ def irr(values, guess=None, tol=1e-12, maxiter=100):
753782 # we don't perform any further calculations and exit early
754783 same_sign = np .all (values > 0 ) if values [0 ] > 0 else np .all (values < 0 )
755784 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.' )
756788 return np .nan
757789
758790 # 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):
789821 return g - 1
790822 g -= delta
791823
824+ if raise_exceptions :
825+ raise IterationsExceededException ('Maximum number of iterations exceeded.' )
826+
792827 return np .nan
793828
794829
@@ -871,7 +906,7 @@ def npv(rate, values):
871906 return npv
872907
873908
874- def mirr (values , finance_rate , reinvest_rate ):
909+ def mirr (values , finance_rate , reinvest_rate , * , raise_exceptions = False ):
875910 """
876911 Modified internal rate of return.
877912
@@ -885,6 +920,11 @@ def mirr(values, finance_rate, reinvest_rate):
885920 Interest rate paid on the cash flows
886921 reinvest_rate : scalar
887922 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.
888928
889929 Returns
890930 -------
@@ -904,6 +944,9 @@ def mirr(values, finance_rate, reinvest_rate):
904944 pos = values > 0
905945 neg = values < 0
906946 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.' )
907950 return np .nan
908951 numer = np .abs (npv (reinvest_rate , values * pos ))
909952 denom = np .abs (npv (finance_rate , values * neg ))
0 commit comments