Last active
December 26, 2024 13:53
-
-
Save nreHieW/6365cb92523f0d347c1338d22f74f780 to your computer and use it in GitHub Desktop.
Discounted Cash Flow (DCF) modelling in Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import pandas as pd | |
| import numpy as np | |
| def calc_cost_of_capital(interest_expense, pre_tax_cost_of_debt, average_maturity, bv_debt, num_shares_outstanding, curr_price, unlevered_beta, tax_rate, risk_free_rate, equity_risk_premium): | |
| market_value_of_debt = interest_expense * (1 - 1 / ((1 + pre_tax_cost_of_debt) ** average_maturity)) / pre_tax_cost_of_debt + bv_debt / ((1 + pre_tax_cost_of_debt) ** average_maturity) | |
| market_value_of_equity = num_shares_outstanding * curr_price | |
| market_value_of_capital = market_value_of_debt + market_value_of_equity | |
| equity_weight = market_value_of_equity / market_value_of_capital | |
| debt_weight = market_value_of_debt / market_value_of_capital | |
| levered_beta = unlevered_beta * (1 + (1 - tax_rate) * (market_value_of_debt / market_value_of_equity)) | |
| cost_of_debt = pre_tax_cost_of_debt * (1 - tax_rate) | |
| cost_of_equity = risk_free_rate + levered_beta * equity_risk_premium | |
| cost_of_capital = cost_of_debt * debt_weight + cost_of_equity * equity_weight | |
| return cost_of_capital, { | |
| "cost_of_debt": cost_of_debt, | |
| "cost_of_equity": cost_of_equity, | |
| "levered_beta": levered_beta, | |
| "risk_free_rate": risk_free_rate, | |
| "equity_risk_premium": equity_risk_premium, | |
| } | |
| def r_and_d_adjustment(expenses: list): | |
| num_years = len(expenses) - 1 | |
| expenses = np.array(expenses) | |
| unamortized_amount = np.linspace(1, 0, num_years + 1) | |
| unamortized_amount = unamortized_amount * expenses | |
| unamortized_amount = np.sum(unamortized_amount) | |
| amortization_this_year = expenses[1:] / num_years | |
| amortization_this_year = np.sum(amortization_this_year) | |
| adjustment = expenses[0] - amortization_this_year | |
| return adjustment, unamortized_amount | |
| def dcf( | |
| revenues, | |
| operating_income, | |
| interest_expense, | |
| book_value_of_equity, | |
| book_value_of_debt, | |
| cash_and_marketable_securities, | |
| cross_holdings_and_other_non_operating_assets, | |
| minority_interest, | |
| number_of_shares_outstanding, | |
| curr_price, | |
| effective_tax_rate, | |
| marginal_tax_rate, | |
| unlevered_beta, | |
| risk_free_rate, | |
| equity_risk_premium, | |
| mature_erp, | |
| pre_tax_cost_of_debt, | |
| average_maturity, | |
| prob_of_failure, | |
| value_of_options, | |
| revenue_growth_rate_next_year, | |
| operating_margin_next_year, | |
| compounded_annual_revenue_growth_rate, | |
| target_pre_tax_operating_margin, | |
| year_of_convergence_for_margin, | |
| years_of_high_growth, | |
| sales_to_capital_ratio_early, | |
| sales_to_capital_ratio_steady, | |
| r_and_d_expenses, | |
| discount_rate: None, | |
| ): | |
| start_cost_of_capital, cost_of_capital_components = calc_cost_of_capital( | |
| interest_expense, pre_tax_cost_of_debt, average_maturity, book_value_of_debt, number_of_shares_outstanding, curr_price, unlevered_beta, marginal_tax_rate, risk_free_rate, equity_risk_premium | |
| ) | |
| if discount_rate is not None: | |
| start_cost_of_capital = discount_rate | |
| if len(r_and_d_expenses) > 0: | |
| r_and_d_adjustment_value, value_of_research_asset = r_and_d_adjustment(r_and_d_expenses) | |
| operating_income += r_and_d_adjustment_value | |
| else: | |
| value_of_research_asset = 0 | |
| revenue_growth_rates = ( | |
| [0] + [revenue_growth_rate_next_year] + [compounded_annual_revenue_growth_rate] * (years_of_high_growth - 1) + np.linspace(compounded_annual_revenue_growth_rate, risk_free_rate, 10 - years_of_high_growth).tolist() + [risk_free_rate] | |
| ) | |
| df = pd.DataFrame({"revenue_growth_rate": revenue_growth_rates}) | |
| df["revenues"] = revenues * (1 + df["revenue_growth_rate"]).cumprod() | |
| starting_operating_margin = operating_income / revenues | |
| # TODO: Adjust operating income then get margin rather than make it an input | |
| df["operating_margin"] = ( | |
| [starting_operating_margin] + np.linspace(operating_margin_next_year, target_pre_tax_operating_margin, year_of_convergence_for_margin).tolist() + [target_pre_tax_operating_margin] * (11 - year_of_convergence_for_margin) | |
| ) | |
| df["operating_income"] = df["revenues"] * df["operating_margin"] | |
| df["tax_rate"] = [effective_tax_rate] * 6 + np.linspace(effective_tax_rate, marginal_tax_rate, 5).tolist() + [marginal_tax_rate] | |
| df["taxes"] = np.where(df["operating_income"] > 0, df["operating_income"] * df["tax_rate"], 0) | |
| df["nol"] = np.where(df["operating_income"] < 0, -df["operating_income"] * 0.8, 0) # https://www.investopedia.com/terms/t/tax-loss-carryforward.asp | |
| df["nol_cumulative"] = df["nol"].cumsum() | |
| df["nol_utilized"] = np.where(df["operating_income"] > 0, np.minimum(df["nol_cumulative"], df["operating_income"]), 0) | |
| df["nol_cumulative"] -= df["nol_utilized"] | |
| df["taxes"] -= df["nol_utilized"] * df["tax_rate"] | |
| df["ebit_after_tax"] = df["operating_income"] - df["taxes"] | |
| df["reinvestment"] = (df["revenues"].diff(-1) * -1) / (np.linspace(sales_to_capital_ratio_early, sales_to_capital_ratio_steady, 7).tolist() + [sales_to_capital_ratio_steady] * 5) | |
| df.loc[0, "reinvestment"] = 0 | |
| starting_invested_capital = book_value_of_equity + book_value_of_debt - cash_and_marketable_securities + value_of_research_asset | |
| df["invested_capital"] = starting_invested_capital + df["reinvestment"].cumsum() | |
| df["roic"] = df["ebit_after_tax"] / df["invested_capital"] | |
| end_cost_of_capital = risk_free_rate + mature_erp | |
| df["cost_of_capital"] = [start_cost_of_capital] * 6 + np.linspace(start_cost_of_capital, end_cost_of_capital, 6).tolist() | |
| df.loc[11, "roic"] = end_cost_of_capital | |
| df.loc[11, "reinvestment"] = risk_free_rate / df.loc[11, "roic"] * df.loc[11, "ebit_after_tax"] | |
| df["fcff"] = df["ebit_after_tax"] - df["reinvestment"] | |
| df["discount_factor"] = (1 / (1 + df["cost_of_capital"])).cumprod() | |
| df["discount_factor"] = df["discount_factor"].shift(1) | |
| df["pv_fcff"] = df["fcff"] * df["discount_factor"] | |
| terminal_val = df.loc[11, "fcff"] / (end_cost_of_capital - risk_free_rate) | |
| terminal_pv = terminal_val * df.loc[10, "discount_factor"] | |
| pv_cf = df.loc[1:11, "pv_fcff"].sum() + terminal_pv | |
| proceeds_if_fail = pv_cf * 0.5 | |
| op_value = pv_cf * (1 - prob_of_failure) + proceeds_if_fail * prob_of_failure | |
| value_of_equity = op_value - book_value_of_debt - minority_interest + cash_and_marketable_securities + cross_holdings_and_other_non_operating_assets | |
| value_of_equity = value_of_equity - value_of_options | |
| value_per_share = value_of_equity / number_of_shares_outstanding | |
| df.index = ["Base"] + list(range(1, 11)) + ["Terminal"] | |
| return ( | |
| value_per_share, | |
| df, | |
| cost_of_capital_components, | |
| { | |
| "present_value_of_cash_flows": pv_cf, | |
| "book_value_of_debt": book_value_of_debt, | |
| "cash_and_marketable_securities": cash_and_marketable_securities, | |
| "cross_holdings_and_other_non_operating_assets": cross_holdings_and_other_non_operating_assets, | |
| "minority_interest": minority_interest, | |
| }, | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment