@@ -671,36 +671,7 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100):
671
671
return rn
672
672
673
673
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 ):
704
675
"""
705
676
Return the Internal Rate of Return (IRR).
706
677
@@ -717,6 +688,9 @@ def irr(values):
717
688
are negative and net "withdrawals" are positive. Thus, for
718
689
example, at least the first element of `values`, which represents
719
690
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.
720
694
721
695
Returns
722
696
-------
@@ -767,28 +741,23 @@ def irr(values):
767
741
if values .ndim != 1 :
768
742
raise ValueError ("Cashflows must be a rank-1 array" )
769
743
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 )
774
748
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
776
755
777
- mask = (res .imag == 0 ) & (res .real > 0 )
778
- if not mask .any ():
756
+ if solution_found :
757
+ return 1 / x - 1
758
+ else :
779
759
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
+
792
761
793
762
794
763
def npv (rate , values ):
0 commit comments