Packages
ex_money
5.2.1
6.1.1
6.1.0
6.0.0
6.0.0-rc.0
5.24.2
5.24.1
5.24.0
retired
5.23.0
5.22.0
5.21.0
5.20.0
5.19.2
5.19.1
5.19.0
5.18.0
5.17.2
5.17.1
5.17.0
5.16.1
5.16.0
5.15.4
5.15.3
5.15.2
5.15.1
5.15.0
5.14.1
5.13.0
5.12.4
5.12.3
5.12.2
5.12.1
5.12.0
5.11.0
5.10.0
5.9.0
5.8.0
5.7.4
5.7.3
5.7.2
5.7.1
5.7.0
5.6.0
5.5.5
5.5.4
5.5.3
5.5.2
5.5.1
5.5.0
5.4.1
5.4.0
5.4.0-rc.0
5.3.2
5.3.1
5.3.0
5.2.1
5.2.0
5.1.0
5.0.2
5.0.1
5.0.0
retired
4.4.3
4.4.2
retired
4.4.1
4.4.0
4.3.0
4.2.2
4.2.1
4.2.0
4.1.0
4.0.0
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.1
3.3.0
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.0
3.0.0
2.12.2
2.12.1
retired
2.12.0
retired
2.11.0
2.10.0
2.9.1
2.9.0
2.8.0
2.7.3
2.7.2
2.7.1
2.7.0
2.6.1
2.6.0
2.5.0
2.4.0
2.3.0
2.2.2
2.2.1
2.2.0
2.1.0
2.1.0-rc.1
retired
2.1.0-rc.0
retired
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.1.3
1.1.2
1.1.1
retired
1.1.0
1.0.0
1.0.0-rc.0
retired
0.9.0
retired
0.8.5
retired
0.8.4
retired
0.8.3
retired
0.8.2
retired
0.8.1
retired
0.8.0
retired
0.7.0
retired
0.6.2
retired
0.6.1
retired
0.6.0
retired
0.5.3
retired
0.5.2
retired
0.5.1
retired
0.5.0
retired
0.4.3
retired
0.4.2
retired
0.4.1
retired
0.4.0
retired
0.3.0
retired
0.2.0
retired
0.1.7
retired
0.1.6
retired
0.1.5
retired
0.1.4
retired
0.1.3
retired
0.1.2
retired
0.1.1
retired
0.1.0
retired
0.0.16
retired
0.0.15
retired
0.0.14
retired
0.0.13
retired
0.0.12
retired
0.0.11
retired
0.0.10
retired
0.0.9
retired
0.0.7
retired
0.0.6
retired
0.0.5
retired
0.0.4
retired
0.0.3
retired
0.0.2
retired
Money functions for operations on and localization of a money data type with support for ISO 4217 currencies and ISO 24165 digial tokens (crypto currencies).
Current section
Files
Jump to
Current section
Files
lib/money/financial.ex
defmodule Money.Financial do
@moduledoc """
A set of financial functions, primarily related to discounted cash flows.
Some of the algorithms are from [finance formulas](http://www.financeformulas.net)
"""
alias Cldr.Math
@doc """
Calculates the future value for a present value, an interest rate
and a number of periods.
* `present_value` is a %Money{} representation of the present value
* `interest_rate` is a float representation of an interest rate. For
example, 12% would be represented as `0.12`
* `periods` in an integer number of periods
## Examples
iex> Money.Financial.future_value Money.new(:USD, 10000), 0.08, 1
#Money<:USD, 10800.00>
iex> Money.Financial.future_value Money.new(:USD, 10000), 0.04, 2
#Money<:USD, 10816.0000>
iex> Money.Financial.future_value Money.new(:USD, 10000), 0.02, 4
#Money<:USD, 10824.32160000>
"""
@spec future_value(Money.t(), number, number) :: Money.t()
@one Decimal.new(1)
def future_value(%Money{currency: currency, amount: amount}, interest_rate, periods)
when is_number(interest_rate) and is_number(periods) do
fv =
interest_rate
|> Decimal.from_float()
|> Decimal.add(@one)
|> Math.power(periods)
|> Decimal.mult(amount)
Money.new(currency, fv)
end
@doc """
Calculates the future value for a list of cash flows and an interest rate.
* `flows` is a list of tuples representing a cash flow. Each flow is
represented as a tuple of the form `{period, %Money{}}`
* `interest_rate` is a float representation of an interest rate. For
example, 12% would be represented as `0.12`
## Example
iex> Money.Financial.future_value([{4, Money.new(:USD, 10000)}, {5, Money.new(:USD, 10000)}, {6, Money.new(:USD, 10000)}], 0.13)
#Money<:USD, 34068.99999999999999999999999>
iex> Money.Financial.future_value [{0, Money.new(:USD, 5000)},{1, Money.new(:USD, 2000)}], 0.12
#Money<:USD, 7600.000000000000000000000000>
"""
@spec future_value(list({number, Money.t()}), number) :: Money.t()
def future_value(flows, interest_rate)
def future_value([{period, %Money{}} | _other_flows] = flows, interest_rate)
when is_integer(period) and is_number(interest_rate) do
{max_period, _} = Enum.max(flows)
present_value(flows, interest_rate)
|> future_value(interest_rate, max_period)
end
@doc """
Calculates the present value for future value, an interest rate
and a number of periods
* `future_value` is a %Money{} representation of the future value
* `interest_rate` is a float representation of an interest rate. For
example, 12% would be represented as `0.12`
* `periods` in an integer number of periods
## Examples
iex> Money.Financial.present_value Money.new(:USD, 100), 0.08, 2
#Money<:USD, 85.73388203017832647462277092>
iex> Money.Financial.present_value Money.new(:USD, 1000), 0.10, 20
#Money<:USD, 148.6436280241436864020760472>
"""
@spec present_value(Money.t(), number, number) :: Money.t()
def present_value(%Money{currency: currency, amount: amount}, interest_rate, periods)
when is_number(interest_rate) and is_number(periods) and periods >= 0 do
pv_1 =
interest_rate
|> Decimal.from_float()
|> Decimal.add(@one)
|> Math.power(periods)
pv = Decimal.div(amount, pv_1)
Money.new(currency, pv)
end
@doc """
Calculates the present value for a list of cash flows and an interest rate.
* `flows` is a list of tuples representing a cash flow. Each flow is
represented as a tuple of the form `{period, %Money{}}`
* `interest_rate` is a float representation of an interest rate. For
example, 12% would be represented as `0.12`
## Example
iex> Money.Financial.present_value([{4, Money.new(:USD, 10000)}, {5, Money.new(:USD, 10000)}, {6, Money.new(:USD, 10000)}], 0.13)
#Money<:USD, 16363.97191111964880256655144>
iex> Money.Financial.present_value [{0, Money.new(:USD, -1000)},{1, Money.new(:USD, -4000)}], 0.1
#Money<:USD, -4636.363636363636363636363636>
"""
@spec present_value(list({integer, Money.t()}), number) :: Money.t()
def present_value(flows, interest_rate)
def present_value([{period, %Money{}} | _other_flows] = flows, interest_rate)
when is_integer(period) and is_number(interest_rate) do
validate_same_currency!(flows)
do_present_value(flows, interest_rate)
end
defp do_present_value({period, %Money{} = flow}, interest_rate)
when is_integer(period) and is_number(interest_rate) do
present_value(flow, interest_rate, period)
end
defp do_present_value([{period, %Money{}} = flow | []], interest_rate)
when is_integer(period) and is_number(interest_rate) do
do_present_value(flow, interest_rate)
end
defp do_present_value([{period, %Money{}} = flow | other_flows], interest_rate)
when is_integer(period) and is_number(interest_rate) do
do_present_value(flow, interest_rate)
|> Money.add!(do_present_value(other_flows, interest_rate))
end
@doc """
Calculates the net present value of an initial investment, a list of
cash flows and an interest rate.
* `flows` is a list of tuples representing a cash flow. Each flow is
represented as a tuple of the form `{period, %Money{}}`
* `interest_rate` is a float representation of an interest rate. For
example, 12% would be represented as `0.12`
* `investment` is a %Money{} struct representing the initial investment
## Example
iex> flows = [{0, Money.new(:USD, 5000)},{1, Money.new(:USD, 2000)},{2, Money.new(:USD, 500)},{3, Money.new(:USD,10_000)}]
iex> Money.Financial.net_present_value flows, 0.08, Money.new(:USD, 100)
#Money<:USD, 15118.84367220444038002337042>
iex> Money.Financial.net_present_value flows, 0.08
#Money<:USD, 15218.84367220444038002337042>
"""
@spec net_present_value(list({integer, Money.t()}), number) :: Money.t()
def net_present_value([{period, %Money{currency: currency}} | _] = flows, interest_rate)
when is_integer(period) and is_number(interest_rate) do
net_present_value(flows, interest_rate, Money.zero(currency))
end
@spec net_present_value(list({integer, Money.t()}), number, Money.t()) :: Money.t()
def net_present_value([{period, %Money{}} | _] = flows, interest_rate, %Money{} = investment)
when is_integer(period) and is_number(interest_rate) do
validate_same_currency!(investment, flows)
present_value(flows, interest_rate)
|> Money.sub!(investment)
end
@doc """
Calculates the net present value of an initial investment, a recurring
payment, an interest rate and a number of periods
* `investment` is a %Money{} struct representing the initial investment
* `future_value` is a %Money{} representation of the future value
* `interest_rate` is a float representation of an interest rate. For
example, 12% would be represented as `0.12`
* `periods` in an integer number of a period
## Example
iex> Money.Financial.net_present_value Money.new(:USD, 10000), 0.13, 2
#Money<:USD, 7831.466833737959119743127888>
iex> Money.Financial.net_present_value Money.new(:USD, 10000), 0.13, 2, Money.new(:USD, 100)
#Money<:USD, 7731.466833737959119743127888>
"""
@spec net_present_value(Money.t(), number, number) :: Money.t()
def net_present_value(%Money{currency: currency} = future_value, interest_rate, periods) do
net_present_value(future_value, interest_rate, periods, Money.new(currency, 0))
end
@spec net_present_value(Money.t(), number, number, Money.t()) :: Money.t()
def net_present_value(%Money{} = future_value, interest_rate, periods, %Money{} = investment) do
present_value(future_value, interest_rate, periods)
|> Money.sub!(investment)
end
@doc """
Calculates the interal rate of return for a given list of cash flows.
* `flows` is a list of tuples representing a cash flow. Each flow is
represented as a tuple of the form `{period, %Money{}}`
"""
@spec internal_rate_of_return(list({integer, Money.t()})) :: number()
def internal_rate_of_return([{_period, %Money{}} | _other_flows] = flows) do
# estimate_m = sum_of_inflows(flows)
# |> Kernel./(abs(Math.to_float(amount)))
# |> :math.pow(2 / (number_of_flows(flows) + 1))
# |> Kernel.-(1)
# estimate_n = :math.pow(1 + estimate_m, )
estimate_n = 0.2
estimate_m = 0.1
do_internal_rate_of_return(flows, estimate_m, estimate_n)
end
@irr_precision 0.000001
defp do_internal_rate_of_return(flows, estimate_m, estimate_n) do
npv_n = net_present_value(flows, estimate_n).amount |> Math.to_float()
npv_m = net_present_value(flows, estimate_m).amount |> Math.to_float()
if abs(npv_n - npv_m) > @irr_precision do
estimate_o = estimate_n - (estimate_n - estimate_m) / (npv_n - npv_m) * npv_n
do_internal_rate_of_return(flows, estimate_n, estimate_o)
else
estimate_n
end
end
@doc """
Calculates the effective interest rate for a given present value,
a future value and a number of periods.
* `present_value` is a %Money{} representation of the present value
* `future_value` is a %Money{} representation of the future value
* `periods` is an integer number of a period
## Examples
iex> Money.Financial.interest_rate Money.new(:USD, 10000), Money.new(:USD, 10816), 2
#Decimal<0.04>
iex> Money.Financial.interest_rate Money.new(:USD, 10000), Money.new(:USD, "10824.3216"), 4
#Decimal<0.02>
"""
@spec interest_rate(Money.t(), Money.t(), number) :: Decimal.t()
def interest_rate(
%Money{currency: pv_currency, amount: pv_amount} = _present_value,
%Money{currency: fv_currency, amount: fv_amount} = _future_value,
periods
)
when pv_currency == fv_currency and is_integer(periods) and periods > 0 do
fv_amount
|> Decimal.div(pv_amount)
|> Math.root(periods)
|> Decimal.sub(@one)
end
@doc """
Calculates the number of periods between a present value and
a future value with a given interest rate.
* `present_value` is a %Money{} representation of the present value
* `future_value` is a %Money{} representation of the future value
* `interest_rate` is a float representation of an interest rate. For
example, 12% would be represented as `0.12`
## Example
iex> Money.Financial.periods Money.new(:USD, 1500), Money.new(:USD, 2000), 0.005
#Decimal<57.68013595323872502502238648>
"""
@spec periods(Money.t(), Money.t(), float) :: Decimal.t()
def periods(
%Money{currency: pv_currency, amount: pv_amount} = _present_value,
%Money{currency: fv_currency, amount: fv_amount} = _future_value,
interest_rate
)
when pv_currency == fv_currency and is_float(interest_rate) and interest_rate > 0 do
Decimal.div(
Math.log(Decimal.div(fv_amount, pv_amount)),
Math.log(Decimal.add(@one, Decimal.from_float(interest_rate)))
)
end
@doc """
Calculates the payment for a given loan or annuity given a
present value, an interest rate and a number of periods.
* `present_value` is a %Money{} representation of the present value
* `interest_rate` is a float representation of an interest rate. For
example, 12% would be represented as `0.12`
* `periods` is an integer number of periods
## Example
iex> Money.Financial.payment Money.new(:USD, 100), 0.12, 20
#Money<:USD, 13.38787800396606622792492299>
"""
@spec payment(Money.t(), float, number) :: Money.t()
def payment(
%Money{currency: pv_currency, amount: pv_amount} = _present_value,
interest_rate,
periods
)
when is_float(interest_rate) and interest_rate > 0 and is_number(periods) and periods > 0 do
interest_rate = Decimal.from_float(interest_rate)
p1 = Decimal.mult(pv_amount, interest_rate)
p2 = Decimal.sub(@one, Decimal.add(@one, interest_rate) |> Math.power(-periods))
Money.new(pv_currency, Decimal.div(p1, p2))
end
defp validate_same_currency!(%Money{} = flow, flows) do
validate_same_currency!([{0, flow} | flows])
end
defp validate_same_currency!(flows) do
number_of_currencies =
flows
|> Enum.map(fn {_period, %Money{currency: currency}} -> currency end)
|> Enum.uniq()
|> Enum.count()
if number_of_currencies > 1 do
raise ArgumentError,
message:
"More than one currency found in cash flows; " <>
"implicit currency conversion is not supported. Cash flows: " <> inspect(flows)
end
end
end