Skip to content

Commit 56157cd

Browse files
committed
Added exceptions to irr, mirr and rate functions
1 parent 3519cdf commit 56157cd

File tree

1 file changed

+57
-6
lines changed

1 file changed

+57
-6
lines changed

numpy_financial/_financial.py

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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('\nNo 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('\nNo 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('\nNo 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

Comments
 (0)