Pyxirr - Rust-powered collection of financial functions for Python.

Overview

rust-lang.org License pypi versions

PyXIRR

Rust-powered collection of financial functions.

PyXIRR stands for "Python XIRR" (for historical reasons), but contains many other financial functions such as IRR, FV, NPV, etc.

Features:

  • correct
  • blazingly fast
  • works with different input data types (iterators, numpy arrays, pandas DataFrames)
  • no external dependencies

Installation

pip install pyxirr

Benchmarks

Rust implementation has been tested against existing xirr package (uses scipy.optimize under the hood) and the implementation from the Stack Overflow (pure python).

bench

PyXIRR is ~10-20x faster in XIRR calculation than the other implementations.

Powered by github-action-benchmark and plotly.js.

Live benchmarks are hosted on Github Pages.

Examples

from datetime import date
from pyxirr import xirr

dates = [date(2020, 1, 1), date(2021, 1, 1), date(2022, 1, 1)]
amounts = [-1000, 750, 500]

# feed columnar data
xirr(dates, amounts)
# feed iterators
xirr(iter(dates), (x / 2 for x in amounts))
# feed an iterable of tuples
xirr(zip(dates, amounts))
# feed a dictionary
xirr(dict(zip(dates, amounts)))
# dates as strings
xirr(['2020-01-01', '2021-01-01'], [-1000, 1200])

Numpy and Pandas support

import numpy as np
import pandas as pd

# feed numpy array
xirr(np.array([dates, amounts]))
xirr(np.array(dates), np.array(amounts))

# feed DataFrame (columns names doesn't matter; ordering matters)
xirr(pd.DataFrame({"a": dates, "b": amounts}))

# feed Series with DatetimeIndex
xirr(pd.Series(amounts, index=pd.to_datetime(dates)))

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

Other financial functions:

import pyxirr

# Future Value
pyxirr.fv(0.05 / 12, 10 * 12, -100, -100)

# Net Present Value
pyxirr.npv(0, [-40_000, 5_000, 8_000, 12_000, 30_000])

# IRR
pyxirr.irr([-100, 39, 59, 55, 20])

# ... and more! Check out the docs.

API reference

See the docs

Roadmap

  • Implement all functions from numpy-financial
  • Improve docs, add more tests
  • Type hints
  • Vectorized versions of numpy-financial functions.
  • Compile library for rust/javascript/python

Development

Running tests with pyo3 is a bit tricky. In short, you need to compile your tests without extension-module feature to avoid linking errors. See the following issues for the details: #341, #771.

If you are using pyenv, make sure you have the shared library installed (check for ${PYENV_ROOT}/versions/<version>/lib/libpython3.so file).

$ PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install <version>

Install dev-requirements

$ pip install -r dev-requirements.txt

Building

$ maturin develop

Testing

$ LD_LIBRARY_PATH=${PYENV_ROOT}/versions/3.8.6/lib cargo test --no-default-features --features tests

Benchmarks

$ pip install -r bench-requirements.txt
$ LD_LIBRARY_PATH=${PYENV_ROOT}/versions/3.8.6/lib cargo +nightly bench --no-default-features --features tests

Building and distribution

This library uses maturin to build and distribute python wheels.

