GithubHelp home page GithubHelp logo

anexen / pyxirr Goto Github PK

View Code? Open in Web Editor NEW
146.0 8.0 13.0 847 KB

Rust-powered collection of financial functions.

Home Page: https://anexen.github.io/pyxirr/

License: The Unlicense

Rust 92.11% Python 7.89%
xirr pyo3 financial-analysis xnpv fast maturin python-extension python rust numpy

pyxirr's People

Contributors

amotzop avatar anexen avatar bernardofcordeiro avatar harkylton avatar tijptjik avatar westandskif avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pyxirr's Issues

Using silent in xnfv

Trying to use keyword silent=True in xnfv but getting keyword not recognized error. Appears to be working in other functions though so wondered if it hasn't been implemented into xnfv yet despite being in documentation. Apologies in advance if this is just a use case issue on my end.

Negative IRR

Hi

Pyxirr is really a good package. The performance is really good. When we using irr function, got a weird issue. If we feed 241, 361, or 481 cash flows (including 1 initial loan, and 240,360, and 480 monthly paybacks) to irr function, we got a negative number (-23.848998449508812). Can you please take a look and help us to fix it?

Here is our testing code:

from pyxirr import irr
import numpy_financial as npf

cf = [-172545.848122807] + [787.735232517999] * 480

print(len(cf))
irr_rt= irr(cf)*12
print(irr_rt)

irr_rt = npf.irr(cf)*12
print(irr_rt)

add `XFV` function

Add XFV function or add description on how it can be calculated using the existing XIRR and XNPV

Can't install from wheels for 3.10 on arm linux (inside M1 host docker)

I'm trying to install it on an arm linux docker container, running on a M1 macbook host.

On my host it downloads the wheels properly:

% python3 --version
Python 3.10.6

