1010Functions support the :class:`decimal.Decimal` type unless
1111otherwise stated.
1212"""
13- from __future__ import division , absolute_import , print_function
13+ from __future__ import absolute_import , division , print_function
1414
1515from decimal import Decimal
1616
1717import numpy as np
1818
19-
2019__all__ = ['fv' , 'pmt' , 'nper' , 'ipmt' , 'ppmt' , 'pv' , 'rate' ,
2120 'irr' , 'npv' , 'mirr' ]
2221
@@ -675,7 +674,7 @@ def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100):
675674 return rn
676675
677676
678- def irr (values , guess = 0.1 , tol = 1e-12 , maxiter = 100 ):
677+ def irr (values , guess = None , tol = 1e-12 , maxiter = 100 ):
679678 """
680679 Return the Internal Rate of Return (IRR).
681680
@@ -694,7 +693,8 @@ def irr(values, guess=0.1, tol=1e-12, maxiter=100):
694693 the initial investment, will typically be negative.
695694 guess : float, optional
696695 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
698698 tol : float, optional
699699 Required tolerance to accept solution. Default is 1e-12.
700700 maxiter : int, optional
@@ -755,28 +755,39 @@ def irr(values, guess=0.1, tol=1e-12, maxiter=100):
755755 if same_sign :
756756 return np .nan
757757
758+ # If no value is passed for `guess`, then make a heuristic estimate
759+ if guess is None :
760+ positive_cashflow = values > 0
761+ inflow = values .sum (where = positive_cashflow )
762+ outflow = - values .sum (where = ~ positive_cashflow )
763+ guess = inflow / outflow - 1
764+
758765 # We aim to solve eirr such that NPV is exactly zero. This can be framed as
759766 # simply finding the closest root of a polynomial to a given initial guess
760767 # as follows:
761768 # V0 V1 V2 V3
762- # NPV = ---------- + ---------- + ---------- + ---------- + ...
769+ # NPV = ---------- + ---------- + ---------- + ---------- + ... = 0
763770 # (1+eirr)^0 (1+eirr)^1 (1+eirr)^2 (1+eirr)^3
764771 #
765- # by letting x = 1 / (1+eirr), we substitute to get
772+ # by letting g = (1+eirr), we substitute to get
773+ #
774+ # NPV = V0 * 1/g^0 + V1 * 1/g^1 + V2 * 1/x^2 + V3 * 1/g^3 + ... = 0
775+ #
776+ # Multiplying by g^N this becomes
777+ #
778+ # V0 * g^N + V1 * g^{N-1} + V2 * g^{N-2} + V3 * g^{N-3} + ... = 0
766779 #
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 )
780+ # which we solve using Newton-Raphson and then reverse out the solution
781+ # as eirr = g - 1 (if we are close enough to a solution)
782+ npv_ = np .polynomial .Polynomial (values [::- 1 ])
772783 d_npv = npv_ .deriv ()
773- x = 1 / ( 1 + guess )
784+ g = 1 + guess
774785
775786 for _ in range (maxiter ):
776- x_new = x - ( npv_ (x ) / d_npv (x ) )
777- if abs (x_new - x ) < tol :
778- return 1 / x_new - 1
779- x = x_new
787+ delta = npv_ (g ) / d_npv (g )
788+ if abs (delta ) < tol :
789+ return g - 1
790+ g -= delta
780791
781792 return np .nan
782793
0 commit comments