$ docker run --rm -v $(pwd):/io konstin2/maturin build --release --manylinux 2010 --strip
$ maturin upload target/wheels/pyxirr-${version}*
Comments
  • Can't install from wheels for 3.10 on arm linux (inside M1 host docker)

    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.
    
    opened by rbusquet 4
  • Negative IRR

    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)
    
    opened by aluyca 4
  • Support Series and DataFrame with DatetimeIndex

    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) :blush:

    opened by Peque 3
  • pass start_date (present

    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)

    opened by yonil7 3
  • Publish new version with updated metadata

    Publish new version with updated metadata

    We'd like to upgrade our project to Python 3.11 soon, but the version of pyxirr on PyPI still requires Python <3.11. It looks like the metadata was updated in https://github.com/Anexen/pyxirr/pull/26 to allow 3.11, but a new version was never published (addressed in this comment).

    Unfortunately, Poetry refuses to install an incompatible package, so there is no workaround for us beyond removing the library entirely.

    opened by paulfri 2
  • Payment scenarios where IRR returns None

    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.

    opened by CliveCleaves 2
  • Following input leads to None output

    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))
    
    opened by dterei 2
  • IRR returns none for this set of values

    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!

    opened by CliveCleaves 2
  • surpress error messages when running in groupby or in loop

    surpress error messages when running in groupby or in loop

    add possibility to set false on error messages when running functions in loop or using Pandas groupby.. In case of error - no value returned (empty), but it's not affect further workflow execution

    opened by naurisozols 1
  • added wheel build for linux arm

    added wheel build for linux arm

    There are no wheels when using on an M1-based (arm) mac inside Docker. uname -a returns

    root@6c6e7283e74d:/app# uname -a
    Linux 6c6e7283e74d 5.10.47-linuxkit #1 SMP PREEMPT Sat Jul 3 21:50:16 UTC 2021 aarch64 GNU/Linux
    
    opened by westandskif 0
  • Implement rate

    Implement rate

    Functions for calculating the payment (pmt) and periods (nper) are implemented, but it seems the function for calculating the interest rate is missing:

    • https://numpy.org/doc/1.17/reference/generated/numpy.rate.html#numpy.rate
    opened by Peque 0
  • `ipmt()` and `ppmt()` do not accept an array as `per`

    `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
    
    opened by Peque 3
Releases(v0.7.3)
A Python CLI tool that finds all third-party packages imported into your Python project

python-third-party-imports This is a Python CLI tool built with Rust that finds all third-party packages imported into your Python project. Install Yo

Maksudul Haque 24 Feb 1, 2023
Signed distance functions + Rust (CPU & GPU) = ❤️❤️

sdf-playground Signed distance functions + Rust (CPU & GPU) = ❤️❤️ Platforms: Windows, Mac & Linux. About sdf-playground is a demo showcasing how you

Patryk Wychowaniec 5 Nov 16, 2023
Distributed compute platform implemented in Rust, and powered by Apache Arrow.

Ballista: Distributed Compute Platform Overview Ballista is a distributed compute platform primarily implemented in Rust, powered by Apache Arrow. It

Ballista 2.3k Jan 3, 2023
BudouX-rs is a rust port of BudouX (machine learning powered line break organizer tool).

BudouX-rs BudouX-rs is a rust port of BudouX (machine learning powered line break organizer tool). Note: This project contains the deliverables of the

null 5 Jan 20, 2022
A collection of CC-BY-SA course material to teach the Rust programming language, in different formats, levels, and focus points

A collection of CC-BY-SA course material to teach the Rust programming language, in different formats, levels, and focus points. Contact me for remote and on-site trainings!

Katharina Fey 13 Apr 13, 2023
Rust numeric library with R, MATLAB & Python syntax

Peroxide Rust numeric library contains linear algebra, numerical analysis, statistics and machine learning tools with R, MATLAB, Python like macros. W

Tae Geun Kim 351 Dec 29, 2022
Rust crate to create Anki decks. Based on the python library genanki

genanki-rs: A Rust Crate for Generating Anki Decks With genanki-rs you can easily generate decks for the popular open source flashcard platform Anki.

Yannick Funk 63 Dec 23, 2022
Locality Sensitive Hashing in Rust with Python bindings

lsh-rs (Locality Sensitive Hashing) Locality sensitive hashing can help retrieving Approximate Nearest Neighbors in sub-linear time. For more informat

Ritchie Vink 65 Jan 2, 2023
Python package to compute levensthein distance in rust

Contents Introduction Installation Usage License Introduction Rust implementation of levensthein distance (https://en.wikipedia.org/wiki/Levenshtein_d

Thibault Blanc 2 Feb 21, 2022
Robust and Fast tokenizations alignment library for Rust and Python

Robust and Fast tokenizations alignment library for Rust and Python

Yohei Tamura 14 Dec 10, 2022
A high performance python technical analysis library written in Rust and the Numpy C API.

Panther A efficient, high-performance python technical analysis library written in Rust using PyO3 and rust-numpy. Indicators ATR CMF SMA EMA RSI MACD

Greg 210 Dec 22, 2022
Rust-port of spotify/annoy as a wrapper for Approximate Nearest Neighbors in C++/Python optimized for memory usage.

Rust-port of spotify/annoy as a wrapper for Approximate Nearest Neighbors in C++/Python optimized for memory usage.

Arthur·Thomas 13 Mar 10, 2022
Rust-port of spotify/annoy as a wrapper for Approximate Nearest Neighbors in C++/Python optimized for memory usage.

Fareast This library is a rust port of spotify/annoy , currently only index serving is supported. It also provides FFI bindings for jvm, dotnet and da

Arthur·Thomas 13 Mar 10, 2022
Python+Rust implementation of the Probabilistic Principal Component Analysis model

Probabilistic Principal Component Analysis (PPCA) model This project implements a PPCA model implemented in Rust for Python using pyO3 and maturin. In

FindHotel 11 Dec 16, 2022
Low effort scraping Python's pickle format in Rust. It is to complete pickle parsing as BeautifulSoup was to complete HTML parsing.

repugnant-pickle Because it is, isn't it? This is a Rust crate for dealing with the Python pickle format. It also has support for opening PyTorch file

Kerfuffle 7 Apr 7, 2023
Sample Python extension using Rust/PyO3/tch to interact with PyTorch

Python extensions using tch to interact with PyTorch This sample crate shows how to use tch to write a Python extension that manipulates PyTorch tenso

Laurent Mazare 5 Jun 10, 2023
Rye is Armin's personal one-stop-shop for all his Python needs.

Rye Rye is Armin's personal one-stop-shop for all his Python needs. It installs and manages Python installations, manages pyproject.toml files, instal

Armin Ronacher 2.8k Apr 26, 2023
Practice repo for learning Rust. Currently going through "Rust for JavaScript Developers" course.

rust-practice ?? Practice repo for learning Rust. Directories /rust-for-js-dev Files directed towards "Rust for JavaScript Developers" course. Thank y

Sammy Samkough 0 Dec 25, 2021
A Rust library with homemade machine learning models to classify the MNIST dataset. Built in an attempt to get familiar with advanced Rust concepts.

mnist-classifier Ideas UPDATED: Finish CLI Flags Parallelize conputationally intensive functions Class-based naive bayes README Image parsing Confusio

Neil Kaushikkar 0 Sep 2, 2021