Current section
2 Versions
Jump to
Current section
2 Versions
Compare versions
4
files changed
+1020
additions
-928
deletions
| @@ -4,10 +4,12 @@ | |
| 4 4 | [](https://github.com/tterb/atomic-design-ui/blob/master/LICENSEs) |
| 5 5 | [](https://hex.pm/packages/financials) |
| 6 6 | |
| 7 | - ***This library is currently incomplete!*** |
| 8 | - |
| 9 7 | A financial modeling library for elixir. Contains functions that can be used as building blocks for complex financial modeling. |
| 10 8 | |
| 9 | + ## Hex |
| 10 | + |
| 11 | + - Package: [Link](https://hex.pm/packages/financials) |
| 12 | + - Hex Docs: [Link](https://hexdocs.pm/financials/api-reference.html) |
| 11 13 | |
| 12 14 | ## Installation |
| 13 15 | |
| @@ -16,7 +18,7 @@ The package can be installed by adding `financial` to your list of dependencies | |
| 16 18 | ```elixir |
| 17 19 | def deps do |
| 18 20 | [ |
| 19 | - {:financials, "~> 0.0.0"} |
| 21 | + {:financials, "~> 0.1.0"} |
| 20 22 | ] |
| 21 23 | end |
| 22 24 | ``` |
| @@ -27,18 +29,18 @@ Requests return a 2-tuple with the standard `:ok` or `:error` status. | |
| 27 29 | |
| 28 30 | ```elixir |
| 29 31 | # Successful response |
| 30 | - {:ok, debt_to_equity_ratio} = Financials.debt_to_equity(100_000, 1_000_000) |
| 32 | + {:ok, result} = Financials.debt_to_equity(Decimal<100.23>, Decimal<25.32>) |
| 31 33 | |
| 32 34 | # Unsuccessful response due to argument type |
| 33 | - {:error, "Arguments must be numerical"} = Financials.net_income(100_000, "1_000_000") |
| 35 | + {:error, "Arguments must be decimals"} = Financials.net_income(123.23, "23.45") |
| 34 36 | |
| 35 37 | # Unsuccessful response due to argument value |
| 36 | - {:error, "total_equity can't be zero (Divide by zero error)"} = Financials.net_income(100_000, 0) |
| 38 | + {:error, "total_equity can't be zero (Divide by zero error)"} = Financials.net_income(Decimal<100000.00>, Decimal<0>) |
| 37 39 | ``` |
| 38 40 | |
| 39 41 | ## Future plans |
| 40 42 | |
| 41 | - - 🎯 Config settings for decimal and rounding accuracy |
| 43 | + - 🎯 Improve decimal and rounding accuracy |
| 42 44 | - 🔢 Config settings for parsing numerical values from string format |
| 43 45 | |
| 44 46 | ## Contributing |
| @@ -47,6 +49,9 @@ Open an issue or create a fork and submit a pull request. | |
| 47 49 | |
| 48 50 | ## Functions |
| 49 51 | |
| 52 | + ### @params :: All arguments must be decimals with the exception of "integer-type" arguments such as `days` which may be either decimal, float or int |
| 53 | + ### @return :: All results within `:ok` tuples are Decimals |
| 54 | + |
| 50 55 | #### Net Income |
| 51 56 | |
| 52 57 | ```elixir |
| @@ -77,22 +82,22 @@ ror(net_income, sales_revenue) | |
| 77 82 | ```elixir |
| 78 83 | ros(operating_profit, net_sales) |
| 79 84 | ``` |
| 80 | - #### Cost of Goods Sold |
| 85 | + #### COGS -- Cost of Goods Sold |
| 81 86 | ```elixir |
| 82 87 | cogs(beginning_inventory, purchases, ending_inventory) |
| 83 88 | ``` |
| 84 89 | |
| 85 | - #### Earnings Before Interest and Taxes |
| 90 | + #### EBIT -- Earnings Before Interest and Taxes |
| 86 91 | ```elixir |
| 87 92 | ebit(revenue, cogs, operating_expenses) |
| 88 93 | ``` |
| 89 94 | |
| 90 | - #### Earnings Before Interest, Taxes, and Amortization |
| 95 | + #### EBITA -- Earnings Before Interest, Taxes, and Amortization |
| 91 96 | ```elixir |
| 92 97 | ebita(revenue, cogs, operating_expenses, amortization) |
| 93 98 | ``` |
| 94 99 | |
| 95 | - #### Earnings Before Interest, Taxes, Depreciation, and Amortization |
| 100 | + #### EBITDA -- Earnings Before Interest, Taxes, Depreciation, and Amortization |
| 96 101 | ```elixir |
| 97 102 | ebitda(net_income, interest_expense, taxes, depreciation, amortization) |
| 98 103 | ``` |
| @@ -152,7 +157,7 @@ cash_flow_coverage(operating_cash_flows, total_debt) | |
| 152 157 | cash_ratio(cash, cash_equivalents, total_current_liabilities) |
| 153 158 | ``` |
| 154 159 | |
| 155 | - #### Compounded Annual Growth Rate |
| 160 | + #### CAGR -- Compounded Annual Growth Rate |
| 156 161 | ```elixir |
| 157 162 | cagr(beginning_investment_value, ending_investment_value, years) |
| 158 163 | ``` |
| @@ -167,7 +172,7 @@ contribution_margin(net_sales, variable_costs) | |
| 167 172 | current_ratio(current_assets, current_liabilities) |
| 168 173 | ``` |
| 169 174 | |
| 170 | - #### Days Payable Outstanding |
| 175 | + #### DPO -- Days Payable Outstanding |
| 171 176 | ```elixir |
| 172 177 | dpo(accounts_payable, cost_of_sales, days) |
| 173 178 | ``` |
| @@ -217,7 +222,7 @@ dti(total_monthly_debt_payments, gross_monthly_income) | |
| 217 222 | dir(defensive_assets, daily_operational_expenses) |
| 218 223 | ``` |
| 219 224 | |
| 220 | - #### Earnings Per Share - Basic |
| 225 | + #### EPS -- Earnings Per Share - Basic |
| 221 226 | ```elixir |
| 222 227 | eps_basic(earnings, shares_outstanding) |
| 223 228 | ``` |
| @@ -247,7 +252,7 @@ eps_retained(retained_earnings, shares_outstanding) | |
| 247 252 | eps_cash(operating_cash_flow, shares_outstanding) |
| 248 253 | ``` |
| 249 254 | |
| 250 | - #### Price to Earnings Ratio |
| 255 | + #### PE -- Price to Earnings Ratio |
| 251 256 | ```elixir |
| 252 257 | pe_ratio(price, earnings_per_share) |
| 253 258 | ``` |
| @@ -287,5 +292,73 @@ equity_multiplier(total_assets, total_stockholders_equity) | |
| 287 292 | equity_ratio(total_equity, total_assets) |
| 288 293 | ``` |
| 289 294 | |
| 290 | - #### ... |
| 291 | - ... |
| 295 | + #### Expense Ratio |
| 296 | + ```elixir |
| 297 | + expense_ratio(operating_expenses, average_value_of_fund_assets) |
| 298 | + ``` |
| 299 | + |
| 300 | + #### Fixed Asset Turnover Ratio |
| 301 | + ```elixir |
| 302 | + fixed_asset_turnover_ratio(net_sales, fixed_assets, accumulated_depreciation) |
| 303 | + ``` |
| 304 | + |
| 305 | + #### Fixed Charge Coverage Ratio |
| 306 | + ```elixir |
| 307 | + fixed_charge_coverage_ratio(ebit, fixed_charges_before_taxes, interest) |
| 308 | + ``` |
| 309 | + |
| 310 | + #### Free Cash Flow |
| 311 | + ```elixir |
| 312 | + fcf(operating_cash_flow, capital_expenditures) |
| 313 | + ``` |
| 314 | + |
| 315 | + #### Goodwill to Assets |
| 316 | + ```elixir |
| 317 | + goodwill_to_assets(goodwill, assets) |
| 318 | + ``` |
| 319 | + |
| 320 | + #### Gross Margin Ratio |
| 321 | + ```elixir |
| 322 | + gross_margin_ratio(gross_margin, net_sales) |
| 323 | + ``` |
| 324 | + |
| 325 | + #### Gross Profit |
| 326 | + ```elixir |
| 327 | + gross_profit(total_sales, cogs) |
| 328 | + ``` |
| 329 | + |
| 330 | + #### Interest Coverage Ratio |
| 331 | + ```elixir |
| 332 | + interest_coverage_ratio(ebit, interest_expense) |
| 333 | + ``` |
| 334 | + |
| 335 | + #### Inventory Turnover Ratio |
| 336 | + ```elixir |
| 337 | + inventory_turnover_ratio(cogs, average_inventory) |
| 338 | + ``` |
| 339 | + |
| 340 | + #### Loan to Value Ratio |
| 341 | + ```elixir |
| 342 | + ltv(mortgage_amount, appraised_value_of_property) |
| 343 | + ``` |
| 344 | + |
| 345 | + #### Long Term Debt to Total Asset Ratio |
| 346 | + ```elixir |
| 347 | + long_term_debt_to_total_asset_ratio(long_term_debt, total_assets) |
| 348 | + ``` |
| 349 | + |
| 350 | + #### Margin of Safety |
| 351 | + ```elixir |
| 352 | + margin_of_safety(actual_sales, break_even_point) |
| 353 | + ``` |
| 354 | + |
| 355 | + #### Margin of Safety Ratio |
| 356 | + ```elixir |
| 357 | + margin_of_safety_ratio(actual_sales, break_even_point) |
| 358 | + ``` |
| 359 | + |
| 360 | + #### Margin of Revenue |
| 361 | + ```elixir |
| 362 | + margin_of_revenue(change_in_total_revenues, change_in_quantity_sold) |
| 363 | + ``` |
| 364 | + |
| @@ -3,10 +3,16 @@ | |
| 3 3 | {<<"description">>,<<"A finance library for elixir.">>}. |
| 4 4 | {<<"elixir">>,<<"~> 1.10.2">>}. |
| 5 5 | {<<"files">>, |
| 6 | - [<<"lib">>,<<"lib/financials.ex">>,<<"mix.exs">>,<<"README.md">>]}. |
| 6 | + [<<"lib">>,<<"lib/financials.ex">>,<<"lib/financials">>,<<"mix.exs">>, |
| 7 | + <<"README.md">>]}. |
| 7 8 | {<<"licenses">>,[<<"MIT">>]}. |
| 8 9 | {<<"links">>, |
| 9 10 | [{<<"GitHub">>,<<"https://github.com/GunnarPDX/Elixir-Finance">>}]}. |
| 10 11 | {<<"name">>,<<"financials">>}. |
| 11 | - {<<"requirements">>,[]}. |
| 12 | - {<<"version">>,<<"0.0.0">>}. |
| 12 | + {<<"requirements">>, |
| 13 | + [[{<<"app">>,<<"decimal">>}, |
| 14 | + {<<"name">>,<<"decimal">>}, |
| 15 | + {<<"optional">>,false}, |
| 16 | + {<<"repository">>,<<"hexpm">>}, |
| 17 | + {<<"requirement">>,<<"~> 2.0">>}]]}. |
| 18 | + {<<"version">>,<<"0.1.0">>}. |
| @@ -1,1032 +1,1044 @@ | |
| 1 1 | defmodule Financials do |
| 2 2 | |
| 3 | + alias Decimal, as: D |
| 4 | + |
| 5 | + @moduledoc """ |
| 6 | + A financial modeling library for elixir. Contains functions that can be used as building blocks for complex financial modeling. |
| 7 | + |
| 8 | + ## Usage |
| 9 | + |
| 10 | + Requests return a 2-tuple with the standard `:ok` or `:error` status. |
| 11 | + |
| 12 | + ```elixir |
| 13 | + # Successful response |
| 14 | + {:ok, result} = Financials.debt_to_equity(100_000, 1_000_000) |
| 15 | + |
| 16 | + # Unsuccessful response due to argument type |
| 17 | + {:error, "Arguments must be numerical"} = Financials.net_income(100_000, "1_000_000") |
| 18 | + |
| 19 | + # Unsuccessful response due to argument value |
| 20 | + {:error, "total_equity can't be zero (Divide by zero error)"} = Financials.net_income(100_000, 0) |
| 21 | + ``` |
| 22 | + |
| 23 | + ## Functions |
| 24 | + """ |
| 25 | + |
| 3 26 | ##-------------------------------------------------------------- |
| 4 | - ## CONSTANTS FOR ROUNDING |
| 27 | + ## CONSTANTS |
| 5 28 | ##-------------------------------------------------------------- |
| 6 29 | @two_decimal_precision 2 |
| 30 | + @arg_msg "Arguments must be decimals" |
| 31 | + @zero_error "can't equal zero (Divide by zero error)" |
| 7 32 | |
| 8 | - ##-------------------------------------------------------------- |
| 9 | - ## Net Income Calculation |
| 10 | - ## @param float -- total_revenues |
| 11 | - ## @param float -- total_expenses |
| 12 | - ## @return tuple - {atom, float} |
| 13 | - ##-------------------------------------------------------------- |
| 14 | - def net_income(total_revenues, total_expenses) |
| 15 | - when is_number(total_expenses) |
| 16 | - and is_number(total_revenues) |
| 17 | - do |
| 18 | - {:ok, (total_revenues - total_expenses)} |
| 33 | + |
| 34 | + @doc """ |
| 35 | + Net Income Calculation |
| 36 | + """ |
| 37 | + def net_income(%Decimal{} = total_revenues, %Decimal{} = total_expenses), |
| 38 | + do: {:ok, D.sub(total_revenues, total_expenses)} |
| 39 | + |
| 40 | + def net_income(_, _), |
| 41 | + do: {:error, @arg_msg} |
| 42 | + |
| 43 | + |
| 44 | + @doc """ |
| 45 | + Net Earnings Calculation |
| 46 | + """ |
| 47 | + def earnings(%Decimal{} = net_income, %Decimal{} = preferred_dividends), |
| 48 | + do: {:ok, D.sub(net_income, preferred_dividends)} |
| 49 | + |
| 50 | + def earnings(_, _), |
| 51 | + do: {:error, @arg_msg} |
| 52 | + |
| 53 | + |
| 54 | + @doc """ |
| 55 | + Retained Earnings Calculation |
| 56 | + """ |
| 57 | + def retained_earnings( |
| 58 | + %Decimal{} = beginning_period_retained_earnings, |
| 59 | + %Decimal{} = net_income, |
| 60 | + %Decimal{} = cash_dividends, |
| 61 | + %Decimal{} = stock_dividends |
| 62 | + ) do |
| 63 | + earnings = D.add(beginning_period_retained_earnings, net_income) |
| 64 | + dividend_expenses = D.sub(cash_dividends, stock_dividends) |
| 65 | + |
| 66 | + {:ok, D.sub(earnings, dividend_expenses)} |
| 19 67 | end |
| 20 | - def net_income(_, _), do: {:error, "Arguments must be numerical"} |
| 21 68 | |
| 22 | - ##-------------------------------------------------------------- |
| 23 | - ## Net Earnings Calculation |
| 24 | - ## @param float -- net_income |
| 25 | - ## @param float -- preferred_dividends |
| 26 | - ## @return tuple - {atom, float} |
| 27 | - ##-------------------------------------------------------------- |
| 28 | - def earnings(net_income, preferred_dividends) |
| 29 | - when is_number(net_income) |
| 30 | - and is_number(preferred_dividends) |
| 31 | - do |
| 32 | - {:ok, (net_income - preferred_dividends)} |
| 69 | + def retained_earnings(_, _, _, _), |
| 70 | + do: {:error, @arg_msg} |
| 71 | + |
| 72 | + |
| 73 | + @doc """ |
| 74 | + Operating Cash Flow Calculation |
| 75 | + """ |
| 76 | + def ocf( |
| 77 | + %Decimal{} = operating_income, |
| 78 | + %Decimal{} = depreciation, |
| 79 | + %Decimal{} = taxes, |
| 80 | + %Decimal{} = change_in_working_capital |
| 81 | + ) do |
| 82 | + r1 = D.add(operating_income, depreciation) |
| 83 | + r2 = D.add(taxes, change_in_working_capital) |
| 84 | + |
| 85 | + {:ok, D.sub(r1, r2)} |
| 33 86 | end |
| 34 | - def earnings(_, _), do: {:error, "Arguments must be numerical"} |
| 35 87 | |
| 36 | - ##-------------------------------------------------------------- |
| 37 | - ## Retained Earnings Calculation |
| 38 | - ## @param float -- beginning_period_retained_earnings |
| 39 | - ## @param float -- net_income |
| 40 | - ## @param float -- cash_dividends |
| 41 | - ## @param float -- stock_dividends |
| 42 | - ## @return tuple - {atom, float} |
| 43 | - ##-------------------------------------------------------------- |
| 44 | - def retained_earnings(beginning_period_retained_earnings, net_income, cash_dividends, stock_dividends) |
| 45 | - when is_number(beginning_period_retained_earnings) |
| 46 | - and is_number(net_income) |
| 47 | - and is_number(cash_dividends) |
| 48 | - and is_number(stock_dividends) |
| 49 | - do |
| 50 | - {:ok, (beginning_period_retained_earnings + net_income - cash_dividends - stock_dividends)} |
| 51 | - end |
| 52 | - def retained_earnings(_, _, _, _), do: {:error, "Arguments must be numerical"} |
| 88 | + def ocf(_, _, _, _), |
| 89 | + do: {:error, @arg_msg} |
| 53 90 | |
| 54 | - # TODO: change_in_working_capital |
| 55 91 | |
| 56 | - ##-------------------------------------------------------------- |
| 57 | - ## Operating Cash Flow Calculation |
| 58 | - ## @param float -- operating_income |
| 59 | - ## @param float -- depreciation |
| 60 | - ## @param float -- taxes |
| 61 | - ## @param float -- change_in_working_capital |
| 62 | - ## @return tuple - {atom, float} |
| 63 | - ##-------------------------------------------------------------- |
| 64 | - def ocf(operating_income, depreciation, taxes, change_in_working_capital) |
| 65 | - when is_number(operating_income) |
| 66 | - and is_number(depreciation) |
| 67 | - and is_number(taxes) |
| 68 | - and is_number(change_in_working_capital) |
| 69 | - do |
| 70 | - {:ok, (operating_income + depreciation - taxes + change_in_working_capital)} |
| 71 | - end |
| 72 | - def ocf(_, _, _, _), do: {:error, "Arguments must be numerical"} |
| 73 | - |
| 74 | - ##-------------------------------------------------------------- |
| 75 | - ## Return on Revenue Calculation |
| 76 | - ## @param float -- net_income |
| 77 | - ## @param float -- sales_revenue |
| 78 | - ## @return tuple - {atom, float} |
| 79 | - ##-------------------------------------------------------------- |
| 80 | - def ror(_, 0), do: {:error, "Sales revenue cannot be zero (divide by zero error)"} |
| 92 | + @doc """ |
| 93 | + Return on Revenue Calculation |
| 94 | + """ |
| 81 95 | def ror(net_income, sales_revenue) |
| 82 | - when is_number(net_income) |
| 83 | - and is_number(sales_revenue) |
| 84 | - do |
| 85 | - {:ok, (net_income/sales_revenue)} |
| 86 | - end |
| 87 | - def ror(_, _), do: {:error, "Arguments must be numeric"} |
| 88 96 | |
| 89 | - ##-------------------------------------------------------------- |
| 90 | - ## Return on Sales Calculation |
| 91 | - ## @param float -- operating_profit |
| 92 | - ## @param float -- net_sales |
| 93 | - ## @return tuple - {atom, float} |
| 94 | - ##-------------------------------------------------------------- |
| 95 | - def ros(_, 0), do: {:error, "Net sales cannot be zero (divide by zero error"} |
| 97 | + def ror(%Decimal{coef: 0} = _, %Decimal{coef: 0} = _), |
| 98 | + do: {:ok, D.new(0)} |
| 99 | + |
| 100 | + def ror(_, %Decimal{coef: 0} = _), |
| 101 | + do: {:error, "sales_revenue #{@zero_error}"} |
| 102 | + |
| 103 | + def ror(%Decimal{} = net_income, %Decimal{} = sales_revenue), |
| 104 | + do: {:ok, D.div(net_income, sales_revenue)} |
| 105 | + |
| 106 | + def ror(_, _), |
| 107 | + do: {:error, @arg_msg} |
| 108 | + |
| 109 | + |
| 110 | + @doc """ |
| 111 | + Return on Sales Calculation |
| 112 | + """ |
| 96 113 | def ros(operating_profit, net_sales) |
| 97 | - when is_number(operating_profit) |
| 98 | - and is_number(net_sales) |
| 99 | - do |
| 100 | - {:ok, (operating_profit/net_sales)} |
| 114 | + |
| 115 | + def ros(_, %Decimal{coef: 0} = _), |
| 116 | + do: {:error, "net_sales #{@zero_error}"} |
| 117 | + |
| 118 | + def ros(%Decimal{} = operating_profit, %Decimal{} = net_sales), |
| 119 | + do: {:ok, D.div(operating_profit, net_sales)} |
| 120 | + |
| 121 | + def ros(_, _), |
| 122 | + do: {:error, @arg_msg} |
| 123 | + |
| 124 | + |
| 125 | + @doc """ |
| 126 | + Cost of Goods Sold Calculation |
| 127 | + """ |
| 128 | + def cogs(%Decimal{} = beginning_inventory, %Decimal{} = purchases, %Decimal{} = ending_inventory), |
| 129 | + do: {:ok, D.add(beginning_inventory, D.sub(purchases, ending_inventory))} |
| 130 | + |
| 131 | + def cogs(_, _, _), |
| 132 | + do: {:error, @arg_msg} |
| 133 | + |
| 134 | + |
| 135 | + @doc """ |
| 136 | + EBIT -- Earnings Before Interest and Taxes Calculation |
| 137 | + """ |
| 138 | + def ebit(%Decimal{} = revenue, %Decimal{} = cogs, %Decimal{} = operating_expenses), |
| 139 | + do: {:ok, D.sub(revenue, D.sub(cogs, operating_expenses))} |
| 140 | + |
| 141 | + def ebit(_, _, _), |
| 142 | + do: {:error, @arg_msg} |
| 143 | + |
| 144 | + |
| 145 | + @doc """ |
| 146 | + EBITA -- Earnings Before Interest, Taxes, and Amortization Calculation |
| 147 | + """ |
| 148 | + def ebita( |
| 149 | + %Decimal{} = revenue, |
| 150 | + %Decimal{} = cogs, |
| 151 | + %Decimal{} = operating_expenses, |
| 152 | + %Decimal{} = amortization |
| 153 | + ) do |
| 154 | + {:ok, D.sub(revenue, D.sub(cogs, D.add(operating_expenses, amortization)))} |
| 101 155 | end |
| 102 | - def ros(_, _), do: {:error, "Arguments must be numerical"} |
| 103 156 | |
| 104 | - ##-------------------------------------------------------------- |
| 105 | - ## Cost of Goods Sold Calculation |
| 106 | - ## @param float -- beginning_inventory |
| 107 | - ## @param float -- purchases |
| 108 | - ## @param float -- ending_inventory |
| 109 | - ## @return float |
| 110 | - ##-------------------------------------------------------------- |
| 111 | - def cogs(beginning_inventory, purchases, ending_inventory) |
| 112 | - when is_number(beginning_inventory) |
| 113 | - and is_number(purchases) |
| 114 | - and is_number(ending_inventory) |
| 115 | - do |
| 116 | - {:ok, (beginning_inventory + purchases - ending_inventory)} |
| 157 | + def ebita(_, _, _, _), |
| 158 | + do: {:error, @arg_msg} |
| 159 | + |
| 160 | + |
| 161 | + @doc """ |
| 162 | + EBITDA -- Earnings Before Interest, Taxes, Depreciation and Amortization Calculation |
| 163 | + """ |
| 164 | + def ebitda( |
| 165 | + %Decimal{} = net_income, |
| 166 | + %Decimal{} = interest_expense, |
| 167 | + %Decimal{} = taxes, |
| 168 | + %Decimal{} = depreciation, |
| 169 | + %Decimal{} = amortization |
| 170 | + ) do |
| 171 | + {:ok, D.add(net_income, D.add(interest_expense, D.add(taxes, D.add(depreciation, amortization))))} |
| 117 172 | end |
| 118 | - def cogs(_, _, _), do: {:error, "Arguments must be numerical"} |
| 119 173 | |
| 120 | - ##-------------------------------------------------------------- |
| 121 | - ## EBIT -- Earnings Before Interest and Taxes Calculation |
| 122 | - ## @param float -- revenue |
| 123 | - ## @param float -- cogs |
| 124 | - ## @param float -- operating_expenses |
| 125 | - ## @return float |
| 126 | - ##-------------------------------------------------------------- |
| 127 | - def ebit(revenue, cogs, operating_expenses) |
| 128 | - when is_number(revenue) |
| 129 | - and is_number(cogs) |
| 130 | - and is_number(operating_expenses) |
| 131 | - do |
| 132 | - {:ok, (revenue - cogs - operating_expenses)} |
| 133 | - end |
| 134 | - def ebit(_, _, _), do: {:error, "Arguments must be numbers"} |
| 174 | + def ebitda(_, _, _, _, _), |
| 175 | + do: {:error, @arg_msg} |
| 135 176 | |
| 136 | - # TODO: operating_expenses |
| 137 177 | |
| 138 | - ##-------------------------------------------------------------- |
| 139 | - ## EBITA -- Earnings Before Interest, Taxes, and Amortization Calculation |
| 140 | - ## @param float -- revenue |
| 141 | - ## @param float -- cogs |
| 142 | - ## @param float -- operating_expenses |
| 143 | - ## @param float -- amortization |
| 144 | - ## @return float |
| 145 | - ##-------------------------------------------------------------- |
| 146 | - def ebita(revenue, cogs, operating_expenses, amortization) |
| 147 | - when is_number(revenue) |
| 148 | - and is_number(cogs) |
| 149 | - and is_number(operating_expenses) |
| 150 | - and is_number(amortization) |
| 151 | - do |
| 152 | - {:ok, (revenue - cogs - (operating_expenses + amortization))} |
| 153 | - end |
| 154 | - def ebita(_, _, _, _), do: {:error, "Arguments must be numerical"} |
| 155 | - |
| 156 | - # TODO: amortization ? |
| 157 | - |
| 158 | - ##-------------------------------------------------------------- |
| 159 | - ## EBITDA -- Earnings Before Interest, Taxes, Depreciation and Amortization Calculation |
| 160 | - ## @param float -- net_income |
| 161 | - ## @param float -- interest_expense |
| 162 | - ## @param float -- taxes |
| 163 | - ## @param float -- depreciation |
| 164 | - ## @param float -- amortization |
| 165 | - ## @return float |
| 166 | - ##-------------------------------------------------------------- |
| 167 | - def ebitda(net_income, interest_expense, taxes, depreciation, amortization) |
| 168 | - when is_number(net_income) |
| 169 | - and is_number(interest_expense) |
| 170 | - and is_number(taxes) |
| 171 | - and is_number(depreciation) |
| 172 | - and is_number(amortization) |
| 173 | - do |
| 174 | - {:ok, (net_income + interest_expense + taxes + depreciation + amortization)} |
| 175 | - end |
| 176 | - def ebitda(_, _, _, _, _), do: {:error, "Arguments must be numerical"} |
| 177 | - |
| 178 | - # TODO: net_credit_sales |
| 179 | - # TODO: average_accounts_receivable |
| 180 | - |
| 181 | - ##-------------------------------------------------------------- |
| 182 | - ## Receivable Turnover Ratio Calculation |
| 183 | - ## @param float -- net_credit_sales |
| 184 | - ## @param float -- average_accounts_receivable |
| 185 | - ## @return float |
| 186 | - ##-------------------------------------------------------------- |
| 187 | - def receivable_turnover_ratio(_, 0), do: {:error, "Avg accounts receivable can't be zero (Divide by zero error)"} |
| 178 | + @doc """ |
| 179 | + Receivable Turnover Ratio Calculation |
| 180 | + """ |
| 188 181 | def receivable_turnover_ratio(net_credit_sales, average_accounts_receivable) |
| 189 | - when is_number(net_credit_sales) |
| 190 | - and is_number(average_accounts_receivable) |
| 191 | - do |
| 192 | - {:ok, (Float.round(net_credit_sales/average_accounts_receivable, @two_decimal_precision))} |
| 193 | - end |
| 194 | - def receivable_turnover_ratio(_, _), do: {:error, "Arguments must be numerical"} |
| 195 182 | |
| 196 | - # TODO: accumulated_depreciation |
| 197 | - # TODO: total_fixed_assets |
| 183 | + def receivable_turnover_ratio(_, %Decimal{coef: 0} = _), |
| 184 | + do: {:error, "avg_accounts_receivable #{@zero_error}"} |
| 198 185 | |
| 199 | - ##-------------------------------------------------------------- |
| 200 | - ## Accumulated Depreciation to Fixed Assets Calculation |
| 201 | - ## @param float -- accumulated_depreciation |
| 202 | - ## @param float -- total_fixed_assets |
| 203 | - ## @return float |
| 204 | - ##-------------------------------------------------------------- |
| 205 | - def accumulated_depreciation_to_fixed_assets(_, 0), do: {:error, "Total fixed assets can't be zero (Divide by zero error)"} |
| 186 | + def receivable_turnover_ratio(%Decimal{} = net_credit_sales, %Decimal{} = average_accounts_receivable), |
| 187 | + do: {:ok, D.div(net_credit_sales, average_accounts_receivable)} |
| 188 | + |
| 189 | + def receivable_turnover_ratio(_, _), |
| 190 | + do: {:error, @arg_msg} |
| 191 | + |
| 192 | + |
| 193 | + @doc """ |
| 194 | + Accumulated Depreciation to Fixed Assets Calculation |
| 195 | + """ |
| 206 196 | def accumulated_depreciation_to_fixed_assets(accumulated_depreciation, total_fixed_assets) |
| 207 | - when is_number(accumulated_depreciation) |
| 208 | - and is_number(total_fixed_assets) |
| 209 | - do |
| 210 | - {:ok, (Float.round(accumulated_depreciation/total_fixed_assets, @two_decimal_precision))} |
| 211 | - end |
| 212 | - def accumulated_depreciation_to_fixed_assets(_, _), do: {:error, "Arguments must be numerical"} |
| 213 197 | |
| 214 | - ##-------------------------------------------------------------- |
| 215 | - ## Asset Coverage Ratio Calculation |
| 216 | - ## @param float -- total_assets |
| 217 | - ## @param float -- intangible_assets |
| 218 | - ## @param float -- current_liabilities |
| 219 | - ## @param float -- short_term_debt |
| 220 | - ## @param float -- total_debt |
| 221 | - ## @return float |
| 222 | - ##-------------------------------------------------------------- |
| 223 | - def asset_coverage(_, _, _, _, 0), do: {:error, "Total debt can't be zero (Divide by zero error)"} |
| 198 | + def accumulated_depreciation_to_fixed_assets(_, %Decimal{coef: 0} = _), |
| 199 | + do: {:error, "total_fixed_assets #{@zero_error}"} |
| 200 | + |
| 201 | + def accumulated_depreciation_to_fixed_assets( |
| 202 | + %Decimal{} = accumulated_depreciation, |
| 203 | + %Decimal{} = total_fixed_assets |
| 204 | + ) do |
| 205 | + {:ok, D.div(accumulated_depreciation, total_fixed_assets)} |
| 206 | + end |
| 207 | + |
| 208 | + def accumulated_depreciation_to_fixed_assets(_, _), |
| 209 | + do: {:error, @arg_msg} |
| 210 | + |
| 211 | + |
| 212 | + @doc """ |
| 213 | + Asset Coverage Ratio Calculation |
| 214 | + """ |
| 224 215 | def asset_coverage(total_assets, intangible_assets, current_liabilities, short_term_debt, total_debt) |
| 225 | - when is_number(total_assets) |
| 226 | - and is_number(intangible_assets) |
| 227 | - and is_number(current_liabilities) |
| 228 | - and is_number(short_term_debt) |
| 229 | - and is_number(total_debt) |
| 230 | - do |
| 231 | - assets = total_assets - intangible_assets |
| 232 | - liabilities = current_liabilities - short_term_debt |
| 233 | - {:ok, (Float.round((assets - liabilities)/total_debt, @two_decimal_precision))} |
| 234 | - end |
| 235 | - def asset_coverage(_, _, _, _, _), do: {:error, "Arguments must be numerical"} |
| 236 216 | |
| 237 | - ##-------------------------------------------------------------- |
| 238 | - ## Asset Turnover Ratio Calculation |
| 239 | - ## @param float -- net_sales |
| 240 | - ## @param float -- average_total_sales |
| 241 | - ## @return float |
| 242 | - ##-------------------------------------------------------------- |
| 243 | - def asset_turnover(_, 0), do: {:error, "Average total sales can't be zero (Divide by zero error)"} |
| 217 | + def asset_coverage(_, _, _, _, %Decimal{coef: 0} = _), |
| 218 | + do: {:error, "total_debt #{@zero_error}"} |
| 219 | + |
| 220 | + def asset_coverage( |
| 221 | + %Decimal{} = total_assets, |
| 222 | + %Decimal{} = intangible_assets, |
| 223 | + %Decimal{} = current_liabilities, |
| 224 | + %Decimal{} = short_term_debt, |
| 225 | + %Decimal{} = total_debt |
| 226 | + ) do |
| 227 | + assets = D.sub(total_assets, intangible_assets) |
| 228 | + liabilities = D.sub(current_liabilities, short_term_debt) |
| 229 | + {:ok, D.div(D.sub(assets, liabilities), total_debt)} |
| 230 | + end |
| 231 | + |
| 232 | + def asset_coverage(_, _, _, _, _), |
| 233 | + do: {:error, @arg_msg} |
| 234 | + |
| 235 | + |
| 236 | + @doc """ |
| 237 | + Asset Turnover Ratio Calculation |
| 238 | + """ |
| 244 239 | def asset_turnover(net_sales, average_total_sales) |
| 245 | - when is_number(net_sales) |
| 246 | - and is_number(average_total_sales) |
| 247 | - do |
| 248 | - {:ok, (Float.round(net_sales/average_total_sales, @two_decimal_precision))} |
| 249 | - end |
| 250 | - def asset_turnover(_, _), do: {:error, "Arguments must be numerical"} |
| 251 240 | |
| 252 | - ##-------------------------------------------------------------- |
| 253 | - ## Average Inventory Period Calculation |
| 254 | - ## @param int/float -- days |
| 255 | - ## @param float -- inventory_turnover |
| 256 | - ## @return float |
| 257 | - ##-------------------------------------------------------------- |
| 258 | - def average_inventory_period(_, 0), do: {:error, "Inventory turnover can't be zero (Divide by zero error)"} |
| 241 | + def asset_turnover(_, %Decimal{coef: 0} = _), |
| 242 | + do: {:error, "average_total_sales #{@zero_error}"} |
| 243 | + |
| 244 | + def asset_turnover(%Decimal{} = net_sales, %Decimal{} = average_total_sales), |
| 245 | + do: {:ok, D.div(net_sales, average_total_sales)} |
| 246 | + |
| 247 | + def asset_turnover(_, _), |
| 248 | + do: {:error, @arg_msg} |
| 249 | + |
| 250 | + |
| 251 | + @doc """ |
| 252 | + Average Inventory Period Calculation |
| 253 | + """ |
| 259 254 | def average_inventory_period(days, inventory_turnover) |
| 260 | - when is_number(days) |
| 261 | - and is_number(inventory_turnover) |
| 262 | - do |
| 263 | - {:ok, (Float.round(days/inventory_turnover, @two_decimal_precision))} |
| 264 | - end |
| 265 | - def average_inventory_period(_, _), do: {:error, "Arguments must be numerical"} |
| 266 255 | |
| 267 | - ##-------------------------------------------------------------- |
| 268 | - ## Average Payment Period Calculation |
| 269 | - ## @param float -- average_accounts_payable |
| 270 | - ## @param float -- total_credit_purchases |
| 271 | - ## @param int -- days |
| 272 | - ## @return float |
| 273 | - ##-------------------------------------------------------------- |
| 274 | - def average_payment_period(_, 0, 0), do: {:error, "Days and total credit purchases can't be zero (Divide by zero error)"} |
| 275 | - def average_payment_period(_, _, 0), do: {:error, "Days can't be zero (Divide by zero error)"} |
| 276 | - def average_payment_period(_, 0, _), do: {:error, "Total credit purchases can't be zero (Divide by zero error)"} |
| 256 | + def average_inventory_period(_, %Decimal{coef: 0} = _), |
| 257 | + do: {:error, "inventory_turnover #{@zero_error}"} |
| 258 | + |
| 259 | + def average_inventory_period(%Decimal{} = days, %Decimal{} = inventory_turnover), |
| 260 | + do: {:ok, D.div(days, inventory_turnover)} |
| 261 | + |
| 262 | + def average_inventory_period(_, _), do: {:error, @arg_msg} |
| 263 | + |
| 264 | + |
| 265 | + @doc """ |
| 266 | + Average Payment Period Calculation |
| 267 | + """ |
| 277 268 | def average_payment_period(average_accounts_payable, total_credit_purchases, days) |
| 278 | - when is_number(average_accounts_payable) |
| 279 | - and is_number(total_credit_purchases) |
| 280 | - and is_number(days) |
| 281 | - do |
| 282 | - credit_days = Float.round(total_credit_purchases/days, @two_decimal_precision) |
| 283 | - {:ok, (Float.round(average_accounts_payable/credit_days, @two_decimal_precision))} |
| 284 | - end |
| 285 | - def average_payment_period(_, _, _), do: {:error, "Arguments must be numerical"} |
| 286 269 | |
| 287 | - ##-------------------------------------------------------------- |
| 288 | - ## Break Even Analysis Calculation |
| 289 | - ## @param float -- fixed_costs |
| 290 | - ## @param float -- sales_price_per_unit |
| 291 | - ## @param float -- variable_cost_per_unit |
| 292 | - ## @return float |
| 293 | - ##-------------------------------------------------------------- |
| 294 | - def break_even_analysis(fixed_costs, sales_price_per_unit, variable_cost_per_unit) |
| 295 | - when is_number(fixed_costs) |
| 296 | - and is_number(sales_price_per_unit) |
| 297 | - and is_number(variable_cost_per_unit) |
| 298 | - do |
| 299 | - case (sales_price_per_unit - variable_cost_per_unit) do |
| 300 | - 0 -> {:error, "sales_price_per_unit - variable_cost_per_unit can't equal zero (Divide by zero error)"} |
| 301 | - price -> {:ok, (Float.round(fixed_costs/price, @two_decimal_precision))} |
| 302 | - end |
| 303 | - end |
| 304 | - def break_even_analysis(_, _, _), do: {:error, "Arguments must be numerical"} |
| 270 | + def average_payment_period(_, %Decimal{coef: 0} = _, 0), |
| 271 | + do: {:error, "days & total_credit_purchases #{@zero_error}"} |
| 305 272 | |
| 306 | - ##-------------------------------------------------------------- |
| 307 | - ## Capitalization Ratio Calculation |
| 308 | - ## @param float -- total_debt |
| 309 | - ## @param float -- shareholders_equity |
| 310 | - ## @return float |
| 311 | - ##-------------------------------------------------------------- |
| 312 | - def capitalization_ratio(_, 0), do: {:error, "Shareholders_equity can't be zero (Divide by zero error)"} |
| 273 | + def average_payment_period(_, %Decimal{coef: 0} = _, %Decimal{coef: 0} = _), |
| 274 | + do: {:error, "days & total_credit_purchases #{@zero_error}"} |
| 275 | + |
| 276 | + def average_payment_period(_, %Decimal{coef: 0} = _, _), |
| 277 | + do: {:error, "total_credit_purchases #{@zero_error}"} |
| 278 | + |
| 279 | + def average_payment_period( |
| 280 | + %Decimal{} = average_accounts_payable, |
| 281 | + %Decimal{} = total_credit_purchases, |
| 282 | + days |
| 283 | + ) when is_integer(days) |
| 284 | + do |
| 285 | + average_payment_period(average_accounts_payable, total_credit_purchases, D.new(days)) |
| 286 | + end |
| 287 | + |
| 288 | + def average_payment_period( |
| 289 | + %Decimal{} = average_accounts_payable, |
| 290 | + %Decimal{} = total_credit_purchases, |
| 291 | + days |
| 292 | + ) when is_float(days) |
| 293 | + do |
| 294 | + average_payment_period(average_accounts_payable, total_credit_purchases, D.from_float(days)) |
| 295 | + end |
| 296 | + |
| 297 | + def average_payment_period(_, _, %Decimal{coef: 0} = _), |
| 298 | + do: {:error, "days #{@zero_error}"} |
| 299 | + |
| 300 | + def average_payment_period( |
| 301 | + %Decimal{} = average_accounts_payable, |
| 302 | + %Decimal{} = total_credit_purchases, |
| 303 | + %Decimal{} = days |
| 304 | + ) do |
| 305 | + credit_days = D.div(total_credit_purchases, D.new(days)) |
| 306 | + {:ok, D.div(average_accounts_payable, credit_days)} |
| 307 | + end |
| 308 | + |
| 309 | + def average_payment_period(_, _, _), |
| 310 | + do: {:error, @arg_msg} |
| 311 | + |
| 312 | + |
| 313 | + @doc """ |
| 314 | + Break Even Analysis Calculation |
| 315 | + """ |
| 316 | + def break_even_analysis( |
| 317 | + %Decimal{} = fixed_costs, |
| 318 | + %Decimal{} = sales_price_per_unit, |
| 319 | + %Decimal{} = variable_cost_per_unit |
| 320 | + ) do |
| 321 | + price = D.sub(sales_price_per_unit, variable_cost_per_unit) |
| 322 | + cond do |
| 323 | + D.eq?(price, 0) -> {:error, "sales_price_per_unit - variable_cost_per_unit #{@zero_error}"} |
| 324 | + :else -> {:ok, D.div(fixed_costs, price)} |
| 325 | + end |
| 326 | + end |
| 327 | + |
| 328 | + def break_even_analysis(_, _, _), |
| 329 | + do: {:error, @arg_msg} |
| 330 | + |
| 331 | + |
| 332 | + @doc """ |
| 333 | + Capitalization Ratio Calculation |
| 334 | + """ |
| 313 335 | def capitalization_ratio(total_debt, shareholders_equity) |
| 314 | - when is_number(total_debt) |
| 315 | - and is_number(shareholders_equity) |
| 316 | - do |
| 317 | - {:ok, (Float.round(total_debt/(total_debt + shareholders_equity), @two_decimal_precision))} |
| 336 | + |
| 337 | + def capitalization_ratio(_, %Decimal{coef: 0} = _), |
| 338 | + do: {:error, "shareholders_equity #{@zero_error}"} |
| 339 | + |
| 340 | + def capitalization_ratio(%Decimal{} = total_debt, %Decimal{} = shareholders_equity), |
| 341 | + do: {:ok, D.div(total_debt, D.add(total_debt, shareholders_equity))} |
| 342 | + |
| 343 | + def capitalization_ratio(_, _), |
| 344 | + do: {:error, @arg_msg} |
| 345 | + |
| 346 | + |
| 347 | + @doc """ |
| 348 | + Cash Conversion Cycle Calculation |
| 349 | + """ |
| 350 | + def cash_conversion_cycle( |
| 351 | + %Decimal{} = days_inventory_outstanding, |
| 352 | + %Decimal{} = days_sales_outstanding, |
| 353 | + %Decimal{} = days_payables_outstanding |
| 354 | + ) do |
| 355 | + {:ok, D.add(days_inventory_outstanding, D.add(days_sales_outstanding, days_payables_outstanding))} |
| 318 356 | end |
| 319 | - def capitalization_ratio(_, _), do: {:error, "Arguments must be numerical"} |
| 320 357 | |
| 321 | - # TODO: days_inventory_outstanding |
| 322 | - # TODO: days_sales_outstanding |
| 323 | - # TODO: days_payables_outstanding |
| 324 | - |
| 325 | - ##-------------------------------------------------------------- |
| 326 | - ## Cash Conversion Cycle Calculation |
| 327 | - ## @param float -- days_inventory_outstanding |
| 328 | - ## @param float -- days_sales_outstanding |
| 329 | - ## @param float -- days_payables_outstanding |
| 330 | - ## @return float |
| 331 | - ##-------------------------------------------------------------- |
| 332 | - def cash_conversion_cycle(days_inventory_outstanding, days_sales_outstanding, days_payables_outstanding) |
| 333 | - when is_number(days_inventory_outstanding) |
| 334 | - and is_number(days_sales_outstanding) |
| 335 | - and is_number(days_payables_outstanding) |
| 336 | - do |
| 337 | - {:ok, (days_inventory_outstanding + days_sales_outstanding + days_payables_outstanding)} |
| 338 | - end |
| 339 | - def cash_conversion_cycle(_, _, _), do: {:error, "Arguments must be numerical"} |
| 358 | + def cash_conversion_cycle(_, _, _), |
| 359 | + do: {:error, @arg_msg} |
| 340 360 | |
| 341 361 | |
| 342 | - ##-------------------------------------------------------------- |
| 343 | - ## Cash Flow Coverage Ratio Calculation |
| 344 | - ## @param float -- operating_cash_flows |
| 345 | - ## @param float -- total_debt |
| 346 | - ## @return float |
| 347 | - ##-------------------------------------------------------------- |
| 348 | - def cash_flow_coverage(_, 0), do: {:error, "total_debt can't be zero (Divide by zero error)"} |
| 362 | + @doc """ |
| 363 | + Cash Flow Coverage Ratio Calculation |
| 364 | + """ |
| 349 365 | def cash_flow_coverage(operating_cash_flows, total_debt) |
| 350 | - when is_number(operating_cash_flows) |
| 351 | - and is_number(total_debt) |
| 352 | - do |
| 353 | - {:ok, (Float.round(operating_cash_flows/total_debt, @two_decimal_precision))} |
| 354 | - end |
| 355 | - def cash_flow_coverage(_, _), do: {:error, "Arguments must be numerical"} |
| 356 366 | |
| 357 | - ##-------------------------------------------------------------- |
| 358 | - ## Cash Ratio Calculation |
| 359 | - ## @param float -- cash |
| 360 | - ## @param float -- cash_equivalents |
| 361 | - ## @param float -- total_current_liabilities |
| 362 | - ## @return float |
| 363 | - ##-------------------------------------------------------------- |
| 364 | - def cash_ratio(_, _, 0), do: {:error, "cash_equivalents can't be zero (Divide by zero error)"} |
| 367 | + def cash_flow_coverage(_, %Decimal{coef: 0} = _), |
| 368 | + do: {:error, "total_debt #{@zero_error}"} |
| 369 | + |
| 370 | + def cash_flow_coverage(%Decimal{} = operating_cash_flows, %Decimal{} = total_debt) do |
| 371 | + {:ok, D.div(operating_cash_flows, total_debt)} |
| 372 | + end |
| 373 | + |
| 374 | + def cash_flow_coverage(_, _), |
| 375 | + do: {:error, @arg_msg} |
| 376 | + |
| 377 | + |
| 378 | + @doc """ |
| 379 | + Cash Ratio Calculation |
| 380 | + """ |
| 365 381 | def cash_ratio(cash, cash_equivalents, total_current_liabilities) |
| 366 | - when is_number(cash) |
| 367 | - and is_number(cash_equivalents) |
| 368 | - and is_number(total_current_liabilities) |
| 369 | - do |
| 370 | - {:ok, (Float.round((cash + cash_equivalents)/total_current_liabilities, @two_decimal_precision))} |
| 371 | - end |
| 372 | - def cash_ratio(_, _, _), do: {:error, "arguments must be numerical"} |
| 373 382 | |
| 374 | - ##-------------------------------------------------------------- |
| 375 | - ## Compound Annual Growth Rate Calculation |
| 376 | - ## @param float -- beginning_investment_value |
| 377 | - ## @param float -- ending_investment_value |
| 378 | - ## @param int -- years |
| 379 | - ## @return float |
| 380 | - ##-------------------------------------------------------------- |
| 381 | - def cagr(0, _, 0), do: {:error, "beginning_investment_amount and years can't be zero (Divide by zero error)"} |
| 382 | - def cagr(0, _, _), do: {:error, "beginning_investment_amount can't be zero (Divide by zero error)"} |
| 383 | - def cagr(_, _, 0), do: {:error, "years can't be zero (Divide by zero error)"} |
| 383 | + def cash_ratio(_, _, %Decimal{coef: 0} = _), |
| 384 | + do: {:error, "cash_equivalents #{@zero_error}"} |
| 385 | + |
| 386 | + def cash_ratio( |
| 387 | + %Decimal{} = cash, |
| 388 | + %Decimal{} = cash_equivalents, |
| 389 | + %Decimal{} = total_current_liabilities |
| 390 | + ) do |
| 391 | + {:ok, D.div(D.add(cash, cash_equivalents), total_current_liabilities)} |
| 392 | + end |
| 393 | + |
| 394 | + def cash_ratio(_, _, _), |
| 395 | + do: {:error, @arg_msg} |
| 396 | + |
| 397 | + @doc """ |
| 398 | + Compound Annual Growth Rate Calculation |
| 399 | + """ |
| 384 400 | def cagr(beginning_investment_value, ending_investment_value, years) |
| 385 | - when is_number(beginning_investment_value) |
| 386 | - and is_number(ending_investment_value) |
| 387 | - and is_number(years) |
| 388 | - do |
| 389 | - value_ratio = Float.round(ending_investment_value/beginning_investment_value, @two_decimal_precision) |
| 390 | - time_ratio = Float.round(1/years, @two_decimal_precision) |
| 391 | - {:ok, (:math.pow(value_ratio,time_ratio) - 1)} |
| 392 | - end |
| 393 | - def cagr(_, _, _), do: {:error, "Arguments must be numerical"} |
| 394 401 | |
| 395 | - ##-------------------------------------------------------------- |
| 396 | - ## Contribution Margin Calculation |
| 397 | - ## @param float -- net_sales |
| 398 | - ## @param float -- variable_costs |
| 399 | - ## @return float |
| 400 | - ##-------------------------------------------------------------- |
| 401 | - def contribution_margin(net_sales, variable_costs) |
| 402 | - when is_number(net_sales) |
| 403 | - and is_number(variable_costs) |
| 404 | - do |
| 405 | - {:ok, (net_sales - variable_costs)} |
| 406 | - end |
| 407 | - def contribution_margin(_, _), do: {:error, "Arguments must be numerical"} |
| 402 | + def cagr(%Decimal{coef: 0} = _, _, 0), |
| 403 | + do: {:error, "beginning_investment_amount & years #{@zero_error}"} |
| 408 404 | |
| 409 | - ##-------------------------------------------------------------- |
| 410 | - ## Current Ratio Calculation |
| 411 | - ## @param float -- current_assets |
| 412 | - ## @param float -- current_liabilities |
| 413 | - ## @return float |
| 414 | - ##-------------------------------------------------------------- |
| 415 | - def current_ratio(_, 0), do: {:error, "current_liabilities can't be zero (Divide by zero error)"} |
| 405 | + def cagr(%Decimal{coef: 0} = _, _, %Decimal{coef: 0} = _), |
| 406 | + do: {:error, "beginning_investment_amount & years #{@zero_error}"} |
| 407 | + |
| 408 | + def cagr(%Decimal{coef: 0} = _, _, _), |
| 409 | + do: {:error, "beginning_investment_amount #{@zero_error}"} |
| 410 | + |
| 411 | + def cagr( |
| 412 | + %Decimal{} = beginning_investment_value, |
| 413 | + %Decimal{} = ending_investment_value, |
| 414 | + years |
| 415 | + ) when is_integer(years) do |
| 416 | + cagr(beginning_investment_value, ending_investment_value, D.new(years)) |
| 417 | + end |
| 418 | + |
| 419 | + def cagr( |
| 420 | + %Decimal{} = beginning_investment_value, |
| 421 | + %Decimal{} = ending_investment_value, |
| 422 | + years |
| 423 | + ) when is_float(years) do |
| 424 | + cagr(beginning_investment_value, ending_investment_value, D.from_float(years)) |
| 425 | + end |
| 426 | + |
| 427 | + def cagr(_, _, %Decimal{coef: 0} = _), |
| 428 | + do: {:error, "years #{@zero_error}"} |
| 429 | + |
| 430 | + def cagr( |
| 431 | + %Decimal{} = beginning_investment_value, |
| 432 | + %Decimal{} = ending_investment_value, |
| 433 | + %Decimal{} = years |
| 434 | + ) |
| 435 | + do |
| 436 | + value_ratio = D.div(ending_investment_value, beginning_investment_value) |
| 437 | + time_ratio = D.div(D.new(1), years) |
| 438 | + res = :math.pow(D.to_float(value_ratio), D.to_float(time_ratio)) |
| 439 | + {:ok, D.sub(D.new(res), D.new(1))} |
| 440 | + end |
| 441 | + |
| 442 | + def cagr(_, _, _), |
| 443 | + do: {:error, @arg_msg} |
| 444 | + |
| 445 | + |
| 446 | + @doc """ |
| 447 | + Contribution Margin Calculation |
| 448 | + """ |
| 449 | + def contribution_margin(%Decimal{} = net_sales, %Decimal{} = variable_costs), |
| 450 | + do: {:ok, D.sub(net_sales, variable_costs)} |
| 451 | + |
| 452 | + def contribution_margin(_, _), |
| 453 | + do: {:error, @arg_msg} |
| 454 | + |
| 455 | + |
| 456 | + @doc """ |
| 457 | + Current Ratio Calculation |
| 458 | + """ |
| 416 459 | def current_ratio(current_assets, current_liabilities) |
| 417 | - when is_number(current_assets) |
| 418 | - and is_number(current_liabilities) |
| 419 | - do |
| 420 | - {:ok, (Float.round(current_assets/current_liabilities, @two_decimal_precision))} |
| 421 | - end |
| 422 | - def current_ratio(_, _), do: {:error, "Arguments must be numerical"} |
| 423 460 | |
| 424 | - ##-------------------------------------------------------------- |
| 425 | - ## Days Payable Outstanding Calculation |
| 426 | - ## @param float -- accounts_payable |
| 427 | - ## @param float -- cost_of_sales |
| 428 | - ## @param int -- days |
| 429 | - ## @return float |
| 430 | - ##-------------------------------------------------------------- |
| 431 | - def dpo(_, 0, 0), do: {:error, "cost_of_sales and days can't be zero (Divide by zero error)"} |
| 432 | - def dpo(_, 0, _), do: {:error, "cost_of_sales can't be zero (Divide by zero error)"} |
| 433 | - def dpo(_, _, 0), do: {:error, "days can't be zero (Divide by zero error)"} |
| 461 | + def current_ratio(_, %Decimal{coef: 0} = _), |
| 462 | + do: {:error, "current_liabilities #{@zero_error}"} |
| 463 | + |
| 464 | + def current_ratio(%Decimal{} = current_assets, %Decimal{} = current_liabilities), do: |
| 465 | + {:ok, D.div(current_assets, current_liabilities)} |
| 466 | + |
| 467 | + def current_ratio(_, _), |
| 468 | + do: {:error, @arg_msg} |
| 469 | + |
| 470 | + @doc """ |
| 471 | + Days Payable Outstanding Calculation |
| 472 | + """ |
| 434 473 | def dpo(accounts_payable, cost_of_sales, days) |
| 435 | - when is_number(accounts_payable) |
| 436 | - and is_number(cost_of_sales) |
| 437 | - and is_number(days) |
| 438 | - do |
| 439 | - cost_of_sales_per_day = Float.round(cost_of_sales/days, @two_decimal_precision) |
| 440 | - {:ok, (Float.round(accounts_payable/cost_of_sales_per_day, @two_decimal_precision))} |
| 441 | - end |
| 442 | - def dpo(_, _, _), do: {:error, "Arguments must be numerical"} |
| 443 474 | |
| 444 | - ##-------------------------------------------------------------- |
| 445 | - ## Days Sales in Inventory Calculation |
| 446 | - ## @param float -- ending_inventory |
| 447 | - ## @param float -- cogs |
| 448 | - ## @return float |
| 449 | - ##-------------------------------------------------------------- |
| 450 | - def dsi(_, 0), do: {:error, "cogs can't be zero (Divide by zero error)"} |
| 475 | + def dpo(_, %Decimal{coef: 0} = _, %Decimal{coef: 0} = _), |
| 476 | + do: {:error, "cost_of_sales & days #{@zero_error}"} |
| 477 | + |
| 478 | + def dpo(_, %Decimal{coef: 0} = _, _), |
| 479 | + do: {:error, "cost_of_sales #{@zero_error}"} |
| 480 | + |
| 481 | + def dpo(%Decimal{} = accounts_payable, %Decimal{} = cost_of_sales, days) when is_integer(days), |
| 482 | + do: dpo(accounts_payable, cost_of_sales, D.new(days)) |
| 483 | + |
| 484 | + def dpo(%Decimal{} = accounts_payable, %Decimal{} = cost_of_sales, days) when is_float(days), |
| 485 | + do: dpo(accounts_payable, cost_of_sales, D.from_float(days)) |
| 486 | + |
| 487 | + def dpo(_, _, %Decimal{coef: 0} = _), |
| 488 | + do: {:error, "days #{@zero_error}"} |
| 489 | + |
| 490 | + def dpo(%Decimal{} = accounts_payable, %Decimal{} = cost_of_sales, %Decimal{} = days) do |
| 491 | + cost_of_sales_per_day = D.div(cost_of_sales, days) |
| 492 | + {:ok, D.div(accounts_payable, cost_of_sales_per_day)} |
| 493 | + end |
| 494 | + |
| 495 | + def dpo(_, _, _), |
| 496 | + do: {:error, @arg_msg} |
| 497 | + |
| 498 | + @doc """ |
| 499 | + Days Sales in Inventory Calculation |
| 500 | + """ |
| 451 501 | def dsi(ending_inventory, cogs) |
| 452 | - when is_number(ending_inventory) |
| 453 | - and is_number(cogs) |
| 454 | - do |
| 455 | - {:ok, (Float.round(ending_inventory/cogs, @two_decimal_precision) * 365)} |
| 456 | - end |
| 457 | - def dsi(_, _), do: {:error, "Arguments must be numerical"} |
| 458 502 | |
| 459 | - ##-------------------------------------------------------------- |
| 460 | - ## Days Sales Outstanding Calculation |
| 461 | - ## @param float -- accounts_receivable |
| 462 | - ## @param float -- net_credit_sales |
| 463 | - ## @return float |
| 464 | - ##-------------------------------------------------------------- |
| 465 | - def dso(_, 0), do: {:error, "net_credit_sales can't be zero (Divide by zero error)"} |
| 503 | + def dsi(_, %Decimal{coef: 0} = _), |
| 504 | + do: {:error, "cogs #{@zero_error}"} |
| 505 | + |
| 506 | + def dsi(%Decimal{} = ending_inventory, %Decimal{} = cogs), |
| 507 | + do: {:ok, D.mult(D.div(ending_inventory,cogs), 365)} |
| 508 | + |
| 509 | + def dsi(_, _), |
| 510 | + do: {:error, @arg_msg} |
| 511 | + |
| 512 | + |
| 513 | + @doc """ |
| 514 | + Days Sales Outstanding Calculation |
| 515 | + """ |
| 466 516 | def dso(accounts_receivable, net_credit_sales) |
| 467 | - when is_number(accounts_receivable) |
| 468 | - and is_number(net_credit_sales) |
| 469 | - do |
| 470 | - {:ok, (Float.round(accounts_receivable/net_credit_sales, @two_decimal_precision) * 365)} |
| 471 | - end |
| 472 | - def dso(_, _), do: {:error, "Arguments must be numerical"} |
| 517 | + |
| 518 | + def dso(_, %Decimal{coef: 0} = _), |
| 519 | + do: {:error, "net_credit_sales #{@zero_error}"} |
| 520 | + |
| 521 | + def dso(%Decimal{} = accounts_receivable, %Decimal{} = net_credit_sales), do: |
| 522 | + {:ok, D.mult(D.div(accounts_receivable, net_credit_sales), 365)} |
| 523 | + |
| 524 | + def dso(_, _), |
| 525 | + do: {:error, @arg_msg} |
| 473 526 | |
| 474 527 | |
| 475 | - ##-------------------------------------------------------------- |
| 476 | - ## Debt Ratio Calculation |
| 477 | - ## @param float -- total_liabilities |
| 478 | - ## @param float -- total_assets |
| 479 | - ## @return float |
| 480 | - ##-------------------------------------------------------------- |
| 481 | - def debt_ratio(_, 0), do: {:error, "total_assets can't be zero (Divide by zero error)"} |
| 528 | + @doc """ |
| 529 | + Debt Ratio Calculation |
| 530 | + """ |
| 482 531 | def debt_ratio(total_liabilities, total_assets) |
| 483 | - when is_number(total_liabilities) |
| 484 | - and is_number(total_assets) |
| 485 | - do |
| 486 | - {:ok, (Float.round(total_liabilities/total_assets, @two_decimal_precision))} |
| 487 | - end |
| 488 | - def debt_ratio(_, _), do: {:error, "Arguments must be numerical"} |
| 489 532 | |
| 490 | - ##-------------------------------------------------------------- |
| 491 | - ## Debt Service Coverage Ratio |
| 492 | - ## @param float -- operating_income |
| 493 | - ## @param float -- total_debt_service_costs |
| 494 | - ## @return float |
| 495 | - ##-------------------------------------------------------------- |
| 496 | - def dscr(_, 0), do: {:error, "total_debt_service_costs can't be zero (Divide by zero error)"} |
| 533 | + def debt_ratio(_, %Decimal{coef: 0} = _), |
| 534 | + do: {:error, "total_assets #{@zero_error}"} |
| 535 | + |
| 536 | + def debt_ratio(%Decimal{} = total_liabilities, %Decimal{} = total_assets), |
| 537 | + do: {:ok, D.div(total_liabilities, total_assets)} |
| 538 | + |
| 539 | + def debt_ratio(_, _), |
| 540 | + do: {:error, @arg_msg} |
| 541 | + |
| 542 | + |
| 543 | + @doc """ |
| 544 | + Debt Service Coverage Ratio |
| 545 | + """ |
| 497 546 | def dscr(operating_income, total_debt_service_costs) |
| 498 | - when is_number(operating_income) |
| 499 | - and is_number(total_debt_service_costs) |
| 500 | - do |
| 501 | - {:ok, (Float.round(operating_income/total_debt_service_costs, @two_decimal_precision))} |
| 502 | - end |
| 503 | - def dscr(_, _), do: {:error, "Arguments must be numerical"} |
| 504 547 | |
| 505 | - ##-------------------------------------------------------------- |
| 506 | - ## Debt to Asset Ratio Calculation |
| 507 | - ## @param float -- total_debt |
| 508 | - ## @param float -- total_assets |
| 509 | - ## @return float |
| 510 | - ##-------------------------------------------------------------- |
| 511 | - def debt_to_asset(_, 0), do: {:error, "total_assets can't be zero (Divide by zero error)"} |
| 548 | + def dscr(_, %Decimal{coef: 0} = _), |
| 549 | + do: {:error, "total_debt_service_costs #{@zero_error}"} |
| 550 | + |
| 551 | + def dscr(%Decimal{} = operating_income, %Decimal{} = total_debt_service_costs), |
| 552 | + do: {:ok, (Float.round(operating_income/total_debt_service_costs, @two_decimal_precision))} |
| 553 | + |
| 554 | + def dscr(_, _), |
| 555 | + do: {:error, @arg_msg} |
| 556 | + |
| 557 | + |
| 558 | + @doc """ |
| 559 | + Debt to Asset Ratio Calculation |
| 560 | + """ |
| 512 561 | def debt_to_asset(total_debt, total_assets) |
| 513 | - when is_number(total_debt) |
| 514 | - and is_number(total_assets) |
| 515 | - do |
| 516 | - {:ok, (Float.round(total_debt/total_assets, @two_decimal_precision))} |
| 517 | - end |
| 518 | - def debt_to_asset(_, _), do: {:error, "Arguments must be numerical"} |
| 519 562 | |
| 520 | - ##-------------------------------------------------------------- |
| 521 | - ## Debt ot Capital Ratio Calculation |
| 522 | - ## @param float -- total_debt |
| 523 | - ## @param float -- shareholders_equity |
| 524 | - ## @return float |
| 525 | - ##-------------------------------------------------------------- |
| 526 | - def debt_to_capital(_, 0), do: {:error, "shareholders_equity can't be zero (Divide by zero error)"} |
| 563 | + def debt_to_asset(_, %Decimal{coef: 0} = _), |
| 564 | + do: {:error, "total_assets #{@zero_error}"} |
| 565 | + |
| 566 | + def debt_to_asset(%Decimal{} = total_debt, %Decimal{} = total_assets), |
| 567 | + do: {:ok, D.div(total_debt, total_assets)} |
| 568 | + |
| 569 | + def debt_to_asset(_, _), |
| 570 | + do: {:error, @arg_msg} |
| 571 | + |
| 572 | + @doc """ |
| 573 | + Debt ot Capital Ratio Calculation |
| 574 | + """ |
| 527 575 | def debt_to_capital(total_debt, shareholders_equity) |
| 528 | - when is_number(total_debt) |
| 529 | - and is_number(shareholders_equity) |
| 530 | - do |
| 531 | - {:ok, (Float.round(total_debt/(total_debt + shareholders_equity), @two_decimal_precision))} |
| 532 | - end |
| 533 | - def debt_to_capital(_, _), do: {:error, "Arguments must be numerical"} |
| 534 576 | |
| 535 | - ##-------------------------------------------------------------- |
| 536 | - ## Debt to Equity Ratio Calculation |
| 537 | - ## @param float -- total_liabilities |
| 538 | - ## @param float -- total_equity |
| 539 | - ## @return float |
| 540 | - ##-------------------------------------------------------------- |
| 541 | - def debt_to_equity(_, 0), do: {:error, "total_equity can't be zero (Divide by zero error)"} |
| 577 | + def debt_to_capital(_, %Decimal{coef: 0} = _), |
| 578 | + do: {:error, "shareholders_equity #{@zero_error}"} |
| 579 | + |
| 580 | + def debt_to_capital(%Decimal{} = total_debt, %Decimal{} = shareholders_equity), |
| 581 | + do: {:ok, D.div(total_debt, D.add(total_debt, shareholders_equity))} |
| 582 | + |
| 583 | + def debt_to_capital(_, _), |
| 584 | + do: {:error, @arg_msg} |
| 585 | + |
| 586 | + |
| 587 | + @doc """ |
| 588 | + Debt to Equity Ratio Calculation |
| 589 | + """ |
| 542 590 | def debt_to_equity(total_liabilities, total_equity) |
| 543 | - when is_number(total_liabilities) |
| 544 | - and is_number(total_equity) |
| 545 | - do |
| 546 | - {:ok, (Float.round(total_liabilities/total_equity, @two_decimal_precision))} |
| 547 | - end |
| 548 | - def debt_to_equity(_, _), do: {:error, "Arguments must be numerical"} |
| 549 591 | |
| 550 | - ##-------------------------------------------------------------- |
| 551 | - ## Debt to Income Ratio Calculation |
| 552 | - ## @param float -- total_monthly_debt_payments |
| 553 | - ## @param float -- gross_monthly_income |
| 554 | - ## @return float |
| 555 | - ##-------------------------------------------------------------- |
| 556 | - def dti(_, 0), do: {:error, "gross_monthly_income can't be zero (Divide by zero error)"} |
| 592 | + def debt_to_equity(_, %Decimal{coef: 0} = _), |
| 593 | + do: {:error, "total_equity #{@zero_error}"} |
| 594 | + |
| 595 | + def debt_to_equity(%Decimal{} = total_liabilities, %Decimal{} = total_equity), |
| 596 | + do: {:ok, D.div(total_liabilities, total_equity)} |
| 597 | + |
| 598 | + def debt_to_equity(_, _), |
| 599 | + do: {:error, @arg_msg} |
| 600 | + |
| 601 | + |
| 602 | + @doc """ |
| 603 | + Debt to Income Ratio Calculation |
| 604 | + """ |
| 557 605 | def dti(total_monthly_debt_payments, gross_monthly_income) |
| 558 | - when is_number(total_monthly_debt_payments) |
| 559 | - and is_number(gross_monthly_income) |
| 560 | - do |
| 561 | - {:ok, (Float.round(total_monthly_debt_payments/gross_monthly_income, @two_decimal_precision))} |
| 562 | - end |
| 563 | - def dti(_, _), do: {:error, "Arguments must be numerical"} |
| 564 606 | |
| 565 | - ##-------------------------------------------------------------- |
| 566 | - ## Defensive Interval Ratio Calculation |
| 567 | - ## @param float -- defensive_assets |
| 568 | - ## @param float -- daily_operational_expenses |
| 569 | - ## @return float |
| 570 | - ##-------------------------------------------------------------- |
| 571 | - def dir(_, 0), do: {:error, "daily_operational_expenses can't be zero (Divide by zero error)"} |
| 607 | + def dti(_, %Decimal{coef: 0} = _), |
| 608 | + do: {:error, "gross_monthly_income #{@zero_error}"} |
| 609 | + |
| 610 | + def dti(%Decimal{} = total_monthly_debt_payments, %Decimal{} = gross_monthly_income), |
| 611 | + do: {:ok, D.div(total_monthly_debt_payments, gross_monthly_income)} |
| 612 | + |
| 613 | + def dti(_, _), |
| 614 | + do: {:error, @arg_msg} |
| 615 | + |
| 616 | + |
| 617 | + @doc """ |
| 618 | + Defensive Interval Ratio Calculation |
| 619 | + """ |
| 572 620 | def dir(defensive_assets, daily_operational_expenses) |
| 573 | - when is_number(defensive_assets) |
| 574 | - and is_number(daily_operational_expenses) |
| 575 | - do |
| 576 | - {:ok, (Float.round(defensive_assets/daily_operational_expenses, @two_decimal_precision))} |
| 577 | - end |
| 578 | - def dir(_, _), do: {:error, "Arguments must be numerical"} |
| 579 621 | |
| 580 | - ##-------------------------------------------------------------- |
| 581 | - ## Basic Earnings Per Share Calculation |
| 582 | - ## @param float -- earnings |
| 583 | - ## @param float -- shares_outstanding |
| 584 | - ## @return float |
| 585 | - ##-------------------------------------------------------------- |
| 586 | - def eps_basic(_, 0), do: {:error, "shares_outstanding can't be zero (Divide ny zero error)"} |
| 622 | + def dir(_, %Decimal{coef: 0} = _), |
| 623 | + do: {:error, "daily_operational_expenses #{@zero_error}"} |
| 624 | + |
| 625 | + def dir(%Decimal{} = defensive_assets, %Decimal{} = daily_operational_expenses), |
| 626 | + do: {:ok, D.div(defensive_assets, daily_operational_expenses)} |
| 627 | + |
| 628 | + def dir(_, _), |
| 629 | + do: {:error, @arg_msg} |
| 630 | + |
| 631 | + |
| 632 | + @doc """ |
| 633 | + Basic Earnings Per Share Calculation |
| 634 | + """ |
| 587 635 | def eps_basic(earnings, shares_outstanding) |
| 588 | - when is_number(earnings) |
| 589 | - and is_number(shares_outstanding) |
| 590 | - do |
| 591 | - {:ok, (Float.round(earnings/shares_outstanding, @two_decimal_precision))} |
| 636 | + |
| 637 | + def eps_basic(_, %Decimal{coef: 0} = _), |
| 638 | + do: {:error, "shares_outstanding #{@zero_error}"} |
| 639 | + |
| 640 | + def eps_basic(%Decimal{} = earnings, %Decimal{} = shares_outstanding), |
| 641 | + do: {:ok, D.div(earnings, shares_outstanding)} |
| 642 | + |
| 643 | + def eps_basic(_, _), |
| 644 | + do: {:error, @arg_msg} |
| 645 | + |
| 646 | + |
| 647 | + @doc """ |
| 648 | + Diluted Earnings Per Share Calculation |
| 649 | + """ |
| 650 | + def eps_diluted(%Decimal{} = earnings, %Decimal{} = shares_outstanding, %Decimal{} = diluted_shares) do |
| 651 | + if D.lt?(shares_outstanding, 1) or D.lt?(diluted_shares, 0) do |
| 652 | + {:error, "shares #{@zero_error}"} |
| 653 | + else |
| 654 | + shares = D.add(shares_outstanding, diluted_shares) |
| 655 | + {:ok, D.div(earnings, shares)} |
| 656 | + end |
| 592 657 | end |
| 593 658 | |
| 594 | - def eps_basic(_, _), do: {:error, "Arguments must be numerical"} |
| 659 | + def eps_diluted(_, _, _), |
| 660 | + do: {:error, @arg_msg} |
| 595 661 | |
| 596 | - ##-------------------------------------------------------------- |
| 597 | - ## Diluted Earnings Per Share Calculation |
| 598 | - ## @param float -- earnings |
| 599 | - ## @param float -- shares_outstanding |
| 600 | - ## @param float -- diluted_shares |
| 601 | - ## @return float |
| 602 | - ##-------------------------------------------------------------- |
| 603 | - def eps_diluted(_, shares_outstanding, diluted_shares) |
| 604 | - when shares_outstanding <= 0 |
| 605 | - or diluted_shares < 0, |
| 606 | - do: {:error, "shares can't be zero (Divide by zero error)"} |
| 607 | - def eps_diluted(earnings, shares_outstanding, diluted_shares) |
| 608 | - when is_number(earnings) |
| 609 | - and is_number(shares_outstanding) |
| 610 | - and is_number(diluted_shares) |
| 611 | - do |
| 612 | - shares = (shares_outstanding + diluted_shares) |
| 613 | - {:ok, (Float.round(earnings/shares, @two_decimal_precision))} |
| 662 | + |
| 663 | + @doc """ |
| 664 | + Pro Forma Earnings Per Share Calculation |
| 665 | + """ |
| 666 | + def eps_pro_forma( |
| 667 | + %Decimal{} = acquirers_net_income, |
| 668 | + %Decimal{} = targets_net_income, |
| 669 | + %Decimal{} = incremental_adjustments, |
| 670 | + %Decimal{} = shares_outstanding, |
| 671 | + %Decimal{} = diluted_shares |
| 672 | + ) do |
| 673 | + if D.lt?(shares_outstanding, 1) or D.lt?(diluted_shares, 0) do |
| 674 | + {:error, "shares #{@zero_error}"} |
| 675 | + else |
| 676 | + earnings = D.add(acquirers_net_income, D.add(targets_net_income, incremental_adjustments)) |
| 677 | + shares = D.add(shares_outstanding, diluted_shares) |
| 678 | + {:ok, D.div(earnings, shares)} |
| 679 | + end |
| 614 680 | end |
| 615 | - def eps_diluted(_, _, _), do: {:error, "Arguments must be numerical"} |
| 616 681 | |
| 617 | - ##-------------------------------------------------------------- |
| 618 | - ## Pro Forma Earnings Per Share Calculation |
| 619 | - ## @param float -- acquirers_net_income |
| 620 | - ## @param float -- targets_net_income |
| 621 | - ## @param float -- incremental_adjustments |
| 622 | - ## @param float -- shares_outstanding |
| 623 | - ## @param float -- diluted_shares |
| 624 | - ## @return float |
| 625 | - ##-------------------------------------------------------------- |
| 626 | - def eps_pro_forma(_, _, _, shares_outstanding, diluted_shares) |
| 627 | - when shares_outstanding <= 0 |
| 628 | - or diluted_shares < 0, |
| 629 | - do: {:error, "shares can't be zero (Divide by zero error)"} |
| 630 | - def eps_pro_forma(acquirers_net_income, targets_net_income, incremental_adjustments, shares_outstanding, diluted_shares) |
| 631 | - when is_number(acquirers_net_income) |
| 632 | - and is_number(targets_net_income) |
| 633 | - and is_number(incremental_adjustments) |
| 634 | - and is_number(shares_outstanding) |
| 635 | - and is_number(diluted_shares) |
| 636 | - do |
| 637 | - earnings = (acquirers_net_income + targets_net_income + incremental_adjustments) |
| 638 | - shares = (shares_outstanding + diluted_shares) |
| 639 | - {:ok, (Float.round(earnings/shares, @two_decimal_precision))} |
| 640 | - end |
| 641 | - def eps_pro_forma(_, _, _, _, _), do: {:error, "Arguments must be numerical"} |
| 682 | + def eps_pro_forma(_, _, _, _, _), |
| 683 | + do: {:error, @arg_msg} |
| 642 684 | |
| 643 | - ##-------------------------------------------------------------- |
| 644 | - ## Book Value Earnings Per Share Calculation |
| 645 | - ## @param float -- total_equity |
| 646 | - ## @param float -- preferred_equity |
| 647 | - ## @param float -- shares_outstanding |
| 648 | - ## @return float |
| 649 | - ##-------------------------------------------------------------- |
| 650 | - def eps_book_value(_, _, 0), do: {:error, "shares_outstanding can't be zero (Divide by zero error)"} |
| 685 | + |
| 686 | + @doc """ |
| 687 | + Book Value Earnings Per Share Calculation |
| 688 | + """ |
| 651 689 | def eps_book_value(total_equity, preferred_equity, shares_outstanding) |
| 652 | - when is_number(total_equity) |
| 653 | - and is_number(preferred_equity) |
| 654 | - and is_number(shares_outstanding) |
| 655 | - do |
| 656 | - {:ok, (Float.round((total_equity - preferred_equity)/shares_outstanding, @two_decimal_precision))} |
| 657 | - end |
| 658 | - def eps_book_value(_, _, _), do: {:error, "Arguments must be numerical"} |
| 659 690 | |
| 660 | - ##-------------------------------------------------------------- |
| 661 | - ## Retained Earnings Per Share Calculation |
| 662 | - ## @param float -- retained_earnings |
| 663 | - ## @param float -- shares_outstanding |
| 664 | - ## @return float |
| 665 | - ##-------------------------------------------------------------- |
| 666 | - def eps_retained(_, 0), do: {:error, "shares_outstanding can't be zero (Divide by zero error)"} |
| 691 | + def eps_book_value(_, _, %Decimal{coef: 0} = _), |
| 692 | + do: {:error, "shares_outstanding #{@zero_error}"} |
| 693 | + |
| 694 | + def eps_book_value(%Decimal{} = total_equity, %Decimal{} = preferred_equity, %Decimal{} = shares_outstanding), |
| 695 | + do: {:ok, D.div(D.sub(total_equity, preferred_equity), shares_outstanding)} |
| 696 | + |
| 697 | + def eps_book_value(_, _, _), |
| 698 | + do: {:error, @arg_msg} |
| 699 | + |
| 700 | + |
| 701 | + @doc """ |
| 702 | + Retained Earnings Per Share Calculation |
| 703 | + """ |
| 667 704 | def eps_retained(retained_earnings, shares_outstanding) |
| 668 | - when is_number(retained_earnings) |
| 669 | - and is_number(shares_outstanding) |
| 670 | - do |
| 671 | - {:ok, (Float.round(retained_earnings/shares_outstanding, @two_decimal_precision))} |
| 672 | - end |
| 673 | - def eps_retained(_, _), do: {:error, "Arguments must be numerical"} |
| 674 705 | |
| 675 | - ##-------------------------------------------------------------- |
| 676 | - ## Cash Earnings Per Share Calculation |
| 677 | - ## @param float -- operating_cash_flow |
| 678 | - ## @param float -- shares_outstanding |
| 679 | - ## @return float |
| 680 | - ##-------------------------------------------------------------- |
| 681 | - def eps_cash(_, 0), do: {:error, "shares_outstanding can't be zero (Divide by zero error)"} |
| 706 | + def eps_retained(_, %Decimal{coef: 0} = _), |
| 707 | + do: {:error, "shares_outstanding #{@zero_error}"} |
| 708 | + |
| 709 | + def eps_retained(%Decimal{} = retained_earnings, %Decimal{} = shares_outstanding), |
| 710 | + do: {:ok, D.div(retained_earnings, shares_outstanding)} |
| 711 | + |
| 712 | + def eps_retained(_, _), |
| 713 | + do: {:error, @arg_msg} |
| 714 | + |
| 715 | + |
| 716 | + @doc """ |
| 717 | + Cash Earnings Per Share Calculation |
| 718 | + """ |
| 682 719 | def eps_cash(operating_cash_flow, shares_outstanding) |
| 683 | - when is_number(operating_cash_flow) |
| 684 | - and is_number(shares_outstanding) |
| 685 | - do |
| 686 | - {:ok, (Float.round(operating_cash_flow/shares_outstanding, @two_decimal_precision))} |
| 687 | - end |
| 688 | - def eps_cash(_, _), do: {:error, "Arguments must be numerical"} |
| 689 720 | |
| 690 | - ##-------------------------------------------------------------- |
| 691 | - ## Price to Earnings Ratio Calculation |
| 692 | - ## @param float -- price |
| 693 | - ## @param float -- earnings_per_share |
| 694 | - ## @return float |
| 695 | - ##-------------------------------------------------------------- |
| 696 | - def pe_ratio(_, 0), do: {:error, "earnings_per_share can't be zero (Divide by zero error)"} |
| 721 | + def eps_cash(_, %Decimal{coef: 0} = _), |
| 722 | + do: {:error, "shares_outstanding #{@zero_error}"} |
| 723 | + |
| 724 | + def eps_cash(%Decimal{} = operating_cash_flow, %Decimal{} = shares_outstanding), |
| 725 | + do: {:ok, D.div(operating_cash_flow, shares_outstanding)} |
| 726 | + |
| 727 | + def eps_cash(_, _), |
| 728 | + do: {:error, @arg_msg} |
| 729 | + |
| 730 | + |
| 731 | + @doc """ |
| 732 | + Price to Earnings Ratio Calculation |
| 733 | + """ |
| 697 734 | def pe_ratio(price, earnings_per_share) |
| 698 | - when is_number(price) |
| 699 | - and is_number(earnings_per_share) |
| 700 | - do |
| 701 | - {:ok, (Float.round(price/earnings_per_share, @two_decimal_precision))} |
| 702 | - end |
| 703 | - def pe_ratio(_, _), do: {:error, "Arguments must be numerical"} |
| 704 735 | |
| 705 | - ##-------------------------------------------------------------- |
| 706 | - ## Price to Earnings to Growth Ratio Calculation |
| 707 | - ## @param float -- price_to_earnings |
| 708 | - ## @param float -- earnings_growth |
| 709 | - ## @return float |
| 710 | - ##-------------------------------------------------------------- |
| 711 | - def peg_ratio(_, 0), do: {:error, "earnings_growth can't be zero (Divide by zero error)"} |
| 736 | + def pe_ratio(_, %Decimal{coef: 0} = _), |
| 737 | + do: {:error, "earnings_per_share #{@zero_error}"} |
| 738 | + |
| 739 | + def pe_ratio(%Decimal{} = price, %Decimal{} = earnings_per_share), do: |
| 740 | + {:ok, D.div(price, earnings_per_share)} |
| 741 | + |
| 742 | + def pe_ratio(_, _), |
| 743 | + do: {:error, @arg_msg} |
| 744 | + |
| 745 | + |
| 746 | + @doc """ |
| 747 | + Price to Earnings to Growth Ratio Calculation |
| 748 | + """ |
| 712 749 | def peg_ratio(price_to_earnings, earnings_growth) |
| 713 | - when is_number(price_to_earnings) |
| 714 | - and is_number(earnings_growth) |
| 715 | - do |
| 716 | - {:ok, (Float.round(price_to_earnings/earnings_growth, @two_decimal_precision))} |
| 717 | - end |
| 718 | - def peg_ratio(_, _), do: {:error, "Arguments must be numerical"} |
| 719 750 | |
| 720 | - ##-------------------------------------------------------------- |
| 721 | - ## Dividend Payout Calculation |
| 722 | - ## @param float -- net_dividends |
| 723 | - ## @param float -- net_income |
| 724 | - ## @return float |
| 725 | - ##-------------------------------------------------------------- |
| 726 | - def dividend_payout(_, 0), do: {:error, "net_income can't be zero (Divide by zero error)"} |
| 751 | + def peg_ratio(_, %Decimal{coef: 0} = _), |
| 752 | + do: {:error, "earnings_growth #{@zero_error}"} |
| 753 | + |
| 754 | + def peg_ratio(%Decimal{} = price_to_earnings, %Decimal{} = earnings_growth), |
| 755 | + do: {:ok, D.div(price_to_earnings, earnings_growth)} |
| 756 | + |
| 757 | + def peg_ratio(_, _), |
| 758 | + do: {:error, @arg_msg} |
| 759 | + |
| 760 | + |
| 761 | + @doc """ |
| 762 | + Dividend Payout Calculation |
| 763 | + """ |
| 727 764 | def dividend_payout(net_dividends, net_income) |
| 728 | - when is_number(net_dividends) |
| 729 | - and is_number(net_income) |
| 730 | - do |
| 731 | - {:ok, (Float.round(net_dividends/net_income, @two_decimal_precision))} |
| 732 | - end |
| 733 | - def dividend_payout(_, _), do: {:error, "Arguments must be numerical"} |
| 734 765 | |
| 735 | - ##-------------------------------------------------------------- |
| 736 | - ## Dividend Yield Calculation |
| 737 | - ## @param float -- cash_dividends_per_share |
| 738 | - ## @param float -- market_value_per_share |
| 739 | - ## @return float |
| 740 | - ##-------------------------------------------------------------- |
| 741 | - def dividend_yield(_, 0), do: {:error, "market_value_per_share can't be zero (Divide by zero error)"} |
| 766 | + def dividend_payout(_, %Decimal{coef: 0} = _), |
| 767 | + do: {:error, "net_income #{@zero_error}"} |
| 768 | + |
| 769 | + def dividend_payout(%Decimal{} = net_dividends, %Decimal{} = net_income), |
| 770 | + do: {:ok, D.div(net_dividends, net_income)} |
| 771 | + |
| 772 | + def dividend_payout(_, _), |
| 773 | + do: {:error, @arg_msg} |
| 774 | + |
| 775 | + |
| 776 | + @doc """ |
| 777 | + Dividend Yield Calculation |
| 778 | + """ |
| 742 779 | def dividend_yield(cash_dividends_per_share, market_value_per_share) |
| 743 | - when is_number(cash_dividends_per_share) |
| 744 | - and is_number(market_value_per_share) |
| 745 | - do |
| 746 | - {:ok, (Float.round(cash_dividends_per_share/market_value_per_share, @two_decimal_precision))} |
| 747 | - end |
| 748 | - def dividend_yield(_, _), do: {:error, "Arguments must be numerical"} |
| 749 780 | |
| 750 | - ##-------------------------------------------------------------- |
| 751 | - ## DuPont Analysis Calculation |
| 752 | - ## @param float -- profit_margin |
| 753 | - ## @param float -- total_asset_turnover |
| 754 | - ## @param float -- financial_leverage |
| 755 | - ## @return float |
| 756 | - ##-------------------------------------------------------------- |
| 757 | - def du_pont_analysis(profit_margin, total_asset_turnover, financial_leverage) |
| 758 | - when is_number(profit_margin) |
| 759 | - and is_number(total_asset_turnover) |
| 760 | - and is_number(financial_leverage) |
| 761 | - do |
| 762 | - {:ok, (profit_margin * total_asset_turnover * financial_leverage)} |
| 763 | - end |
| 764 | - def du_pont_analysis(_, _, _), do: {:error, "Arguments must be numerical"} |
| 781 | + def dividend_yield(_, %Decimal{coef: 0} = _), |
| 782 | + do: {:error, "market_value_per_share #{@zero_error}"} |
| 765 783 | |
| 766 | - ##-------------------------------------------------------------- |
| 767 | - ## Enterprise Value Calculation |
| 768 | - ## @param float -- market_capitalization |
| 769 | - ## @param float -- debt |
| 770 | - ## @param float -- current_cash |
| 771 | - ## @return float |
| 772 | - ##-------------------------------------------------------------- |
| 773 | - def ev(market_capitalization, debt, current_cash) |
| 774 | - when is_number(market_capitalization) |
| 775 | - and is_number(debt) |
| 776 | - and is_number(current_cash) |
| 777 | - do |
| 778 | - {:ok, (market_capitalization + debt - current_cash)} |
| 779 | - end |
| 780 | - def ev(_, _, _), do: {:error, "Arguments must be numerical"} |
| 784 | + def dividend_yield(%Decimal{} = cash_dividends_per_share, %Decimal{} = market_value_per_share), |
| 785 | + do: {:ok, D.div(cash_dividends_per_share, market_value_per_share)} |
| 781 786 | |
| 782 | - ##-------------------------------------------------------------- |
| 783 | - ## Equity Multiplier Calculation |
| 784 | - ## @param float -- total_assets |
| 785 | - ## @param float -- total_stockholders_equity |
| 786 | - ## @return float |
| 787 | - ##-------------------------------------------------------------- |
| 788 | - def equity_multiplier(_, 0), do: {:error, "total_stockholders_equity can't be zero (Divide by zero error)"} |
| 787 | + def dividend_yield(_, _), |
| 788 | + do: {:error, @arg_msg} |
| 789 | + |
| 790 | + |
| 791 | + @doc """ |
| 792 | + DuPont Analysis Calculation |
| 793 | + """ |
| 794 | + def du_pont_analysis(%Decimal{} = profit_margin, %Decimal{} = total_asset_turnover, %Decimal{} = financial_leverage), |
| 795 | + do: {:ok, D.mult(profit_margin, D.mult(total_asset_turnover, financial_leverage))} |
| 796 | + |
| 797 | + def du_pont_analysis(_, _, _), |
| 798 | + do: {:error, @arg_msg} |
| 799 | + |
| 800 | + |
| 801 | + @doc """ |
| 802 | + Enterprise Value Calculation |
| 803 | + """ |
| 804 | + def ev(%Decimal{} = market_capitalization, %Decimal{} = debt, %Decimal{} = current_cash), |
| 805 | + do: {:ok, D.sub(D.add(market_capitalization, debt), current_cash)} |
| 806 | + |
| 807 | + def ev(_, _, _), |
| 808 | + do: {:error, @arg_msg} |
| 809 | + |
| 810 | + |
| 811 | + @doc """ |
| 812 | + Equity Multiplier Calculation |
| 813 | + """ |
| 789 814 | def equity_multiplier(total_assets, total_stockholders_equity) |
| 790 | - when is_number(total_assets) |
| 791 | - and is_number(total_stockholders_equity) |
| 792 | - do |
| 793 | - {:ok, (Float.round(total_assets/total_stockholders_equity, @two_decimal_precision))} |
| 794 | - end |
| 795 | - def equity_multiplier(_, _), do: {:error, "Arguments must be numerical"} |
| 796 815 | |
| 797 | - ##-------------------------------------------------------------- |
| 798 | - ## Equity Ratio Calculation |
| 799 | - ## @param float -- total_equity |
| 800 | - ## @param float -- total_assets |
| 801 | - ## @return float |
| 802 | - ##-------------------------------------------------------------- |
| 803 | - def equity_ratio(_, 0), do: {:error, "total_assets can't be zero (Divide by zero error)"} |
| 816 | + def equity_multiplier(_, %Decimal{coef: 0} = _), |
| 817 | + do: {:error, "total_stockholders_equity #{@zero_error}"} |
| 818 | + |
| 819 | + def equity_multiplier(%Decimal{} = total_assets, %Decimal{} = total_stockholders_equity), |
| 820 | + do: {:ok, D.div(total_assets, total_stockholders_equity)} |
| 821 | + |
| 822 | + def equity_multiplier(_, _), |
| 823 | + do: {:error, @arg_msg} |
| 824 | + |
| 825 | + |
| 826 | + @doc """ |
| 827 | + Equity Ratio Calculation |
| 828 | + """ |
| 804 829 | def equity_ratio(total_equity, total_assets) |
| 805 | - when is_number(total_assets) |
| 806 | - and is_number(total_equity) |
| 807 | - do |
| 808 | - {:ok, (Float.round(total_equity/total_assets, @two_decimal_precision))} |
| 809 | - end |
| 810 | - def equity_ratio(_, _), do: {:error, "Arguments must be numerical"} |
| 811 830 | |
| 812 | - # TODO: add below to readme |
| 831 | + def equity_ratio(_, %Decimal{coef: 0} = _), |
| 832 | + do: {:error, "total_assets #{@zero_error}"} |
| 813 833 | |
| 814 | - ##-------------------------------------------------------------- |
| 815 | - ## Expense Ratio Calculation |
| 816 | - ## @param float -- operating_expenses |
| 817 | - ## @param float -- average_value_of_fund_assets |
| 818 | - ## @return float |
| 819 | - ##-------------------------------------------------------------- |
| 820 | - def expense_ratio(_, 0), do: {:error, "average_value_of_fund_assets can't be zero (Divide by zero error)"} |
| 834 | + def equity_ratio(%Decimal{} = total_equity, %Decimal{} = total_assets), |
| 835 | + do: {:ok, D.div(total_equity, total_assets)} |
| 836 | + |
| 837 | + def equity_ratio(_, _), |
| 838 | + do: {:error, @arg_msg} |
| 839 | + |
| 840 | + |
| 841 | + @doc """ |
| 842 | + Expense Ratio Calculation |
| 843 | + """ |
| 821 844 | def expense_ratio(operating_expenses, average_value_of_fund_assets) |
| 822 | - when is_number(operating_expenses) |
| 823 | - and is_number(average_value_of_fund_assets) |
| 824 | - do |
| 825 | - {:ok, (Float.round(operating_expenses/average_value_of_fund_assets, @two_decimal_precision))} |
| 826 | - end |
| 827 | - def expense_ratio(_, _), do: {:error, "Arguments must be numerical"} |
| 828 845 | |
| 829 | - ##-------------------------------------------------------------- |
| 830 | - ## Fixed Asset Turnover Ratio |
| 831 | - ## @param float -- net_sales |
| 832 | - ## @param float -- fixed_assets |
| 833 | - ## @param float -- accumulated_depreciation |
| 834 | - ## @return float |
| 835 | - ##-------------------------------------------------------------- |
| 836 | - def fixed_asset_turnover_ratio(net_sales, fixed_assets, accumulated_depreciation) |
| 837 | - when is_number(net_sales) |
| 838 | - and is_number(fixed_assets) |
| 839 | - and is_number(accumulated_depreciation) |
| 840 | - do |
| 841 | - case (fixed_assets - accumulated_depreciation) do |
| 842 | - 0 -> {:error, "fixed_assets - accumulated_depreciation can't equal zero (Divide by zero error)"} |
| 843 | - depreciated_assets -> {:ok, (Float.round(net_sales/depreciated_assets, @two_decimal_precision))} |
| 844 | - end |
| 845 | - end |
| 846 | - def fixed_asset_turnover_ratio(_, _, _), do: {:error, "Arguments must be numerical"} |
| 846 | + def expense_ratio(_, %Decimal{coef: 0} = _), |
| 847 | + do: {:error, "average_value_of_fund_assets #{@zero_error}"} |
| 847 848 | |
| 848 | - ##-------------------------------------------------------------- |
| 849 | - ## Fixed Charge Coverage Ratio |
| 850 | - ## @param float -- ebit |
| 851 | - ## @param float -- fixed_charges_before_taxes |
| 852 | - ## @param float -- interest |
| 853 | - ## @return float |
| 854 | - ##-------------------------------------------------------------- |
| 855 | - def fixed_charge_coverage_ratio(ebit, fixed_charges_before_taxes, interest) |
| 856 | - when is_number(ebit) |
| 857 | - and is_number(fixed_charges_before_taxes) |
| 858 | - and is_number(interest) |
| 859 | - do |
| 860 | - case (fixed_charges_before_taxes + interest) do |
| 861 | - 0 -> {:error, "fixed_charges_before_taxes + interest can't equal zero (Divide by zero error)"} |
| 862 | - res -> {:ok, (Float.round((ebit + fixed_charges_before_taxes)/res, @two_decimal_precision))} |
| 863 | - end |
| 864 | - end |
| 865 | - def fixed_charge_coverage_ratio(_, _, _), do: {:error, "Arguments must be numerical"} |
| 849 | + def expense_ratio(%Decimal{} = operating_expenses, %Decimal{} = average_value_of_fund_assets), |
| 850 | + do: {:ok, D.div(operating_expenses, average_value_of_fund_assets)} |
| 866 851 | |
| 867 | - ##-------------------------------------------------------------- |
| 868 | - ## Free Cash Flow Calculation |
| 869 | - ## @param float -- operating_cash_flow |
| 870 | - ## @param float -- capital_expenditures |
| 871 | - ## @return float |
| 872 | - ##-------------------------------------------------------------- |
| 873 | - def fcf(operating_cash_flow, capital_expenditures) |
| 874 | - when is_number(operating_cash_flow) |
| 875 | - and is_number(capital_expenditures) |
| 876 | - do |
| 877 | - {:ok, (operating_cash_flow - capital_expenditures)} |
| 878 | - end |
| 879 | - def fcf(_, _), do: {:error, "Arguments must be numerical"} |
| 852 | + def expense_ratio(_, _), |
| 853 | + do: {:error, @arg_msg} |
| 880 854 | |
| 881 | - ##-------------------------------------------------------------- |
| 882 | - ## Goodwill to Assets Calculation |
| 883 | - ## @param float -- goodwill |
| 884 | - ## @param float -- assets |
| 885 | - ## @return float |
| 886 | - ##-------------------------------------------------------------- |
| 887 | - def goodwill_to_assets(_, 0), do: {:error, "assets can't be zero (Divide by zero error)"} |
| 855 | + |
| 856 | + @doc """ |
| 857 | + Fixed Asset Turnover Ratio |
| 858 | + """ |
| 859 | + def fixed_asset_turnover_ratio( |
| 860 | + %Decimal{} = net_sales, |
| 861 | + %Decimal{} = fixed_assets, |
| 862 | + %Decimal{} = accumulated_depreciation |
| 863 | + ) do |
| 864 | + depreciated_assets = D.sub(fixed_assets, accumulated_depreciation) |
| 865 | + cond do |
| 866 | + D.eq?(depreciated_assets, 0) -> {:error, "fixed_assets - accumulated_depreciation #{@zero_error}"} |
| 867 | + :else -> {:ok, D.div(net_sales, depreciated_assets)} |
| 868 | + end |
| 869 | + end |
| 870 | + |
| 871 | + def fixed_asset_turnover_ratio(_, _, _), |
| 872 | + do: {:error, @arg_msg} |
| 873 | + |
| 874 | + |
| 875 | + @doc """ |
| 876 | + Fixed Charge Coverage Ratio |
| 877 | + """ |
| 878 | + def fixed_charge_coverage_ratio( |
| 879 | + %Decimal{} = ebit, |
| 880 | + %Decimal{} = fixed_charges_before_taxes, |
| 881 | + %Decimal{} = interest |
| 882 | + ) do |
| 883 | + charges = (fixed_charges_before_taxes + interest) |
| 884 | + cond do |
| 885 | + D.eq?(charges, 0) -> {:error, "fixed_charges_before_taxes + interest #{@zero_error}"} |
| 886 | + :else -> {:ok, D.div(D.add(ebit, fixed_charges_before_taxes), charges)} |
| 887 | + end |
| 888 | + end |
| 889 | + |
| 890 | + def fixed_charge_coverage_ratio(_, _, _), |
| 891 | + do: {:error, @arg_msg} |
| 892 | + |
| 893 | + |
| 894 | + @doc """ |
| 895 | + Free Cash Flow Calculation |
| 896 | + """ |
| 897 | + def fcf(%Decimal{} = operating_cash_flow, %Decimal{} = capital_expenditures), |
| 898 | + do: {:ok, D.sub(operating_cash_flow, capital_expenditures)} |
| 899 | + |
| 900 | + def fcf(_, _), |
| 901 | + do: {:error, @arg_msg} |
| 902 | + |
| 903 | + |
| 904 | + @doc """ |
| 905 | + Goodwill to Assets Calculation |
| 906 | + """ |
| 888 907 | def goodwill_to_assets(goodwill, assets) |
| 889 | - when is_number(goodwill) |
| 890 | - and is_number(assets) |
| 891 | - do |
| 892 | - {:ok, Float.round(goodwill/assets, @two_decimal_precision)} |
| 893 | - end |
| 894 | - def goodwill_to_assets(_, _), do: {:error, "Arguments must be numerical"} |
| 895 908 | |
| 896 | - ##-------------------------------------------------------------- |
| 897 | - ## Gross Margin Ratio Calculation |
| 898 | - ## @param float -- gross_margin |
| 899 | - ## @param float -- net_sales |
| 900 | - ## @return float |
| 901 | - ##-------------------------------------------------------------- |
| 902 | - def gross_margin_ratio(_, 0), do: {:error, "net_sales can't be zero (Divide by zero error)"} |
| 909 | + def goodwill_to_assets(_, %Decimal{coef: 0} = _), |
| 910 | + do: {:error, "assets #{@zero_error}"} |
| 911 | + |
| 912 | + def goodwill_to_assets(%Decimal{} = goodwill, %Decimal{} = assets), |
| 913 | + do: {:ok, D.div(goodwill, assets)} |
| 914 | + |
| 915 | + def goodwill_to_assets(_, _), |
| 916 | + do: {:error, @arg_msg} |
| 917 | + |
| 918 | + |
| 919 | + @doc """ |
| 920 | + Gross Margin Ratio Calculation |
| 921 | + """ |
| 903 922 | def gross_margin_ratio(gross_margin, net_sales) |
| 904 | - when is_number(gross_margin) |
| 905 | - and is_number(net_sales) |
| 906 | - do |
| 907 | - {:ok, (Float.round(gross_margin/net_sales, @two_decimal_precision))} |
| 908 | - end |
| 909 | - def gross_margin_ratio(_, _), do: {:error, "Arguments must be numerical"} |
| 923 | + |
| 924 | + def gross_margin_ratio(_, %Decimal{coef: 0} = _), |
| 925 | + do: {:error, "net_sales #{@zero_error}"} |
| 926 | + |
| 927 | + def gross_margin_ratio(%Decimal{} = gross_margin, %Decimal{} = net_sales), |
| 928 | + do: {:ok, D.div(gross_margin, net_sales)} |
| 929 | + |
| 930 | + def gross_margin_ratio(_, _), |
| 931 | + do: {:error, @arg_msg} |
| 910 932 | |
| 911 933 | |
| 912 | - ##-------------------------------------------------------------- |
| 913 | - ## Gross Profit Calculation |
| 914 | - ## @param float -- total_sales |
| 915 | - ## @param float -- cogs |
| 916 | - ## @return float |
| 917 | - ##-------------------------------------------------------------- |
| 918 | - def gross_profit(total_sales, cogs) |
| 919 | - when is_number(total_sales) |
| 920 | - and is_number(cogs) |
| 921 | - do |
| 922 | - {:ok, (total_sales - cogs)} |
| 923 | - end |
| 924 | - def gross_profit(_, _), do: {:error, "Arguments must be numerical"} |
| 934 | + @doc """ |
| 935 | + Gross Profit Calculation |
| 936 | + """ |
| 937 | + def gross_profit(%Decimal{} = total_sales, %Decimal{} = cogs), |
| 938 | + do: {:ok, D.sub(total_sales, cogs)} |
| 925 939 | |
| 926 | - ##-------------------------------------------------------------- |
| 927 | - ## Interest Coverage Ratio Calculation |
| 928 | - ## @param float -- ebit |
| 929 | - ## @param float -- interest_expense |
| 930 | - ## @return float |
| 931 | - ##-------------------------------------------------------------- |
| 932 | - def interest_coverage_ratio(_, 0), do: {:error, "interest_expense can't be zero (Divide by zero error)"} |
| 940 | + def gross_profit(_, _), |
| 941 | + do: {:error, @arg_msg} |
| 942 | + |
| 943 | + |
| 944 | + @doc """ |
| 945 | + Interest Coverage Ratio Calculation |
| 946 | + """ |
| 933 947 | def interest_coverage_ratio(ebit, interest_expense) |
| 934 | - when is_number(ebit) |
| 935 | - and is_number(interest_expense) |
| 936 | - do |
| 937 | - {:ok, (Float.round(ebit/interest_expense, @two_decimal_precision))} |
| 938 | - end |
| 939 | - def interest_coverage_ratio(_, _), do: {:error, "Arguments must be numerical"} |
| 940 948 | |
| 941 | - ##-------------------------------------------------------------- |
| 942 | - ## Inventory Turnover Ratio |
| 943 | - ## @param float -- cogs |
| 944 | - ## @param float -- average_inventory |
| 945 | - ## @return float |
| 946 | - ##-------------------------------------------------------------- |
| 947 | - def inventory_turnover_ratio(_, 0), do: {:error, "average_inventory can't be zero (Divide by zero error)"} |
| 949 | + def interest_coverage_ratio(_, %Decimal{coef: 0} = _), |
| 950 | + do: {:error, "interest_expense #{@zero_error}"} |
| 951 | + |
| 952 | + def interest_coverage_ratio(%Decimal{} = ebit, %Decimal{} = interest_expense), |
| 953 | + do: {:ok, D.div(ebit, interest_expense)} |
| 954 | + |
| 955 | + def interest_coverage_ratio(_, _), |
| 956 | + do: {:error, @arg_msg} |
| 957 | + |
| 958 | + |
| 959 | + @doc """ |
| 960 | + Inventory Turnover Ratio |
| 961 | + """ |
| 948 962 | def inventory_turnover_ratio(cogs, average_inventory) |
| 949 | - when is_number(cogs) |
| 950 | - and is_number(average_inventory) |
| 951 | - do |
| 952 | - {:ok, (Float.round(cogs/average_inventory, @two_decimal_precision))} |
| 953 | - end |
| 954 | - def inventory_turnover_ratio(_, _), do: {:error, "Arguments must be numerical"} |
| 955 963 | |
| 956 | - ##-------------------------------------------------------------- |
| 957 | - ## Loan to Value Ratio Calculation |
| 958 | - ## @param float -- mortgage_amount |
| 959 | - ## @param float -- appraised_value_of_property |
| 960 | - ## @return float |
| 961 | - ##-------------------------------------------------------------- |
| 962 | - def ltv(_, 0), do: {:error, "appraised_value_of_property can't be zero (Divide by zero error)"} |
| 964 | + def inventory_turnover_ratio(_, %Decimal{coef: 0} = _), |
| 965 | + do: {:error, "average_inventory #{@zero_error}"} |
| 966 | + |
| 967 | + def inventory_turnover_ratio(%Decimal{} = cogs, %Decimal{} = average_inventory), do: |
| 968 | + {:ok, D.div(cogs, average_inventory)} |
| 969 | + |
| 970 | + def inventory_turnover_ratio(_, _), |
| 971 | + do: {:error, @arg_msg} |
| 972 | + |
| 973 | + |
| 974 | + @doc """ |
| 975 | + Loan to Value Ratio Calculation |
| 976 | + """ |
| 963 977 | def ltv(mortgage_amount, appraised_value_of_property) |
| 964 | - when is_number(mortgage_amount) |
| 965 | - and is_number(appraised_value_of_property) |
| 966 | - do |
| 967 | - {:ok, (Float.round(mortgage_amount/appraised_value_of_property, @two_decimal_precision))} |
| 968 | - end |
| 969 | - def ltv(_, _), do: {:error, "Arguments must be numerical"} |
| 978 | + |
| 979 | + def ltv(_, %Decimal{coef: 0} = _), |
| 980 | + do: {:error, "appraised_value_of_property #{@zero_error}"} |
| 981 | + |
| 982 | + def ltv(%Decimal{} = mortgage_amount, %Decimal{} = appraised_value_of_property), |
| 983 | + do: {:ok, D.div(mortgage_amount, appraised_value_of_property)} |
| 984 | + |
| 985 | + def ltv(_, _), |
| 986 | + do: {:error, @arg_msg} |
| 970 987 | |
| 971 988 | |
| 972 | - ##-------------------------------------------------------------- |
| 973 | - ## Long Term Debt to Total Asset Ratio Calculation |
| 974 | - ## @param float -- long_term_debt |
| 975 | - ## @param float -- total_assets |
| 976 | - ## @return float |
| 977 | - ##-------------------------------------------------------------- |
| 978 | - def long_term_debt_to_total_asset_ratio(_, 0), do: {:error, "total_assets can't be zero (Divide by zero error)"} |
| 989 | + @doc """ |
| 990 | + Long Term Debt to Total Asset Ratio Calculation |
| 991 | + """ |
| 979 992 | def long_term_debt_to_total_asset_ratio(long_term_debt, total_assets) |
| 980 | - when is_number(long_term_debt) |
| 981 | - and is_number(total_assets) |
| 982 | - do |
| 983 | - {:ok, (Float.round(long_term_debt/total_assets, @two_decimal_precision))} |
| 984 | - end |
| 985 | - def long_term_debt_to_total_asset_ratio(_, _), do: {:error, "Arguments must be numerical"} |
| 986 993 | |
| 987 | - ##-------------------------------------------------------------- |
| 988 | - ## Margin of Safety Calculation |
| 989 | - ## @param float -- actual_sales |
| 990 | - ## @param float -- break_even_point |
| 991 | - ## @return float |
| 992 | - ##-------------------------------------------------------------- |
| 993 | - def margin_of_safety(actual_sales, break_even_point) |
| 994 | - when is_number(actual_sales) |
| 995 | - and is_number(break_even_point) |
| 996 | - do |
| 997 | - {:ok, (actual_sales - break_even_point)} |
| 998 | - end |
| 999 | - def margin_of_safety(_, _), do: {:error, "Arguments must be numerical"} |
| 994 | + def long_term_debt_to_total_asset_ratio(_, %Decimal{coef: 0} = _), |
| 995 | + do: {:error, "total_assets #{@zero_error}"} |
| 1000 996 | |
| 1001 | - ##-------------------------------------------------------------- |
| 1002 | - ## Margin of Safety Ratio Calculation |
| 1003 | - ## @param float -- actual_sales |
| 1004 | - ## @param float -- break_even_point |
| 1005 | - ## @return float |
| 1006 | - ##-------------------------------------------------------------- |
| 1007 | - def margin_of_safety_ratio(0, _), do: {:error, "actual_sales can't be zero (Divide by zero error)"} |
| 997 | + def long_term_debt_to_total_asset_ratio(%Decimal{} = long_term_debt, %Decimal{} = total_assets), |
| 998 | + do: {:ok, D.div(long_term_debt, total_assets)} |
| 999 | + |
| 1000 | + def long_term_debt_to_total_asset_ratio(_, _), |
| 1001 | + do: {:error, @arg_msg} |
| 1002 | + |
| 1003 | + |
| 1004 | + @doc """ |
| 1005 | + Margin of Safety Calculation |
| 1006 | + """ |
| 1007 | + def margin_of_safety(%Decimal{} = actual_sales, %Decimal{} = break_even_point), |
| 1008 | + do: {:ok, D.sub(actual_sales, break_even_point)} |
| 1009 | + |
| 1010 | + def margin_of_safety(_, _), |
| 1011 | + do: {:error, @arg_msg} |
| 1012 | + |
| 1013 | + |
| 1014 | + @doc """ |
| 1015 | + Margin of Safety Ratio Calculation |
| 1016 | + """ |
| 1008 1017 | def margin_of_safety_ratio(actual_sales, break_even_point) |
| 1009 | - when is_number(actual_sales) |
| 1010 | - and is_number(break_even_point) |
| 1011 | - do |
| 1012 | - {:ok, (Float.round((actual_sales - break_even_point)/actual_sales, @two_decimal_precision))} |
| 1013 | - end |
| 1014 | - def margin_of_safety_ratio(_, _), do: {:error, "Arguments must be numerical"} |
| 1015 1018 | |
| 1016 | - ##-------------------------------------------------------------- |
| 1017 | - ## Margin of Revenue Calculation |
| 1018 | - ## @param float -- change_in_total_revenues |
| 1019 | - ## @param float -- change_in_quantity_sold |
| 1020 | - ## @return float |
| 1021 | - ##-------------------------------------------------------------- |
| 1022 | - def margin_of_revenue(_, 0), do: {:error, "change_in_quantity_sold can't be zero (Divide by zero error)"} |
| 1019 | + def margin_of_safety_ratio(%Decimal{coef: 0} = _, _), |
| 1020 | + do: {:error, "actual_sales #{@zero_error}"} |
| 1021 | + |
| 1022 | + def margin_of_safety_ratio(%Decimal{} = actual_sales, %Decimal{} = break_even_point), |
| 1023 | + do: {:ok, D.div(D.sub(actual_sales, break_even_point), actual_sales)} |
| 1024 | + |
| 1025 | + def margin_of_safety_ratio(_, _), |
| 1026 | + do: {:error, @arg_msg} |
| 1027 | + |
| 1028 | + |
| 1029 | + @doc """ |
| 1030 | + Margin of Revenue Calculation |
| 1031 | + """ |
| 1023 1032 | def margin_of_revenue(change_in_total_revenues, change_in_quantity_sold) |
| 1024 | - when is_number(change_in_total_revenues) |
| 1025 | - and is_number(change_in_quantity_sold) |
| 1026 | - do |
| 1027 | - {:ok, (Float.round(change_in_total_revenues/change_in_quantity_sold, @two_decimal_precision))} |
| 1028 | - end |
| 1029 | - def margin_of_revenue(_, _), do: {:error, "Arguments must be numerical"} |
| 1033 | + |
| 1034 | + def margin_of_revenue(_, %Decimal{coef: 0} = _), |
| 1035 | + do: {:error, "change_in_quantity_sold #{@zero_error}"} |
| 1036 | + |
| 1037 | + def margin_of_revenue(%Decimal{} = change_in_total_revenues, %Decimal{} = change_in_quantity_sold), |
| 1038 | + do: {:ok, D.div(change_in_total_revenues, change_in_quantity_sold)} |
| 1039 | + |
| 1040 | + def margin_of_revenue(_, _), |
| 1041 | + do: {:error, @arg_msg} |
| 1030 1042 | |
| 1031 1043 | |
| 1032 1044 | end |
| \ No newline at end of file |
| @@ -3,7 +3,7 @@ defmodule Finance.Mixfile do | |
| 3 3 | |
| 4 4 | def project do |
| 5 5 | [app: :financials, |
| 6 | - version: "0.0.0", |
| 6 | + version: "0.1.0", |
| 7 7 | elixir: "~> 1.10.2", |
| 8 8 | description: "A finance library for elixir.", |
| 9 9 | package: package(), |
| @@ -32,6 +32,7 @@ defmodule Finance.Mixfile do | |
| 32 32 | |
| 33 33 | defp deps do |
| 34 34 | [ |
| 35 | + {:decimal, "~> 2.0"}, |
| 35 36 | {:ex_doc, ">= 0.0.0", only: :dev, runtime: false} |
| 36 37 | ] |
| 37 38 | end |