Finance.CashFlow (finance v1.4.2)

Copy Markdown View Source

Discounting a series of cash flows: net present value (npv/2, xnpv/2) and internal rate of return (irr/1, xirr/2, mirr/3).

The dated functions (xirr, xnpv) take {date, amount} flows at arbitrary dates and discount on an Actual/365 basis, matching the spreadsheet XIRR/XNPV. The periodic functions (irr, npv) take a plain list of amounts at equally spaced periods 0, 1, 2, ….

xirr/irr find the rate r that brings the net present value to zero, Σ cf_i / (1 + r)^t_i = 0, using Finance.Solver (Newton-Raphson with a bisection fallback by default).

Options

The rate-finding and value functions take an optional keyword list. Options are validated with nimble_options: an unknown key or bad value raises, while problems with the data come back as {:error, reason}.

  • :guess (float/0) - initial rate for the Newton-Raphson solver The default value is 0.1.

  • :tolerance (float/0) - convergence threshold on the net present value The default value is 1.0e-9.

  • :max_iterations (pos_integer/0) - cap on solver iterations before giving up The default value is 100.

  • :precision (non_neg_integer/0) - decimal places the result is rounded to The default value is 6.

  • :solver (atom/0) - module implementing the Finance.Solver behaviour (defaults to Finance.Solver.Newton)

Summary

Functions

Computes the IRR — the internal rate of return for a list of amounts that occur at equally spaced periods 0, 1, 2, …. Think of it as xirr/2 for the common case where your flows land at regular intervals and you don't need to track exact dates.

Same as irr/1, and additionally takes the same options as xirr/2.

Same as irr/1, but returns the rate directly and raises ArgumentError on error.

Computes the MIRR — the modified internal rate of return for periodic amounts. It refines the idea behind IRR by letting you set two separate rates: positive flows are assumed to be reinvested at reinvest_rate, and negative flows are assumed to be financed at finance_rate.

Same as mirr/3, but returns the rate directly and raises ArgumentError on error.

Computes the periodic NPV — the net present value of amounts occurring at equally spaced periods 0, 1, 2, …, discounted at rate.

Same as npv/2, and additionally takes a :precision option. See npv/2.

Same as npv/2, but returns the value directly and raises ArgumentError on error.

Finds the XIRR of a list of {date, amount} cash flows — the annual rate of return that the flows imply, given when each one lands.

Finds the XIRR, accepting either {date, amount} pairs together with options, or two parallel lists — one of dates, one of amounts.

Finds the XIRR from two parallel lists — dates and amounts — while also taking options for the solver.

Same as xirr/1, but returns the rate directly and raises ArgumentError on error.

Same as xirr/2, but returns the rate directly and raises ArgumentError on error.

Computes the XNPV — the net present value of a set of dated cash flows, discounted back at rate.

Same as xnpv/2, and additionally takes a :precision option. See xnpv/2.

Same as xnpv/2, but returns the value directly and raises ArgumentError on error.

Types

amount()

@type amount() :: Finance.amount()

cash_flow()

@type cash_flow() :: Finance.cash_flow()

date()

@type date() :: Finance.date()

error()

@type error() :: Finance.error()

option()

@type option() :: Finance.option()

rate()

@type rate() :: Finance.rate()

Functions

irr(amounts)

@spec irr([amount()]) :: {:ok, rate()} | {:error, error()}

Computes the IRR — the internal rate of return for a list of amounts that occur at equally spaced periods 0, 1, 2, …. Think of it as xirr/2 for the common case where your flows land at regular intervals and you don't need to track exact dates.

What comes back is the rate per period. As with xirr/2, the series has to contain at least one positive amount and one negative one.

iex> Finance.CashFlow.irr([-1000, 1100])
{:ok, 0.1}

iex> Finance.CashFlow.irr([-1000, 500, 500, 300])
{:ok, 0.156579}

irr(amounts, opts)

@spec irr([amount()], [option()]) :: {:ok, rate()} | {:error, error()}

Same as irr/1, and additionally takes the same options as xirr/2.

irr!(amounts, opts \\ [])

@spec irr!([amount()], [option()]) :: rate()

Same as irr/1, but returns the rate directly and raises ArgumentError on error.

mirr(amounts, finance_rate, reinvest_rate, opts \\ [])

@spec mirr([amount()], number(), number(), [option()]) ::
  {:ok, rate()} | {:error, error()}

Computes the MIRR — the modified internal rate of return for periodic amounts. It refines the idea behind IRR by letting you set two separate rates: positive flows are assumed to be reinvested at reinvest_rate, and negative flows are assumed to be financed at finance_rate.

Because those assumptions are spelled out, MIRR has a closed form and a single answer, which sidesteps the multiple-root and convergence trouble that IRR can run into. As with irr/1, the series has to contain at least one positive amount and one negative one.

iex> Finance.CashFlow.mirr([-120_000, 39_000, 30_000, 21_000, 37_000, 46_000], 0.10, 0.12)
{:ok, 0.126094}

mirr!(amounts, finance_rate, reinvest_rate, opts \\ [])

