{"id":15020436,"url":"https://github.com/anexen/pyxirr","last_synced_at":"2025-05-15T09:03:55.706Z","repository":{"id":46272939,"uuid":"364376889","full_name":"Anexen/pyxirr","owner":"Anexen","description":"Rust-powered collection of financial functions.","archived":false,"fork":false,"pushed_at":"2025-01-02T18:16:10.000Z","size":864,"stargazers_count":188,"open_issues_count":4,"forks_count":19,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-04-07T03:15:54.102Z","etag":null,"topics":["day-count","fast","financial-analysis","financial-functions","isda","maturin","numpy","pme","private-equity","pyo3","python","python-extension","rust","xirr","xirr-calculation","xnpv"],"latest_commit_sha":null,"homepage":"https://anexen.github.io/pyxirr/","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Anexen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"ko_fi":"anexen"}},"created_at":"2021-05-04T20:23:25.000Z","updated_at":"2025-03-07T08:57:28.000Z","dependencies_parsed_at":"2023-12-01T23:22:21.008Z","dependency_job_id":"70083bc2-29ae-4b1f-9ed3-a6971195ebde","html_url":"https://github.com/Anexen/pyxirr","commit_stats":{"total_commits":201,"total_committers":7,"mean_commits":"28.714285714285715","dds":"0.24875621890547261","last_synced_commit":"f8d8050d4213d339640fa9af69bbc6cd121932e2"},"previous_names":[],"tags_count":31,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Anexen%2Fpyxirr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Anexen%2Fpyxirr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Anexen%2Fpyxirr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Anexen%2Fpyxirr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Anexen","download_url":"https://codeload.github.com/Anexen/pyxirr/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248890619,"owners_count":21178472,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["day-count","fast","financial-analysis","financial-functions","isda","maturin","numpy","pme","private-equity","pyo3","python","python-extension","rust","xirr","xirr-calculation","xnpv"],"created_at":"2024-09-24T19:55:05.303Z","updated_at":"2025-04-14T13:46:43.105Z","avatar_url":"https://github.com/Anexen.png","language":"Rust","funding_links":["https://ko-fi.com/anexen"],"categories":[],"sub_categories":[],"readme":"[![rust-lang.org](https://img.shields.io/badge/Made%20with-Rust-red)](https://www.rust-lang.org/)\n[![License](https://img.shields.io/github/license/Anexen/pyxirr.svg)](https://github.com/Anexen/pyxirr/blob/master/LICENSE)\n[![pypi](https://img.shields.io/pypi/v/pyxirr.svg)](https://pypi.org/project/pyxirr/)\n[![versions](https://img.shields.io/pypi/pyversions/pyxirr.svg)](https://pypi.org/project/pyxirr/)\n\n# PyXIRR\n\nRust-powered collection of financial functions.\n\nPyXIRR stands for \"Python XIRR\" (for historical reasons), but contains many other financial functions such as IRR, FV, NPV, etc.\n\nFeatures:\n\n- correct\n- supports different day count conventions (e.g. ACT/360, 30E/360, etc.)\n- works with different input data types (iterators, numpy arrays, pandas DataFrames)\n- no external dependencies\n- type annotations\n- blazingly fast\n\n# Installation\n\n```\npip install pyxirr\n```\n\n\u003e WASM wheels for [pyodide](https://github.com/pyodide/pyodide) are also available,\n\u003e but unfortunately are [not supported by PyPI](https://github.com/pypi/warehouse/issues/10416).\n\u003e You can find them on the [GitHub Releases](https://github.com/Anexen/pyxirr/releases) page.\n\n# Benchmarks\n\nRust implementation has been tested against existing [xirr](https://pypi.org/project/xirr/) package\n(uses [scipy.optimize](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.newton.html) under the hood)\nand the [implementation from the Stack Overflow](https://stackoverflow.com/a/11503492) (pure python).\n\n![bench](https://raw.githubusercontent.com/Anexen/pyxirr/main/docs/static/bench.png)\n\nPyXIRR is much faster than the other implementations.\n\nPowered by [github-action-benchmark](https://github.com/benchmark-action/github-action-benchmark) and [plotly.js](https://github.com/plotly/plotly.js).\n\nLive benchmarks are hosted on [Github Pages](https://anexen.github.io/pyxirr/bench).\n\n# Example\n\n```python\nfrom datetime import date\nfrom pyxirr import xirr\n\ndates = [date(2020, 1, 1), date(2021, 1, 1), date(2022, 1, 1)]\namounts = [-1000, 750, 500]\n\n# feed columnar data\nxirr(dates, amounts)\n# feed iterators\nxirr(iter(dates), (x / 2 for x in amounts))\n# feed an iterable of tuples\nxirr(zip(dates, amounts))\n# feed a dictionary\nxirr(dict(zip(dates, amounts)))\n# dates as strings\nxirr(['2020-01-01', '2021-01-01'], [-1000, 1200])\n```\n\n# Multiple IRR problem\n\nThe Multiple IRR problem occurs when the signs of cash flows change more than\nonce. In this case, we say that the project has non-conventional cash flows.\nThis leads to situation, where it can have more the one IRR or have no IRR at all.\n\nPyXIRR addresses the Multiple IRR problem as follows:\n\n1. It looks for positive result around 0.1 (the same as Excel with the default guess=0.1).\n2. If it can't find a result, it uses several other attempts and selects the lowest IRR to be conservative.\n\nHere is an example illustrating how to identify multiple IRRs:\n\n```python\nimport numpy as np\nimport pyxirr\n\n# load cash flow:\ncf = pd.read_csv(\"tests/samples/30-22.csv\", names=[\"date\", \"amount\"])\n# check whether the cash flow is conventional:\nprint(pyxirr.is_conventional_cash_flow(cf[\"amount\"]))  # false\n\n# build NPV profile:\n# calculate 50 NPV values for different rates\nrates = np.linspace(-0.5, 0.5, 50)\n# any iterable, any rates, e.g.\n# rates = [-0.5, -0.3, -0.1, 0.1, -0.6]\nvalues = pyxirr.xnpv(rates, cf)\n\n# print NPV profile:\n# NPV changes sign two times:\n#   1) between -0.316 and -0.295\n#   2) between -0.03 and -0.01\nprint(\"NPV profile:\")\nfor rate, value in zip(rates, values):\n    print(rate, value)\n\n# plot NPV profile\nimport pandas as pd\nseries = pd.Series(values, index=rates)\npd.DataFrame(series[series \u003e -1e6]).assign(zero=0).plot()\n\n# find points where NPV function crosses zero\nindexes = pyxirr.zero_crossing_points(values)\n\nprint(\"Zero crossing points:\")\nfor idx in indexes:\n    print(\"between\", rates[idx], \"and\", rates[idx+1])\n\n# XIRR has two results:\n#   -0.31540826742734207\n#   -0.028668460065441048\nfor i, idx in enumerate(indexes, start=1):\n    rate = pyxirr.xirr(cf, guess=rates[idx])\n    npv = pyxirr.xnpv(rate, cf)\n    print(f\"{i}) {rate}; XNPV = {npv}\")\n```\n\n# More Examples\n\n### Numpy and Pandas\n\n```python\nimport numpy as np\nimport pandas as pd\n\n# feed numpy array\nxirr(np.array([dates, amounts]))\nxirr(np.array(dates), np.array(amounts))\n\n# feed DataFrame (columns names doesn't matter; ordering matters)\nxirr(pd.DataFrame({\"a\": dates, \"b\": amounts}))\n\n# feed Series with DatetimeIndex\nxirr(pd.Series(amounts, index=pd.to_datetime(dates)))\n\n# bonus: apply xirr to a DataFrame with DatetimeIndex:\ndf = pd.DataFrame(\n    index=pd.date_range(\"2021\", \"2022\", freq=\"MS\", inclusive=\"left\"),\n    data={\n        \"one\": [-100] + [20] * 11,\n        \"two\": [-80] + [19] * 11,\n    },\n)\ndf.apply(xirr)  # Series(index=[\"one\", \"two\"], data=[5.09623547168478, 8.780801977141174])\n```\n\n### Day count conventions\n\nCheck out the available options on the [docs/day-count-conventions](https://anexen.github.io/pyxirr/functions.html#day-count-conventions).\n\n```python\nfrom pyxirr import DayCount\n\nxirr(dates, amounts, day_count=DayCount.ACT_360)\n\n# parse day count from string\nxirr(dates, amounts, day_count=\"30E/360\")\n```\n\n### Private equity performance metrics\n\n```python\nfrom pyxirr import pe\n\npe.pme_plus([-20, 15, 0], index=[100, 115, 130], nav=20)\n\npe.direct_alpha([-20, 15, 0], index=[100, 115, 130], nav=20)\n```\n\n[Docs](https://anexen.github.io/pyxirr/private_equity.html)\n\n### Other financial functions\n\n```python\nimport pyxirr\n\n# Future Value\npyxirr.fv(0.05/12, 10*12, -100, -100)\n\n# Net Present Value\npyxirr.npv(0, [-40_000, 5_000, 8_000, 12_000, 30_000])\n\n# IRR\npyxirr.irr([-100, 39, 59, 55, 20])\n\n# ... and more! Check out the docs.\n```\n\n[Docs](https://anexen.github.io/pyxirr/functions.html)\n\n### Vectorization\n\nPyXIRR supports numpy-like vectorization.\n\nIf all input is scalar, returns a scalar float. If any input is array_like,\nreturns values for each input element. If multiple inputs are\narray_like, performs broadcasting and returns values for each element.\n\n```python\nimport pyxirr\n\n# feed list\npyxirr.fv([0.05/12, 0.06/12], 10*12, -100, -100)\npyxirr.fv([0.05/12, 0.06/12], [10*12, 9*12], [-100, -200], -100)\n\n# feed numpy array\nimport numpy as np\nrates = np.array([0.05, 0.06, 0.07])/12\npyxirr.fv(rates, 10*12, -100, -100)\n\n# feed any iterable!\npyxirr.fv(\n    np.linspace(0.01, 0.2, 10),\n    (x + 1 for x in range(10)),\n    range(-100, -1100, -100),\n    tuple(range(-100, -200, -10))\n)\n\n# 2d, 3d, 4d, and more!\nrates = [[[[[[0.01], [0.02]]]]]]\npyxirr.fv(rates, 10*12, -100, -100)\n```\n\n# API reference\n\nSee the [docs](https://anexen.github.io/pyxirr)\n\n# Roadmap\n\n- [x] Implement all functions from [numpy-financial](https://numpy.org/numpy-financial/latest/index.html)\n- [x] Improve docs, add more tests\n- [x] Type hints\n- [x] Vectorized versions of numpy-financial functions.\n- [ ] Compile library for rust/javascript/python\n\n# Development\n\nRunning tests with pyo3 is a bit tricky. In short, you need to compile your tests without `extension-module` feature to avoid linking errors.\nSee the following issues for the details: [#341](https://github.com/PyO3/pyo3/issues/341), [#771](https://github.com/PyO3/pyo3/issues/771).\n\nIf you are using `pyenv`, make sure you have the shared library installed (check for `${PYENV_ROOT}/versions/\u003cversion\u003e/lib/libpython3.so` file).\n\n```bash\n$ PYTHON_CONFIGURE_OPTS=\"--enable-shared\" pyenv install \u003cversion\u003e\n```\n\nInstall dev-requirements\n\n```bash\n$ pip install -r dev-requirements.txt\n```\n\n### Building\n\n```bash\n$ maturin develop\n```\n\n### Testing\n\n```bash\n$ LD_LIBRARY_PATH=${PYENV_ROOT}/versions/3.10.8/lib cargo test\n```\n\n### Benchmarks\n\n```bash\n$ pip install -r bench-requirements.txt\n$ LD_LIBRARY_PATH=${PYENV_ROOT}/versions/3.10.8/lib cargo +nightly bench\n```\n\n# Building and distribution\n\nThis library uses [maturin](https://github.com/PyO3/maturin) to build and distribute python wheels.\n\n```bash\n$ docker run --rm -v $(pwd):/io ghcr.io/pyo3/maturin build --release --manylinux 2010 --strip\n$ maturin upload target/wheels/pyxirr-${version}*\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanexen%2Fpyxirr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanexen%2Fpyxirr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanexen%2Fpyxirr/lists"}