@@ -709,7 +709,27 @@ def rate(
709709 return rn
710710
711711
712- def irr (values , * , raise_exceptions = False ):
712+ def _irr_default_selection (eirr ):
713+ """ default selection logic for IRR function when there are > 1 real solutions """
714+ # check sign of all IRR solutions
715+ same_sign = np .all (eirr > 0 ) if eirr [0 ] > 0 else np .all (eirr < 0 )
716+
717+ # if the signs of IRR solutions are not the same, first filter potential IRR
718+ # by comparing the total positive and negative cash flows.
719+ if not same_sign :
720+ pos = sum (eirr [eirr > 0 ])
721+ neg = sum (eirr [eirr < 0 ])
722+ if pos >= neg :
723+ eirr = eirr [eirr >= 0 ]
724+ else :
725+ eirr = eirr [eirr < 0 ]
726+
727+ # pick the smallest one in magnitude and return
728+ abs_eirr = np .abs (eirr )
729+ return eirr [np .argmin (abs_eirr )]
730+
731+
732+ def irr (values , * , raise_exceptions = False , selection_logic = _irr_default_selection ):
713733 r"""Return the Internal Rate of Return (IRR).
714734
715735 This is the "average" periodically compounded rate of return
@@ -731,6 +751,12 @@ def irr(values, *, raise_exceptions=False):
731751 having reached the maximum number of iterations (IterationsExceededException).
732752 Set to False as default, thus returning NaNs in the two previous
733753 cases.
754+ selection_logic: function, optional
755+ Function for selection logic when more than 1 real solutions is found.
756+ User may insert their own customised function for selection
757+ of IRR values.The function should accept a one-dimensional array
758+ of numbers and return a number.
759+
734760
735761 Returns
736762 -------
@@ -775,20 +801,24 @@ def irr(values, *, raise_exceptions=False):
775801 0.06206
776802 >>> round(npf.irr([-5, 10.5, 1, -8, 1]), 5)
777803 0.0886
778-
804+ >>> npf.irr([[-100, 0, 0, 74], [-100, 100, 0, 7]]).round(5)
805+ array([-0.0955 , 0.06206])
806+
779807 """
780- values = np .atleast_1d (values )
781- if values .ndim != 1 :
782- raise ValueError ("Cashflows must be a rank-1 array" )
783-
784- # If all values are of the same sign no solution exists
785- # we don't perform any further calculations and exit early
786- same_sign = np .all (values > 0 ) if values [0 ] > 0 else np .all (values < 0 )
787- if same_sign :
788- if raise_exceptions :
789- raise NoRealSolutionError ('No real solution exists for IRR since all '
790- 'cashflows are of the same sign.' )
791- return np .nan
808+ values = np .atleast_2d (values )
809+ if values .ndim != 2 :
810+ raise ValueError ("Cashflows must be a 2D array" )
811+
812+ irr_results = np .empty (values .shape [0 ])
813+ for i , row in enumerate (values ):
814+ # If all values are of the same sign, no solution exists
815+ # We don't perform any further calculations and exit early
816+ same_sign = np .all (row > 0 ) if row [0 ] > 0 else np .all (row < 0 )
817+ if same_sign :
818+ if raise_exceptions :
819+ raise NoRealSolutionError ('No real solution exists for IRR since all '
820+ 'cashflows are of the same sign.' )
821+ irr_results [i ] = np .nan
792822
793823 # We aim to solve eirr such that NPV is exactly zero. This can be framed as
794824 # simply finding the closest root of a polynomial to a given initial guess
@@ -807,40 +837,25 @@ def irr(values, *, raise_exceptions=False):
807837 #
808838 # which we solve using Newton-Raphson and then reverse out the solution
809839 # as eirr = g - 1 (if we are close enough to a solution)
810-
811- g = np .roots (values )
812- eirr = np .real (g [np .isreal (g )]) - 1
813-
814- # realistic IRR
815- eirr = eirr [eirr >= - 1 ]
816-
817- # if no real solution
818- if len (eirr ) == 0 :
819- if raise_exceptions :
820- raise NoRealSolutionError ("No real solution is found for IRR." )
821- return np .nan
822-
823- # if only one real solution
824- if len (eirr ) == 1 :
825- return eirr [0 ]
826-
827- # below is for the situation when there are more than 2 real solutions.
828- # check sign of all IRR solutions
829- same_sign = np .all (eirr > 0 ) if eirr [0 ] > 0 else np .all (eirr < 0 )
830-
831- # if the signs of IRR solutions are not the same, first filter potential IRR
832- # by comparing the total positive and negative cash flows.
833- if not same_sign :
834- pos = sum (values [values > 0 ])
835- neg = sum (values [values < 0 ])
836- if pos >= neg :
837- eirr = eirr [eirr >= 0 ]
838840 else :
839- eirr = eirr [eirr < 0 ]
840-
841- # pick the smallest one in magnitude and return
842- abs_eirr = np .abs (eirr )
843- return eirr [np .argmin (abs_eirr )]
841+ g = np .roots (row )
842+ eirr = np .real (g [np .isreal (g )]) - 1
843+
844+ # Realistic IRR
845+ eirr = eirr [eirr >= - 1 ]
846+
847+ # If no real solution
848+ if len (eirr ) == 0 :
849+ if raise_exceptions :
850+ raise NoRealSolutionError ("No real solution is found for IRR." )
851+ irr_results [i ] = np .nan
852+ # If only one real solution
853+ elif len (eirr ) == 1 :
854+ irr_results [i ] = eirr [0 ]
855+ else :
856+ irr_results [i ] = selection_logic (eirr )
857+
858+ return _ufunc_like (irr_results )
844859
845860
846861def npv (rate , values ):
0 commit comments