@spec mirr!([amount()], number(), number(), [option()]) :: rate()

Same as mirr/3, but returns the rate directly and raises ArgumentError on error.

npv(rate, amounts)

@spec npv(rate(), [amount()]) :: {:ok, number()} | {:error, error()}

Computes the periodic NPV — the net present value of amounts occurring at equally spaced periods 0, 1, 2, …, discounted at rate.

Σ amount_i / (1 + rate)^i    (i starting at 0)

Convention

The first amount sits at period 0 and so is left undiscounted. That is what lets npv/2 and irr/1 line up: npv(irr(a), a) comes out to roughly zero. It also means the result differs from a spreadsheet NPV, which places the first amount at period 1. If you want to match a spreadsheet, discount the first amount yourself or prepend a leading 0 to the list.

iex> Finance.CashFlow.npv(0.1, [-1000, 1100])
{:ok, 0.0}

iex> Finance.CashFlow.npv(0.1, [-1000, 600, 600])
{:ok, 41.322314}

npv(rate, amounts, opts)

@spec npv(rate(), [amount()], [option()]) :: {:ok, number()} | {:error, error()}

Same as npv/2, and additionally takes a :precision option. See npv/2.

npv!(rate, amounts)

@spec npv!(rate(), [amount()]) :: number()

Same as npv/2, but returns the value directly and raises ArgumentError on error.

xirr(cash_flows)

@spec xirr([cash_flow()]) :: {:ok, rate()} | {:error, error()}

Finds the XIRR of a list of {date, amount} cash flows — the annual rate of return that the flows imply, given when each one lands.

Reach for this when your cash flows happen on irregular dates rather than at neat intervals. See xirr/2 if you want to pass options or use the two-list form.

iex> Finance.CashFlow.xirr([{~D[2015-06-01], 1_000_000}, {~D[2015-10-01], -2_200_000}, {~D[2015-11-01], -800_000}])
{:ok, 21.118359}

xirr(first, second)

@spec xirr([cash_flow()], [option()]) :: {:ok, rate()} | {:error, error()}
@spec xirr([date()], [amount()]) :: {:ok, rate()} | {:error, error()}

Finds the XIRR, accepting either {date, amount} pairs together with options, or two parallel lists — one of dates, one of amounts.

The result is {:ok, rate} on success or {:error, reason} when the data can't yield a rate. Flows that fall on the same date are added together first. The series has to include at least one positive amount and one negative one, because without money flowing both in and out there is no return to solve for.

iex> Finance.CashFlow.xirr([{~D[2019-01-01], -1000}, {~D[2020-01-01], 1100}], guess: 0.5)
{:ok, 0.1}

iex> Finance.CashFlow.xirr([~D[2019-01-01], ~D[2020-01-01]], [-1000, 1100])
{:ok, 0.1}

xirr(dates, values, opts)

@spec xirr([date()], [amount()], [option()]) :: {:ok, rate()} | {:error, error()}

Finds the XIRR from two parallel lists — dates and amounts — while also taking options for the solver.

iex> Finance.CashFlow.xirr([~D[2019-01-01], ~D[2020-01-01]], [-1000, 1100], precision: 2)
{:ok, 0.1}

xirr!(cash_flows)

@spec xirr!([cash_flow()]) :: rate()

Same as xirr/1, but returns the rate directly and raises ArgumentError on error.

xirr!(first, second)

@spec xirr!([cash_flow()] | [date()], [option()] | [amount()]) :: rate()

Same as xirr/2, but returns the rate directly and raises ArgumentError on error.

xnpv(rate, cash_flows)

@spec xnpv(rate(), [cash_flow()]) :: {:ok, number()} | {:error, error()}

Computes the XNPV — the net present value of a set of dated cash flows, discounted back at rate.

Σ cf_i / (1 + rate)^t_i

Each time t_i is measured in years from the earliest flow on an Actual/365 basis, the same day-count convention xirr/2 uses. That shared convention is what ties the two together: xnpv(r, flows) comes out to roughly zero when r is xirr(flows), so this is a handy way to check an XIRR result. And because a net present value is defined for any series, the flows here don't have to change sign the way xirr/2 requires.

The result is {:ok, value} or {:error, reason}. Flows on the same date are added together first, and you can pass the same :precision option as xirr/2 (it defaults to 6).

iex> Finance.CashFlow.xnpv(0.1, [{~D[2019-01-01], -1000}, {~D[2020-01-01], 1000}])
{:ok, -90.909091}

iex> Finance.CashFlow.xnpv(0.1, [{~D[2019-01-01], -1000}, {~D[2020-01-01], 1100}])
{:ok, 0.0}

xnpv(rate, cash_flows, opts)

@spec xnpv(rate(), [cash_flow()], [option()]) :: {:ok, number()} | {:error, error()}

Same as xnpv/2, and additionally takes a :precision option. See xnpv/2.

xnpv!(rate, cash_flows)

@spec xnpv!(rate(), [cash_flow()]) :: number()

Same as xnpv/2, but returns the value directly and raises ArgumentError on error.