Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/Anexen/pyxirr
Rust-powered collection of financial functions.
https://github.com/Anexen/pyxirr
day-count fast financial-analysis financial-functions isda maturin numpy pme private-equity pyo3 python python-extension rust xirr xirr-calculation xnpv
Last synced: about 2 months ago
JSON representation
Rust-powered collection of financial functions.
- Host: GitHub
- URL: https://github.com/Anexen/pyxirr
- Owner: Anexen
- License: unlicense
- Created: 2021-05-04T20:23:25.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-10-28T16:16:33.000Z (about 2 months ago)
- Last Synced: 2024-10-28T18:21:57.689Z (about 2 months ago)
- Topics: day-count, fast, financial-analysis, financial-functions, isda, maturin, numpy, pme, private-equity, pyo3, python, python-extension, rust, xirr, xirr-calculation, xnpv
- Language: Rust
- Homepage: https://anexen.github.io/pyxirr/
- Size: 842 KB
- Stars: 171
- Watchers: 8
- Forks: 16
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
[![rust-lang.org](https://img.shields.io/badge/Made%20with-Rust-red)](https://www.rust-lang.org/)
[![License](https://img.shields.io/github/license/Anexen/pyxirr.svg)](https://github.com/Anexen/pyxirr/blob/master/LICENSE)
[![pypi](https://img.shields.io/pypi/v/pyxirr.svg)](https://pypi.org/project/pyxirr/)
[![versions](https://img.shields.io/pypi/pyversions/pyxirr.svg)](https://pypi.org/project/pyxirr/)# 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
- supports different day count conventions (e.g. ACT/360, 30E/360, etc.)
- works with different input data types (iterators, numpy arrays, pandas DataFrames)
- no external dependencies
- type annotations
- blazingly fast# Installation
```
pip install pyxirr
```> WASM wheels for [pyodide](https://github.com/pyodide/pyodide) are also available,
> but unfortunately are [not supported by PyPI](https://github.com/pypi/warehouse/issues/10416).
> You can find them on the [GitHub Releases](https://github.com/Anexen/pyxirr/releases) page.# Benchmarks
Rust implementation has been tested against existing [xirr](https://pypi.org/project/xirr/) package
(uses [scipy.optimize](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.newton.html) under the hood)
and the [implementation from the Stack Overflow](https://stackoverflow.com/a/11503492) (pure python).![bench](https://raw.githubusercontent.com/Anexen/pyxirr/main/docs/static/bench.png)
PyXIRR is much faster than the other implementations.
Powered by [github-action-benchmark](https://github.com/benchmark-action/github-action-benchmark) and [plotly.js](https://github.com/plotly/plotly.js).
Live benchmarks are hosted on [Github Pages](https://anexen.github.io/pyxirr/bench).
# Example
```python
from datetime import date
from pyxirr import xirrdates = [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])
```# Multiple IRR problem
The Multiple IRR problem occurs when the signs of cash flows change more than
once. In this case, we say that the project has non-conventional cash flows.
This leads to situation, where it can have more the one IRR or have no IRR at all.PyXIRR addresses the Multiple IRR problem as follows:
1. It looks for positive result around 0.1 (the same as Excel with the default guess=0.1).
2. If it can't find a result, it uses several other attempts and selects the lowest IRR to be conservative.Here is an example illustrating how to identify multiple IRRs:
```python
import numpy as np
import pyxirr# load cash flow:
cf = pd.read_csv("tests/samples/30-22.csv", names=["date", "amount"])
# check whether the cash flow is conventional:
print(pyxirr.is_conventional_cash_flow(cf["amount"])) # false# build NPV profile:
# calculate 50 NPV values for different rates
rates = np.linspace(-0.5, 0.5, 50)
# any iterable, any rates, e.g.
# rates = [-0.5, -0.3, -0.1, 0.1, -0.6]
values = pyxirr.xnpv(rates, cf)# print NPV profile:
# NPV changes sign two times:
# 1) between -0.316 and -0.295
# 2) between -0.03 and -0.01
print("NPV profile:")
for rate, value in zip(rates, values):
print(rate, value)# plot NPV profile
import pandas as pd
series = pd.Series(values, index=rates)
pd.DataFrame(series[series > -1e6]).assign(zero=0).plot()# find points where NPV function crosses zero
indexes = pyxirr.zero_crossing_points(values)print("Zero crossing points:")
for idx in indexes:
print("between", rates[idx], "and", rates[idx+1])# XIRR has two results:
# -0.31540826742734207
# -0.028668460065441048
for i, idx in enumerate(indexes, start=1):
rate = pyxirr.xirr(cf, guess=rates[idx])
npv = pyxirr.xnpv(rate, cf)
print(f"{i}) {rate}; XNPV = {npv}")
```# More Examples
### Numpy and Pandas
```python
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", inclusive="left"),
data={
"one": [-100] + [20] * 11,
"two": [-80] + [19] * 11,
},
)
df.apply(xirr) # Series(index=["one", "two"], data=[5.09623547168478, 8.780801977141174])
```### Day count conventions
Check out the available options on the [docs/day-count-conventions](https://anexen.github.io/pyxirr/functions.html#day-count-conventions).
```python
from pyxirr import DayCountxirr(dates, amounts, day_count=DayCount.ACT_360)
# parse day count from string
xirr(dates, amounts, day_count="30E/360")
```### Private equity performance metrics
```python
from pyxirr import pepe.pme_plus([-20, 15, 0], index=[100, 115, 130], nav=20)
pe.direct_alpha([-20, 15, 0], index=[100, 115, 130], nav=20)
```[Docs](https://anexen.github.io/pyxirr/private_equity.html)
### Other financial functions
```python
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.
```[Docs](https://anexen.github.io/pyxirr/functions.html)
### Vectorization
PyXIRR supports numpy-like vectorization.
If all input is scalar, returns a scalar float. If any input is array_like,
returns values for each input element. If multiple inputs are
array_like, performs broadcasting and returns values for each element.```python
import pyxirr# feed list
pyxirr.fv([0.05/12, 0.06/12], 10*12, -100, -100)
pyxirr.fv([0.05/12, 0.06/12], [10*12, 9*12], [-100, -200], -100)# feed numpy array
import numpy as np
rates = np.array([0.05, 0.06, 0.07])/12
pyxirr.fv(rates, 10*12, -100, -100)# feed any iterable!
pyxirr.fv(
np.linspace(0.01, 0.2, 10),
(x + 1 for x in range(10)),
range(-100, -1100, -100),
tuple(range(-100, -200, -10))
)# 2d, 3d, 4d, and more!
rates = [[[[[[0.01], [0.02]]]]]]
pyxirr.fv(rates, 10*12, -100, -100)
```# API reference
See the [docs](https://anexen.github.io/pyxirr)
# Roadmap
- [x] Implement all functions from [numpy-financial](https://numpy.org/numpy-financial/latest/index.html)
- [x] Improve docs, add more tests
- [x] Type hints
- [x] 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](https://github.com/PyO3/pyo3/issues/341), [#771](https://github.com/PyO3/pyo3/issues/771).If you are using `pyenv`, make sure you have the shared library installed (check for `${PYENV_ROOT}/versions//lib/libpython3.so` file).
```bash
$ PYTHON_CONFIGURE_OPTS="--enable-shared" pyenv install
```Install dev-requirements
```bash
$ pip install -r dev-requirements.txt
```### Building
```bash
$ maturin develop
```### Testing
```bash
$ LD_LIBRARY_PATH=${PYENV_ROOT}/versions/3.10.8/lib cargo test
```### Benchmarks
```bash
$ pip install -r bench-requirements.txt
$ LD_LIBRARY_PATH=${PYENV_ROOT}/versions/3.10.8/lib cargo +nightly bench
```# Building and distribution
This library uses [maturin](https://github.com/PyO3/maturin) to build and distribute python wheels.
```bash
$ docker run --rm -v $(pwd):/io ghcr.io/pyo3/maturin build --release --manylinux 2010 --strip
$ maturin upload target/wheels/pyxirr-${version}*
```