Skip to content

Commit 50c0930

Browse files
authored
Merge pull request #99 from yatshunlee/main
Updated irr function [issue 98]
2 parents 8232cb4 + 605bab2 commit 50c0930

File tree

2 files changed

+32
-36
lines changed

2 files changed

+32
-36
lines changed

numpy_financial/_financial.py

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ def rate(
710710
return rn
711711

712712

713-
def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
713+
def irr(values, *, raise_exceptions=False):
714714
r"""Return the Internal Rate of Return (IRR).
715715
716716
This is the "average" periodically compounded rate of return
@@ -726,14 +726,6 @@ def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
726726
are negative and net "withdrawals" are positive. Thus, for
727727
example, at least the first element of `values`, which represents
728728
the initial investment, will typically be negative.
729-
guess : float, optional
730-
Initial guess of the IRR for the iterative solver. If no guess is
731-
given an heuristic is used to estimate the guess through the ratio of
732-
positive to negative cash lows
733-
tol : float, optional
734-
Required tolerance to accept solution. Default is 1e-12.
735-
maxiter : int, optional
736-
Maximum iterations to perform in finding a solution. Default is 100.
737729
raise_exceptions: bool, optional
738730
Flag to raise an exception when the irr cannot be computed due to
739731
either having all cashflows of the same sign (NoRealSolutionException) or
@@ -799,13 +791,6 @@ def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
799791
'cashflows are of the same sign.')
800792
return np.nan
801793

802-
# If no value is passed for `guess`, then make a heuristic estimate
803-
if guess is None:
804-
positive_cashflow = values > 0
805-
inflow = values.sum(where=positive_cashflow)
806-
outflow = -values.sum(where=~positive_cashflow)
807-
guess = inflow / outflow - 1
808-
809794
# We aim to solve eirr such that NPV is exactly zero. This can be framed as
810795
# simply finding the closest root of a polynomial to a given initial guess
811796
# as follows:
@@ -823,20 +808,40 @@ def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
823808
#
824809
# which we solve using Newton-Raphson and then reverse out the solution
825810
# as eirr = g - 1 (if we are close enough to a solution)
826-
npv_ = np.polynomial.Polynomial(values[::-1])
827-
d_npv = npv_.deriv()
828-
g = 1 + guess
811+
812+
g = np.roots(values)
813+
eirr = np.real(g[np.isreal(g)]) - 1
829814

830-
for _ in range(maxiter):
831-
delta = npv_(g) / d_npv(g)
832-
if abs(delta) < tol:
833-
return g - 1
834-
g -= delta
815+
# realistic IRR
816+
eirr = eirr[eirr>=-1]
835817

836-
if raise_exceptions:
837-
raise IterationsExceededError('Maximum number of iterations exceeded.')
818+
# if no real solution
819+
if len(eirr) == 0:
820+
if raise_exceptions:
821+
raise NoRealSolutionError("No real solution is found for IRR.")
822+
return np.nan
838823

839-
return np.nan
824+
# if only one real solution
825+
if len(eirr) == 1:
826+
return eirr[0]
827+
828+
# below is for the situation when there are more than 2 real solutions.
829+
# check sign of all IRR solutions
830+
same_sign = np.all(eirr > 0) if eirr[0] > 0 else np.all(eirr < 0)
831+
832+
# if the signs of IRR solutions are not the same, first filter potential IRR
833+
# by comparing the total positive and negative cash flows.
834+
if not same_sign:
835+
pos = sum(values[values>0])
836+
neg = sum(values[values<0])
837+
if pos >= neg:
838+
eirr = eirr[eirr>=0]
839+
else:
840+
eirr = eirr[eirr<0]
841+
842+
# pick the smallest one in magnitude and return
843+
abs_eirr = np.abs(eirr)
844+
return eirr[np.argmin(abs_eirr)]
840845

841846

842847
@nb.njit

tests/test_financial.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -860,12 +860,3 @@ def test_irr_no_real_solution_exception(self):
860860

861861
with pytest.raises(npf.NoRealSolutionError):
862862
npf.irr(cashflows, raise_exceptions=True)
863-
864-
def test_irr_maximum_iterations_exception(self):
865-
# Test that if the maximum number of iterations is reached,
866-
# then npf.irr returns IterationsExceededException
867-
# when raise_exceptions is set to True.
868-
cashflows = numpy.array([-40000, 5000, 8000, 12000, 30000])
869-
870-
with pytest.raises(npf.IterationsExceededError):
871-
npf.irr(cashflows, maxiter=1, raise_exceptions=True)

0 commit comments

Comments
 (0)