@@ -671,36 +671,7 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100):
671671 return rn
672672
673673
674- def _roots (p ):
675- """Modified version of NumPy's roots function.
676-
677- NumPy's roots uses the companion matrix method, which divides by
678- p[0]. This can causes overflows/underflows. Instead form a
679- modified companion matrix that is scaled by 2^c * p[0], where the
680- exponent c is chosen to balance the magnitudes of the
681- coefficients. Since scaling the matrix just scales the
682- eigenvalues, we can remove the scaling at the end.
683-
684- Scaling by a power of 2 is chosen to avoid rounding errors.
685-
686- """
687- _ , e = np .frexp (p )
688- # Balance the most extreme exponents e_max and e_min by solving
689- # the equation
690- #
691- # |c + e_max| = |c + e_min|.
692- #
693- # Round the exponent to an integer to avoid rounding errors.
694- c = int (- 0.5 * (np .max (e ) + np .min (e )))
695- p = np .ldexp (p , c )
696-
697- A = np .diag (np .full (p .size - 2 , p [0 ]), k = - 1 )
698- A [0 ,:] = - p [1 :]
699- eigenvalues = np .linalg .eigvals (A )
700- return eigenvalues / p [0 ]
701-
702-
703- def irr (values ):
674+ def irr (values , guess = 0.1 ):
704675 """
705676 Return the Internal Rate of Return (IRR).
706677
@@ -717,6 +688,9 @@ def irr(values):
717688 are negative and net "withdrawals" are positive. Thus, for
718689 example, at least the first element of `values`, which represents
719690 the initial investment, will typically be negative.
691+ guess : float, optional
692+ Initial guess of the IRR for the iterative solver. If no guess is
693+ given 0.1 is used instead.
720694
721695 Returns
722696 -------
@@ -767,28 +741,23 @@ def irr(values):
767741 if values .ndim != 1 :
768742 raise ValueError ("Cashflows must be a rank-1 array" )
769743
770- # Strip leading and trailing zeros. Since we only care about
771- # positive roots we can neglect roots at zero.
772- non_zero = np . nonzero ( np . ravel ( values ))[ 0 ]
773- values = values [ int ( non_zero [ 0 ]): int ( non_zero [ - 1 ]) + 1 ]
744+ solution_found = False
745+ p = np . polynomial . Polynomial ( values )
746+ pp = p . deriv ()
747+ x = 1 / ( 1 + guess )
774748
775- res = _roots (values [::- 1 ])
749+ for i in range (100 ):
750+ x_new = x - (p (x ) / pp (x ))
751+ if abs (x_new - x ) < 1e-12 :
752+ solution_found = True
753+ break
754+ x = x_new
776755
777- mask = (res .imag == 0 ) & (res .real > 0 )
778- if not mask .any ():
756+ if solution_found :
757+ return 1 / x - 1
758+ else :
779759 return np .nan
780- res = res [mask ].real
781- # NPV(rate) = 0 can have more than one solution so we return
782- # only the solution closest to zero.
783- rate = 1 / res - 1
784-
785- # If there are any positive solutions prefer those over negative
786- # rates.
787- if (rate > 0 ).any ():
788- rate = np .where (rate > 0 , rate , np .inf )
789-
790- rate = rate .item (np .argmin (np .abs (rate )))
791- return rate
760+
792761
793762
794763def npv (rate , values ):
0 commit comments