# Finance
[](https://github.com/tubedude/finance-elixir/actions/workflows/ci.yml)
An Elixir library for cash-flow analysis. It covers internal rate of return
(`xirr`/`irr`), net present value (`xnpv`/`npv`), and modified IRR (`mirr`),
along with the usual time-value-of-money and depreciation helpers. Options are
validated with `nimble_options`, and amounts may be `Decimal` values when you
have that optional dependency installed.
The rate and value functions come in two forms. The **dated** ones (`xirr`,
`xnpv`) work with flows that land on arbitrary dates, discounting on an
Actual/365 basis to match spreadsheet `XIRR`/`XNPV`. The **periodic** ones
(`irr`, `npv`, `mirr`) take a plain list of amounts spread over equally spaced
periods, for when the exact dates don't matter.
## Installation
Add `finance` to your dependencies in `mix.exs`:
```elixir
def deps do
[{:finance, "~> 1.0"}]
end
```
If you also want to pass `Decimal` amounts, add `{:decimal, "~> 3.0"}` alongside
it.
## Usage
Pass a list of `{date, amount}` cash flows. Money coming in is positive and money
going out is negative, and the series needs at least one of each — without flows
in both directions there is no rate to solve for.
```elixir
Finance.xirr([
{~D[2015-06-01], 1_000_000},
{~D[2015-10-01], -2_200_000},
{~D[2015-11-01], -800_000}
])
#=> {:ok, 21.118359}
```
Dates can also be `{year, month, day}` tuples, and if it reads better you can
supply two parallel lists instead of pairs:
```elixir
Finance.xirr([{2019, 1, 1}, {2020, 1, 1}], [-1000, 1100])
#=> {:ok, 0.1}
```
If you would rather work with the rate directly than unwrap an `:ok` tuple,
`xirr!/1` and `xirr!/2` return it on its own and raise on error.
### Periodic functions
For flows at equally spaced periods `0, 1, 2, …`, pass a plain list of amounts:
```elixir
Finance.irr([-1000, 500, 500, 300]) #=> {:ok, 0.156579}
Finance.npv(0.1, [-1000, 600, 600]) #=> {:ok, 41.322314}
Finance.mirr([-120_000, 39_000, 30_000, 21_000, 37_000, 46_000], 0.10, 0.12)
#=> {:ok, 0.126094}
```
One thing to watch: `npv/2` places the first amount at period 0, which is what
makes `npv(irr(a), a) ≈ 0` hold. A spreadsheet `NPV` instead places the first
amount at period 1, so the two won't agree unless you account for that.
### Amounts and Decimal
Amounts may be any number — integer minor units such as cents, or floats. If your
app already depends on [`Decimal`](https://hex.pm/packages/decimal), you can pass
`Decimal` values straight through, with no conversion on your side:
```elixir
Finance.xirr([{~D[2019-01-01], Decimal.new("-1000")}, {~D[2020-01-01], Decimal.new("1100")}])
#=> {:ok, 0.1}
```
`Decimal` is an optional dependency, so apps that don't use it pull in nothing
extra. Either way the result comes back as a float: XIRR's math is inherently
irrational, so accepting `Decimal` is about convenience at the call site, not
added precision in the answer.
### Errors
When the data can't produce a result, `xirr/1` and `xirr/2` return
`{:error, reason}`, where `reason` is one of:
| Reason | Meaning |
| ---------------------- | ----------------------------------------------- |
| `:mismatched_lengths` | date and amount lists differ in length |
| `:insufficient_data` | fewer than two distinct-date flows |
| `:single_signed_flow` | all amounts have the same sign |
| `:invalid_date` | a date could not be parsed |
| `:did_not_converge` | no rate found within the iteration limit |
## Development
```bash
mix deps.get
mix test
mix format
mix credo --strict
mix dialyzer
```
See [CHANGELOG.md](CHANGELOG.md) for the 1.0 rewrite notes.