Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor selection logic for IRR #121

Merged
merged 4 commits into from
May 7, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 29 additions & 18 deletions numpy_financial/_financial.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,27 @@ def rate(
return rn


def irr(values, *, raise_exceptions=False):
# default selection logic for IRR function when there are > 2 real solutions
def _irr_default_selection(eirr):
# check sign of all IRR solutions
Comment on lines +712 to +714
Copy link
Member

@Kai-Striega Kai-Striega Apr 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# default selection logic for IRR function when there are > 2 real solutions
def _irr_default_selection(eirr):
# check sign of all IRR solutions
def _irr_default_selection(eirr):
"""Default selection logic for IRR function when there is > 1 real solution"""
# check sign of all IRR solutions

Python treats triple quoted strings directly following a function/method/class definition as docstrings. These are special comments that are used to document the function/method/class.

It dosen't matter for this function (as it's private), but, for public functions/methods/classes, please follow the numpydoc style guide.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also you currently have > 2 real solutions, this should be > 1 real solutions

same_sign = np.all(eirr > 0) if eirr[0] > 0 else np.all(eirr < 0)

# if the signs of IRR solutions are not the same, first filter potential IRR
# by comparing the total positive and negative cash flows.
if not same_sign:
pos = sum(eirr[eirr > 0])
neg = sum(eirr[eirr < 0])
if pos >= neg:
eirr = eirr[eirr >= 0]
else:
eirr = eirr[eirr < 0]

# pick the smallest one in magnitude and return
abs_eirr = np.abs(eirr)
return eirr[np.argmin(abs_eirr)]


def irr(values, *, raise_exceptions=False, selection_logic=_irr_default_selection):
r"""Return the Internal Rate of Return (IRR).

This is the "average" periodically compounded rate of return
Expand All @@ -731,6 +751,12 @@ def irr(values, *, raise_exceptions=False):
having reached the maximum number of iterations (IterationsExceededException).
Set to False as default, thus returning NaNs in the two previous
cases.
selection_logic: function, optional
Function for selection logic when more than 1 real solutions is found.
User may insert their own customised function for selection
of IRR values.The function should accept a one-dimensional array
of numbers and return a number.


Returns
-------
Expand Down Expand Up @@ -824,23 +850,8 @@ def irr(values, *, raise_exceptions=False):
if len(eirr) == 1:
return eirr[0]

# below is for the situation when there are more than 2 real solutions.
# check sign of all IRR solutions
same_sign = np.all(eirr > 0) if eirr[0] > 0 else np.all(eirr < 0)

# if the signs of IRR solutions are not the same, first filter potential IRR
# by comparing the total positive and negative cash flows.
if not same_sign:
pos = sum(values[values>0])
neg = sum(values[values<0])
if pos >= neg:
eirr = eirr[eirr>=0]
else:
eirr = eirr[eirr<0]

# pick the smallest one in magnitude and return
abs_eirr = np.abs(eirr)
return eirr[np.argmin(abs_eirr)]
eirr = selection_logic(eirr)
return eirr


def npv(rate, values):
Expand Down
Loading