Packages

Professional financial calculations for Elixir applications

Current section

Files

Jump to
finex lib finex.ex
Raw

lib/finex.ex

defmodule Finex do
@moduledoc """
Professional financial calculations for Elixir applications.
Finex provides accurate, battle-tested financial calculations with decimal precision
and comprehensive coverage of common financial scenarios.
## Features
- 🎯 **Precision**: Uses `Decimal` for all calculations to avoid floating-point errors
- 📊 **Comprehensive**: Covers investments, savings, and inflation analysis
- 🚀 **Performance**: Optimized for production fintech applications
- 🧪 **Tested**: Extensive test coverage with real-world scenarios
- 💰 **Fee Transparency**: Separate yield and fees for accurate calculations
## Quick Start
# Calculate investment outcome with yield and fees
iex> Finex.investment_outcome(1000, 500, 5, 0.07, 0.01)
{:ok, %{final_amount: Decimal.new("36228.25"), total_contributions: Decimal.new("31000.00"), total_interest: Decimal.new("5228.25"), initial_growth: Decimal.new("1348.46"), monthly_growth: Decimal.new("34879.79")}}
# Calculate timeline for savings goal
iex> Finex.savings_timeline(10_000, 500, 0.05, 0.0)
{:ok, %{years: 1.6, months: 19.25}}
# Analyze inflation impact
iex> Finex.inflation_impact(1_000_000, 4, 0.03)
{:ok, %{future_nominal_value: Decimal.new("1000000.00"), purchasing_power: Decimal.new("888487.05"), purchasing_power_loss: Decimal.new("111512.95"), purchasing_power_percentage: 88.85}}
## Core Module
- `Finex.Investment` - All financial calculations with yield and fee transparency
"""
alias Finex.Investment
@type amount :: Decimal.t() | number()
@type rate :: float()
@type period :: pos_integer()
@doc """
Calculate final investment outcome with initial and monthly contributions.
## Parameters
- `initial` - Initial lump sum investment
- `monthly` - Monthly contribution amount
- `years` - Investment period in years
- `annual_yield` - Expected annual return before fees
- `annual_fees` - Annual fees and expenses
## Examples
iex> Finex.investment_outcome(1000, 500, 5, 0.07, 0.01)
{:ok, %{final_amount: Decimal.new("36228.25"), total_contributions: Decimal.new("31000.00"), total_interest: Decimal.new("5228.25"), initial_growth: Decimal.new("1348.46"), monthly_growth: Decimal.new("34879.79")}}
"""
@spec investment_outcome(amount(), amount(), number(), rate(), rate()) ::
{:ok, map()} | {:error, atom()}
defdelegate investment_outcome(initial, monthly, years, annual_yield, annual_fees \\ 0.0),
to: Investment,
as: :calculate_outcome
@doc """
Calculate timeline to reach savings goal using monthly contributions.
## Parameters
- `target` - Target savings amount
- `monthly` - Monthly contribution
- `annual_yield` - Expected annual return
- `annual_fees` - Annual fees (default: 0)
## Examples
iex> Finex.savings_timeline(10_000, 500, 0.05, 0.0)
{:ok, %{years: 1.6, months: 19.25}}
"""
@spec savings_timeline(amount(), amount(), rate(), rate()) ::
{:ok, %{years: float(), months: integer()}} | {:error, atom()}
defdelegate savings_timeline(target, monthly, annual_yield, annual_fees \\ 0.0),
to: Investment,
as: :calculate_timeline
@doc """
Calculate timeline for lump sum investment to grow to target.
## Parameters
- `target` - Target investment amount
- `initial` - Initial lump sum investment
- `annual_yield` - Expected annual return
- `annual_fees` - Annual fees (default: 0)
## Examples
iex> Finex.lump_sum_timeline(10_000, 5_000, 0.07, 0.0)
{:ok, %{years: 10.0, months: 120}}
"""
@spec lump_sum_timeline(amount(), amount(), rate(), rate()) ::
{:ok, %{years: float(), months: integer()}} | {:error, atom()}
defdelegate lump_sum_timeline(target, initial, annual_yield, annual_fees \\ 0.0),
to: Investment,
as: :calculate_timeline_lump_sum
@doc """
Calculate required monthly contribution to reach investment goal.
## Parameters
- `target` - Target investment amount
- `initial` - Initial investment
- `years` - Investment period in years
- `annual_yield` - Expected annual return
- `annual_fees` - Annual fees (default: 0)
## Examples
iex> Finex.monthly_needed(60_000, 5_000, 5, 0.06, 0.01)
{:ok, Decimal.new("788.04")}
"""
@spec monthly_needed(amount(), amount(), number(), rate(), rate()) ::
{:ok, Decimal.t()} | {:error, atom()}
defdelegate monthly_needed(target, initial, years, annual_yield, annual_fees \\ 0.0),
to: Investment,
as: :required_monthly_contribution
@doc """
Calculate progress percentage toward investment goal.
## Parameters
- `current` - Current investment value
- `target` - Target investment amount
## Examples
iex> Finex.goal_progress(7_500, 10_000)
{:ok, 75.0}
"""
@spec goal_progress(amount(), amount()) :: {:ok, float()} | {:error, atom()}
defdelegate goal_progress(current, target),
to: Investment,
as: :progress_percentage
@doc """
Calculate future purchasing power accounting for inflation.
## Parameters
- `amount_today` - Current amount
- `years` - Number of years in the future
- `inflation_rate` - Annual inflation rate
## Examples
iex> Finex.inflation_impact(1_000_000, 4, 0.03)
{:ok, %{future_nominal_value: Decimal.new("1000000.00"), purchasing_power: Decimal.new("888487.05"), purchasing_power_loss: Decimal.new("111512.95"), purchasing_power_percentage: 88.85}}
"""
@spec inflation_impact(amount(), number(), rate()) :: {:ok, map()} | {:error, atom()}
defdelegate inflation_impact(amount_today, years, inflation_rate),
to: Investment,
as: :future_value_with_inflation
@doc """
Analyze investment goal feasibility given income constraints.
## Parameters
- `monthly_income` - Monthly take-home income
- `target` - Investment goal
- `initial` - Initial investment
- `years` - Timeline in years
- `annual_yield` - Expected return
- `annual_fees` - Annual fees (default: 0)
## Examples
iex> Finex.goal_feasibility(5000, 50_000, 0, 5, 0.07, 0.01)
{:ok, %{difficulty: :moderate, income_percentage: 14.34, recommendation: "Reasonable goal with good budgeting", required_monthly: Decimal.new("716.75")}}
"""
@spec goal_feasibility(amount(), amount(), amount(), number(), rate(), rate()) ::
{:ok, map()} | {:error, atom()}
defdelegate goal_feasibility(
monthly_income,
target,
initial,
years,
annual_yield,
annual_fees \\ 0.0
),
to: Investment,
as: :feasibility_analysis
@doc """
Calculate the time required to double an investment (Rule of 72 refined).
Uses the net return rate after accounting for fees to provide accurate doubling time.
## Parameters
- `annual_yield` - Annual return rate before fees
- `annual_fees` - Annual fees (default: 0)
## Examples
iex> Finex.doubling_time(0.07, 0.01)
{:ok, 11.59}
iex> Finex.doubling_time(0.08, 0.0)
{:ok, 8.69}
"""
@spec doubling_time(rate(), rate()) :: {:ok, float()} | {:error, atom()}
def doubling_time(annual_yield, annual_fees \\ 0.0)
def doubling_time(annual_yield, annual_fees) when annual_yield > 0 do
with {:ok, yield_decimal} <- Finex.Utils.to_decimal(annual_yield),
{:ok, fees_decimal} <- Finex.Utils.to_decimal(annual_fees),
:ok <- Finex.Utils.validate_rate(annual_yield),
:ok <- Finex.Utils.validate_rate(annual_fees) do
monthly_yield = Decimal.div(yield_decimal, Decimal.new(12))
monthly_fees = Decimal.div(fees_decimal, Decimal.new(12))
net_monthly_rate =
Decimal.add(Decimal.new(1), monthly_yield)
|> Decimal.mult(Decimal.sub(Decimal.new(1), monthly_fees))
|> Decimal.sub(Decimal.new(1))
monthly_rate_float = Decimal.to_float(net_monthly_rate)
net_annual_rate = :math.pow(1 + monthly_rate_float, 12) - 1
if net_annual_rate > 0 do
years = :math.log(2) / :math.log(1 + net_annual_rate)
{:ok, Float.round(years, 2)}
else
{:error, :invalid_rate}
end
end
end
def doubling_time(_, _), do: {:error, :invalid_rate}
end