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,38 @@ 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+ 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+
758764 # We aim to solve eirr such that NPV is exactly zero. This can be framed as
759765 # simply finding the closest root of a polynomial to a given initial guess
760766 # as follows:
761767 # V0 V1 V2 V3
762- # NPV = ---------- + ---------- + ---------- + ---------- + ...
768+ # NPV = ---------- + ---------- + ---------- + ---------- + ... = 0
763769 # (1+eirr)^0 (1+eirr)^1 (1+eirr)^2 (1+eirr)^3
764770 #
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
766778 #
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 ])
772782 d_npv = npv_ .deriv ()
773- x = 1 / ( 1 + guess )
783+ g = 1 + guess
774784
775785 for _ in range (maxiter ):
776- delta = npv_ (x ) / d_npv (x )
786+ delta = npv_ (g ) / d_npv (g )
777787 if abs (delta ) < tol :
778- return 1 / ( x - delta ) - 1
779- x -= delta
788+ return g - 1
789+ g -= delta
780790
781791 return np .nan
782792
0 commit comments