defmodule Finance do use Timex @moduledoc """ Library to calculate IRR through the Bisection method. """ @type rate :: float @type date :: Date.t() defp xirr_reduction({period, value, rate}), do: value / :math.pow(1 + rate, period) @doc """ Function to calculate the XIRR for a given array of dates and values. ## Examples iex> d = [{2015, 11, 1}, {2015,10,1}, {2015,6,1}] iex> v = [-800_000, -2_200_000, 1_000_000] iex> Finance.xirr(d,v) { :ok, 21.118359 } """ @spec xirr([date], [number]) :: rate def xirr(dates, values) when length(dates) != length(values) do {:error, "Date and Value collections must have the same size"} end def xirr(dates, values) do dates = dates |> Enum.map(&Date.from_erl!/1) min_date = Enum.min(dates) {dates, values, dates_values} = compact_flow(Enum.zip(dates, values), min_date) cond do !verify_flow(values) -> {:error, "Values should have at least one positive or negative value."} length(dates) - length(values) == 0 && verify_flow(values) -> boundries = {guess_rate(dates, values), -1.0, +1.0} calculate(:xirr, dates_values, [], boundries, 0) true -> {:error, "Uncaught error"} end end # def xirr defp compact_flow(dates_values, min_date) do flow = Enum.reduce(dates_values, %{}, &organize_value(&1, &2, min_date)) {Map.keys(flow), Map.values(flow), Enum.filter(flow, &(elem(&1, 1) != 0))} end defp organize_value({date, value}, map, min_date) do days = Timex.diff(date, min_date, :days) / 365.0 Map.update(map, days, value, &(value + &1)) end defp verify_flow(values) do Enum.any?(values, fn x -> x > 0 end) && Enum.any?(values, fn x -> x < 0 end) end @spec guess_rate([date], [number]) :: rate defp guess_rate(dates, values) do {min_value, max_value} = Enum.min_max(values) period = 1 / (length(dates) - 1) multiple = 1 + abs(max_value / min_value) rate = :math.pow(multiple, period) - 1 Float.round(rate, 3) end defp reached_boundry(rate, upper), do: abs(Float.round(rate - upper, 2)) == 0.0 defp first_value_sign(dates_values) do [head | _] = dates_values {_, first_value} = head cond do first_value < 0 -> 1 first_value > 0 -> -1 true -> 0 end end defp reduce_date_values(dates_values, rate) do list = dates_values acc = list |> Enum.map(fn x -> { elem(x, 0), elem(x, 1), rate } end) |> Enum.map(&xirr_reduction/1) |> Enum.sum() |> Float.round(4) acc * first_value_sign(dates_values) end defp calculate(:xirr, _date_values, 0.0, {rate, _bottom, _upper}, _tries) do {:ok, Float.round(rate, 6)} end defp calculate(:xirr, _date_values, _acc, {-1.0, _bottom, _upper}, _tries) do {:error, "Could not converge"} end defp calculate(:xirr, _date_values, _acc, {_, _, _}, 300) do {:error, "Unable to converge"} end defp calculate(:xirr, dates_values, _acc, {rate, bottom, upper}, tries) do acc = reduce_date_values(dates_values, rate) resp = cond do acc < 0 -> # upper = rate # rate = (bottom + rate) / 2 {(bottom + rate) / 2, bottom, rate} acc > 0 && reached_boundry(rate, upper) -> # bottom = rate # rate = (rate + upper) / 2 # upper = upper + 1 {(rate + upper) / 2, rate, upper + 1} acc > 0 && !reached_boundry(rate, upper) -> # bottom = rate # rate = (rate + upper) / 2 {(rate + upper) / 2, rate, upper} acc == 0.0 -> # rate {rate, bottom, upper} end tries = tries + 1 calculate(:xirr, dates_values, acc, resp, tries) end end