10
10
Functions support the :class:`decimal.Decimal` type unless
11
11
otherwise stated.
12
12
"""
13
- from __future__ import division , absolute_import , print_function
13
+ from __future__ import absolute_import , division , print_function
14
14
15
15
from decimal import Decimal
16
16
17
17
import numpy as np
18
18
19
-
20
19
__all__ = ['fv' , 'pmt' , 'nper' , 'ipmt' , 'ppmt' , 'pv' , 'rate' ,
21
20
'irr' , 'npv' , 'mirr' ]
22
21
@@ -675,7 +674,7 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100):
675
674
return rn
676
675
677
676
678
- def irr (values , guess = 0.1 , tol = 1e-12 , maxiter = 100 ):
677
+ def irr (values , guess = None , tol = 1e-12 , maxiter = 100 ):
679
678
"""
680
679
Return the Internal Rate of Return (IRR).
681
680
@@ -694,7 +693,8 @@ def irr(values, guess=0.1, tol=1e-12, maxiter=100):
694
693
the initial investment, will typically be negative.
695
694
guess : float, optional
696
695
Initial guess of the IRR for the iterative solver. If no guess is
697
- given an initial guess of 0.1 (i.e. 10%) is assumed instead.
696
+ given an heuristic is used to estimate the guess through the ratio of
697
+ positive to negative cash lows
698
698
tol : float, optional
699
699
Required tolerance to accept solution. Default is 1e-12.
700
700
maxiter : int, optional
@@ -755,28 +755,38 @@ def irr(values, guess=0.1, tol=1e-12, maxiter=100):
755
755
if same_sign :
756
756
return np .nan
757
757
758
+ # If no value is passed for `guess`, then make a heuristic estimate
759
+ if guess is None :
760
+ inflow = sum (x for x in values if x > 0 )
761
+ outflow = - sum (x for x in values if x < 0 )
762
+ guess = inflow / outflow - 1
763
+
758
764
# We aim to solve eirr such that NPV is exactly zero. This can be framed as
759
765
# simply finding the closest root of a polynomial to a given initial guess
760
766
# as follows:
761
767
# V0 V1 V2 V3
762
- # NPV = ---------- + ---------- + ---------- + ---------- + ...
768
+ # NPV = ---------- + ---------- + ---------- + ---------- + ... = 0
763
769
# (1+eirr)^0 (1+eirr)^1 (1+eirr)^2 (1+eirr)^3
764
770
#
765
- # by letting x = 1 / (1+eirr), we substitute to get
771
+ # by letting g = (1+eirr), we substitute to get
772
+ #
773
+ # NPV = V0 * 1/g^0 + V1 * 1/g^1 + V2 * 1/x^2 + V3 * 1/g^3 + ... = 0
774
+ #
775
+ # Multiplying by g^N this becomes
776
+ #
777
+ # V0 * g^N + V1 * g^{N-1} + V2 * g^{N-2} + V3 * g^{N-3} + ... = 0
766
778
#
767
- # NPV = V0 * x^0 + V1 * x^1 + V2 * x^2 + V3 * x^3 + ...
768
- #
769
- # which we solve using Newton-Raphson and then reverse out the solution
770
- # as eirr = 1/x - 1 (if we are close enough to a solution)
771
- npv_ = np .polynomial .Polynomial (values )
779
+ # which we solve using Newton-Raphson and then reverse out the solution
780
+ # as eirr = g - 1 (if we are close enough to a solution)
781
+ npv_ = np .polynomial .Polynomial (values [::- 1 ])
772
782
d_npv = npv_ .deriv ()
773
- x = 1 / ( 1 + guess )
783
+ g = 1 + guess
774
784
775
785
for _ in range (maxiter ):
776
- delta = npv_ (x ) / d_npv (x )
786
+ delta = npv_ (g ) / d_npv (g )
777
787
if abs (delta ) < tol :
778
- return 1 / ( x - delta ) - 1
779
- x -= delta
788
+ return g - 1
789
+ g -= delta
780
790
781
791
return np .nan
782
792
0 commit comments