Packages

A finance library for elixir.

Current section

2 Versions

Jump to

Compare versions

4 files changed
+1020 additions
-928 deletions
  @@ -4,10 +4,12 @@
4 4 [![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/tterb/atomic-design-ui/blob/master/LICENSEs)
5 5 [![Hex.pm Version](http://img.shields.io/hexpm/v/financials.svg?style=flat)](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