% python3 -m pip install pyxirr --no-cache
Collecting pyxirr
  Downloading pyxirr-0.7.2-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl (406 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 406.5/406.5 kB 13.2 MB/s eta 0:00:00
Installing collected packages: pyxirr
Successfully installed pyxirr-0.7.2

It installs correctly up to python 3.9:

# python --version
Python 3.9.14
# pip install pyxirr --no-cache
Collecting pyxirr
  Downloading pyxirr-0.7.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (207 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 207.3/207.3 KB 11.1 MB/s eta 0:00:00
Installing collected packages: pyxirr
Successfully installed pyxirr-0.7.2

but fails with python 3.10:

# pip install pyxirr --no-cache
Collecting pyxirr
  Downloading pyxirr-0.7.2.tar.gz (128 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 128.7/128.7 kB 10.4 MB/s eta 0:00:00
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Preparing metadata (pyproject.toml) ... error
  error: subprocess-exited-with-error
  
  × Preparing metadata (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> [6 lines of output]
      
      Cargo, the Rust package manager, is not installed or is not on PATH.
      This package requires Rust and Cargo to compile extensions. Install it through
      the system's package manager or via https://rustup.rs/
      
      Checking for Rust toolchain....
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed

× Encountered error while generating package metadata.
╰─> See above for output.

note: This is an issue with the package mentioned above, not pip.
hint: See above for details.

Interest in adding private equity metrics?

Having other PE metrics in the same package would be useful for my project (and I'm guessing others?)

I'm thinking of adding DPI, MOIC and Modified IRR as basic metrics and then adding public market equivalent (PME) metrics too (the same metrics but rescaling the PME "cash flows" based on standard methods such as Kaplan Schoar)

For all of these, other than Modified IRR, there would need to be a wrapper on the input data to either separate out positive (distributions) and negative (contributions) cash flows or the two series could be input separately. For Modified IRR, it would just require discount rate and investment rate inputs. For the PMEs the user would need to supply either price or return series for the PME

Also, I'm willing to mock something up that works in python if there is any interest, I just don't have any experience with Rust, so would need help converting it. I already have something in my project, but would need to separate it out to make it clearer

Following input leads to None output

from pyxirr import xirr
from datetime import date

dates = [date(2020, 3, 5), date(2020, 3, 16)]
values = [-18480.0, 13120.0]
print(xirr(dates, values))

Payment scenarios where IRR returns None

First off, I wanted to say that I LOVE this library. I tested moving from 0.6.4 -> 0.7.2 and it seems like the IRR calcs are somehow twice as fast as they were. So thats incredible. I was hoping that it would reduce None results as well, but it does not. I was able to come up with ~1700 situations where pyxirr returns None and numpy financial returns a value. (There were 1707 in 0.6.4 and the same 1707 fail in 0.7.2) NPF is rather slow as you know and it'd be awesome to not have to fallback to NPF at all.

I wanted to get in and see if I could figure out what the issue is but I don't have any r experience - yet.

Here is a file containing the payment info of the ~1700 scenarios
failed-irr.txt

I just had a quick script like this:

import json
from pyxirr import irr

with open("failed-irr.txt", "r") as file_object:
    bad_payments_list = json.loads(file_object.read())

fail_count = 0
success_count = 0

for bad_payment in bad_payments_list:
    monthly_irr = irr(bad_payment)
    if monthly_irr is None:
        fail_count += 1
    else:
        success_count += 1

print('Successful: ', success_count)
print('Failed: ', fail_count)

It takes a bit to run through all 1700 calcs. I realize this is open source and you likely have limited time to look at this. Let me know if theres anything else I can provide to help.

New release?

Any chance I can convince you to do a new point release that includes the change on HEAD to upgrade maturin? (I'm trying to import this in a build system that knows about PEP 621 and the updated pyproject.toml would help it automatically figure out dependencies.) Thanks!

`ipmt()` and `ppmt()` do not accept an array as `per`

It seems that is fine with numpy_financial:

>>> numpy_financial.ipmt(rate=0.03 / 12, per=numpy.arange(35) + 1, nper=35, pv=10000)
array([-25.        , -24.3156167 , -23.62952244, -22.94171294, ...

However, with pyxirr:

>>> pyxirr.ipmt(rate=0.03 / 12, per=numpy.arange(35) + 1, nper=35, pv=10000)
TypeError: argument 'per': only size-1 arrays can be converted to Python scalars

Not sure if it is related or not, but the returned type when not using an array is also different:

>>> numpy_financial.ipmt(rate=0.03 / 12, per=1, nper=35, pv=10000)
array(-25.)                                                                    
>>> pyxirr.ipmt(rate=0.03 / 12, per=1, nper=35, pv=10000)                          
-25

Wrong irr values

We are getting very different irr values for slightly different cashflows.

cf1
out[16]: 
array([-1.44852555e+08,  1.28859998e+06,  1.27305118e+06,  1.25407349e+06,
        1.24199669e+06,  1.22647792e+06,  1.21095552e+06,  1.19206955e+06,
        1.17989821e+06,  1.16436524e+06,  1.14883185e+06,  1.12945217e+06,
        1.11780102e+06,  1.10228427e+06,  1.08671783e+06,  1.06755759e+06,
        1.05502327e+06,  1.03885451e+06,  1.02247003e+06,  1.00227444e+06,
        9.88024873e+05])
cf2
Out[17]: 
array([-1.44852555e+08,  1.41881733e+06,  1.40267049e+06,  1.38296290e+06,
        1.37042160e+06,  1.35430596e+06,  1.33818655e+06,  1.31857419e+06,
        1.30593472e+06,  1.28980433e+06,  1.27367350e+06,  1.25354845e+06,
        1.24144917e+06,  1.22533563e+06,  1.20917048e+06,  1.18927331e+06,
        1.17625690e+06,  1.15946627e+06,  1.14245161e+06,  1.12147927e+06,
        1.10668164e+06])

pyxirr.irr(cf1), pyxirr.irr(cf2)
Out[19]: (-0.13854127397889382, -1.761587679320197)
# comparison with numpy_financial
npf.irr(cf1), npf.irr(cf2)
Out[18]: (-0.13854127397889426, -0.1320937225730502)

XIRR for a pd.Series

Currently XIRR calculates IRR (or YTM) for a single date. But it would be awesome if I could provide it (or a different function) with a pd.Series of a bond's or bill's prices across time to calculate XIRR for every point.

For example if we have:

import pandas as pd

prices = pd.Series({
    '2020-01-01': 100,
    '2020-08-01': 103
})
prices.index = pd.to_datetime(prices.index)

bond_schedule = pd.Series({
    '2020-07-01': 10,
    '2021-01-01': 110
})

then I would need to create a new arguments for dates and amounts for every date/price, i.e. ['2020-01-01', '2020-07-01'] and [-100, 10, 110] , ['2020-08-01', '2021-01-01'] and [-103, 110]. And it could be that I have not 2, but hundreds of dates. As you can see here, as date/prices grow newer, some of the amounts aren't relevant anymore as well. Would be awesome if it's implemented!

Support Series and DataFrame with DatetimeIndex

In example:

s = Series(
    index=date_range("2021", "2022", freq="MS", closed="left"),
    data=[-100] + [20] * 11,
)
xirr(s)  # 5.09623547168478

df = DataFrame(
    index=date_range("2021", "2022", freq="MS", closed="left"),
    data={
        "one": [-100] + [20] * 11,
        "two": [-80] + [19] * 11,
    },
)
xirr(df)  # Series(index=["one", "two"], data=[5.09623547168478, 8.780801977141174])

For the DataFrame I am suggesting returning a Series type simply because that is what i.e. df.sum() would return (applying .sum() to all columns).

Feel free to close if is out of scope for this module! (I would understand that) 😊

Fetching PME index amounts

Thanks for your work on pyXIRR. I was looking for something with XIRR, but it was a nice surprise to find the PME functionality too.

For the index amounts on the PME functions, would it be possible to include the dates of the cash flows and then automatically fetch the index amounts somehow?

Conflicting sub-dependencies with other libraries

Hello. First of all, I´d like to thank you guys for the awesome library !
I am having an issue while trying to use it, I´ve managed to fix it by manually searching for conflicting dependencies inside pipenv, which leads me to believe this issue may be related to undeclared dependencies withing the build-system you guys are using on the package !

Here is the issue I face to run the app after installing pyxirr:

PS C:\codebase\my_path> & C:/Users/myuser/.virtualenvs/my_path-r724wWIu/Scripts/python.exe my_path/main.py
Traceback (most recent call last):
  File "C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\requests_toolbelt\_compat.py", line 48, in <module>
    from requests.packages.urllib3.contrib import appengine as gaecontrib
ImportError: cannot import name 'appengine' from 'requests.packages.urllib3.contrib' (C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\urllib3\contrib\__init__.py)
    from ..util.multipart_stream import MultipartStream
  File "C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\boxsdk\util\multipart_stream.py", line 3, in <module>
    from requests_toolbelt.multipart.encoder import MultipartEncoder
  File "C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\requests_toolbelt\__init__.py", line 12, in <module>
    from .adapters import SSLAdapter, SourceAddressAdapter
  File "C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\requests_toolbelt\adapters\__init__.py", line 12, in <module>
    from .ssl import SSLAdapter
  File "C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\requests_toolbelt\adapters\ssl.py", line 16, in <module>
    from .._compat import poolmanager
  File "C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\requests_toolbelt\_compat.py", line 50, in <module>
    from urllib3.contrib import appengine as gaecontrib
ImportError: cannot import name 'appengine' from 'urllib3.contrib' (C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\urllib3\contrib\__init__.py)

steps to reproduce:

  • with pipenv, try to install pyxirr while having a conflicting package that uses an undeclared dependency. When running the main.py script you will start having these errors due to uncompatible libs (in my case, boxsdk 1.8-2.2)

IRR returns none for this set of values

irr([87.17, 87.17, 87.17, 87.17, 87.17, 87.17, 87.17, 87.17, 87.17, 87.17, 87.17, 87.17, -86.43])

Returns None

numpy_financial returns -0.5020732642263963 for these same values

Similarly if I reverse the signs

irr([-87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, 86.43])

pyxirr's irr returns None

and numpy_financial's IRR (npf.irr) returns -0.5020732642263963 (the same as above)

I'm not 100% sure what the breaking point is, but I have other examples of many more items (up to 180 'deposits') that are returning None from the irr function.

e.g.:

[-87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, -87.17, 5809.3]

Please let me know if I could provide anything else to help troubleshoot!

IRR function args for tolerance and max iterations

Thanks for making this library!

Your irr function is about 15x faster than numpy_financial on my data.

Would it be possible to add arguments for the convergence-tolerance and max number of iterations for the root-finding algorithm? It seems they have recently added these to numpy_financial but it is not yet released.

I would like to try and make irr run even faster by sacrificing some precision of the result, and also to ensure the time-usage is limited and predictable. Because I need to run this on many thousands of arrays of data, as fast as possible, and I only need a few digits of precision.

There also seems to be a problem when the first number of the array is positive but there are negative numbers for some of the future amounts. This example runs 400x slower than normal and does not return a value or raise an exception:

from pyxirr import irr
x = [10.0, 1.0, 2.0, -3.0, 4.0]
irr(x)

I need it to return np.nan when a result could not be found within the max number of iterations allowed and the error is not within the given tolerance level.

By the way, pyxirr.__version__ is apparently also missing.

I hope you can make these small changes.

Thanks!

Weird values for xirr when too close to zero

Hi,

when I run the following example:

dates = [date(2020,1,9),date(2020,2,12),date(2020,3,2),date(2020,3,13),date(2020,5,11),date(2020,5,11),date(2020,5,11),date(2020,5,11),date(2020,11,3),date(2020,12,29),date(2021,3,26),date(2021,7,21),date(2022,6,16),date(2022,7,6)]

cashflow = [-1200,-1050,-400,-800,1500,1100,2000,450,-2000,2850,-1500,2025,-2000,2635]

xirr(dates, cashflow)

I get an xirr of 3.6894338683170713; the same calculation in excel gives me. a value of 0.0000003%. Seems to be an issue with the function for values very close to zero, any suggestions on how to deal with this?

thanks

Dependency Issue

Hello. First of all, I´d like to thank you guys for the awesome library !
I am having an issue while trying to use it, I´ve managed to fix it by manually searching for conflicting dependencies inside pipenv, which leads me to believe this issue may be related to undeclared dependencies withing the build-system you guys are using on the package !

Here is the issue I face to run the app after installing pyxirr:

PS C:\codebase\my_path> & C:/Users/myuser/.virtualenvs/my_path-r724wWIu/Scripts/python.exe my_path/main.py
Traceback (most recent call last):
  File "C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\requests_toolbelt\_compat.py", line 48, in <module>
    from requests.packages.urllib3.contrib import appengine as gaecontrib
ImportError: cannot import name 'appengine' from 'requests.packages.urllib3.contrib' (C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\urllib3\contrib\__init__.py)
    from ..util.multipart_stream import MultipartStream
  File "C:\Users\myuser\.virtualenvsmy_path-r724wWIu\Lib\site-packages\boxsdk\util\multipart_stream.py", line 3, in <module>
    from requests_toolbelt.multipart.encoder import MultipartEncoder
  File "C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\requests_toolbelt\__init__.py", line 12, in <module>
    from .adapters import SSLAdapter, SourceAddressAdapter
  File "C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\requests_toolbelt\adapters\__init__.py", line 12, in <module>
    from .ssl import SSLAdapter
  File "C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\requests_toolbelt\adapters\ssl.py", line 16, in <module>
    from .._compat import poolmanager
  File "C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\requests_toolbelt\_compat.py", line 50, in <module>
    from urllib3.contrib import appengine as gaecontrib
ImportError: cannot import name 'appengine' from 'urllib3.contrib' (C:\Users\myuser\.virtualenvs\my_path-r724wWIu\Lib\site-packages\urllib3\contrib\__init__.py)

steps to reproduce:

  • with pipenv, try to install pyxirr while having a conflicting package that uses an undeclared dependency. When running the main.py script you will start having these errors due to uncompatible libs (in my case, boxsdk 1.8-2.2)

pass start_date (present "moment") and end_date (future "moment") to XFV

on XFV instead of passing:

rate: Rate, # Rate of interest per period
nper: int, # Number of compounding periods

we can pass:

rate: Rate, # the annual interest rate
start_date: DateLike, # the start date (present "moment")
end_date: DateLike, # the end date (future "moment")

Another (less explicit) option instead of passing start_date and end_date is for the function to automatically take them from the series of dates passed (in amounts or dates) (start_date will be the minimum date of this series, end_date will be the maximum date of this series)
In this case if the user will want to have a start_date / end_date that does not happen on the first/last payment, he/she can add these dates to the series with amount=0

Also, we can add clarifications to the docs on when rate is per period (e.g. on FV) and when rate is the annual interest rate (e.g. on XNPV)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.