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

Improve Numerical Instability in XIRR function #62

Open
yugui923 opened this issue Dec 7, 2024 · 1 comment
Open

Improve Numerical Instability in XIRR function #62

yugui923 opened this issue Dec 7, 2024 · 1 comment

Comments

@yugui923
Copy link

yugui923 commented Dec 7, 2024

XIRR function will produce unstable results when the input cashflow is reasonably large (e.g. 1 * 10^12)
The simple fix to this is to normalize the input cashflow sequence, which will greatly improve the numerical stability of the output.
pyxirr could check for the order of magnitude of the input cashflow, and apply proactive normalization to the inputs to ensure numerical stability, at a low marginal cost of precision.

See the example fix below:

import datetime

import pyxirr

dates = [
    datetime.date(2000, 1, 1),
    datetime.date(1994, 6, 15),
    datetime.date(1994, 7, 12),
    datetime.date(2017, 10, 30),
    datetime.date(2008, 1, 22),
    datetime.date(1977, 7, 1),
    datetime.date(2005, 12, 30),
    datetime.date(2005, 12, 30),
    datetime.date(2005, 12, 30),
    datetime.date(1976, 12, 19),
    datetime.date(1976, 12, 19),
    datetime.date(1976, 12, 19),
    datetime.date(2107, 3, 23),
]

cf1 = [
    -253524383149.60605,
    -0.0,
    -0.0,
    -0.0,
    -0.0,
    -1.1201669784316488,
    -0.01,
    -0.0,
    -0.0,
    -0.0,
    -0.0,
    -0.0,
    32017.129321072072,
]
cf2 = [
    -253524383149.60605,
    -0.0,
    -0.0,
    -0.0,
    -0.0,
    -1.1201669784316488,
    -0.01,
    -0.0,
    -0.0,
    -0.0,
    -0.0,
    -0.0,
    36590.74097669936,
]

# Exhibit 1
# Falsifying example, if large floats are not not normalized
xirr1 = pyxirr.xirr(dates, cf1)
xirr2 = pyxirr.xirr(dates, cf2)

print(xirr1)
print(xirr2)

# Results:
# -0.13761159201780845
# 1.800265354828463e+17

# Exhibit 2
# Normalize cash flows
max_cf1 = max(abs(x) for x in cf1)
max_cf2 = max(abs(x) for x in cf2)

normalized_cf1 = [x / max_cf1 for x in cf1]
normalized_cf2 = [x / max_cf2 for x in cf2]

# Calculate XIRR on normalized cash flows
xirr1 = pyxirr.xirr(dates, normalized_cf1)
xirr2 = pyxirr.xirr(dates, normalized_cf2)

print(xirr1)
print(xirr2)

# Normalized results:
# -0.13761159201780845
# -0.13653769882244854

@Anexen
Copy link
Owner

Anexen commented Dec 10, 2024

@yugui923, thanks for the detailed description. I definitely want to implement normalization. It could also be helpful in cases with small intervals between dates.
I am also thinking about a data cleanup procedure, e.g, removing zero coefficients after t0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants