
Automating DCF Modeling with Python
Feb 10 2025 03:31 PM
Tre Brown
Discounted Cash Flow (DCF) modeling is one of the most effective ways to determine the intrinsic value of a company. By projecting future cash flows and discounting them to present value, investors can make data-driven decisions. In this post, we'll automate DCF modeling using Python and the `yfinance` library.
We'll break down the script into sections to explain its functionality step by step. The full code is provided at the end for easy implementation.
Step 1: Fetching Financial Data
To perform DCF modeling, we need financial statements, including the income statement, balance sheet, and cash flow statement. We use the yfinance library to fetch this data.
import yfinance as yf import pandas as pd def fetch_financial_data(ticker): stock = yf.Ticker(ticker) income_statement = stock.financials cash_flow = stock.cashflow balance_sheet = stock.balance_sheet info = stock.info return income_statement, cash_flow, balance_sheet, infoThis function retrieves key financials for a given stock ticker. Step 2: Preparing the Data Once we have financial data, we need to extract relevant metrics such as free cash flow, revenue growth rate, and equity.
def prepare_data(cash_flow, income_statement, balance_sheet): cash_flow = cash_flow.transpose() latest_free_cash_flow = cash_flow['Free Cash Flow'].iloc[0] latest_net_income = income_statement.loc['Net Income'].iloc[0] previous_net_income = income_statement.loc['Net Income'].iloc[1] net_income_growth_rate = (latest_net_income - previous_net_income) / previous_net_income latest_revenue = income_statement.loc['Total Revenue'].iloc[0] previous_revenue = income_statement.loc['Total Revenue'].iloc[1] revenue_growth_rate = (latest_revenue - previous_revenue) / previous_revenue latest_assets = balance_sheet.loc['Total Assets'].iloc[0] latest_liabilities = balance_sheet.loc['Total Debt'].iloc[0] equity = latest_assets - latest_liabilities return latest_free_cash_flow, net_income_growth_rate, revenue_growth_rate, equity, latest_assets, latest_liabilitiesThis function extracts crucial financial indicators required for DCF analysis. Step 3: Calculating Discounted Cash Flow (DCF) DCF valuation is performed by projecting free cash flows and discounting them to present value.
def calculate_dcf(free_cash_flow, discount_rate, growth_rate, years=5): cash_flows = [] for year in range(1, years + 1): future_cash_flow = free_cash_flow * ((1 + growth_rate) ** year) discounted_cash_flow = future_cash_flow / ((1 + discount_rate) ** year) cash_flows.append(discounted_cash_flow) dcf_value = sum(cash_flows) return dcf_value, cash_flowsThis function forecasts future cash flows and discounts them to estimate the present value. Step 4: Forecasting Stock Price & Calculating PE Ratio We calculate the future stock price based on the DCF valuation and price-to-earnings (P/E) ratio.
def forecast_future_price(dcf_value, shares_issued, pe_ratio): future_price = (dcf_value / shares_issued) * pe_ratio return future_price def calculate_pe_ratio(income_statement, shares_issued): latest_net_income = income_statement.loc['Net Income'].iloc[0] pe_ratio = latest_net_income / shares_issued return pe_ratio def calculate_future_stock_prices(latest_price, growth_rate, years): future_prices = [] for year in range(1, years + 1): future_price = latest_price * ((1 + growth_rate) ** year) future_prices.append(future_price) return future_pricesThese functions provide an estimate of the stock’s future price and PE ratio. Step 5: Performing Financial Analysis We further analyze financial ratios such as Gross Profit Margin, Return on Assets (ROA), and Return on Equity (ROE).
def analyze_financials(income_statement, cash_flow, balance_sheet, discount_rate=0.1, ebitda_multiple=10, market_cap=None): total_revenue = income_statement.loc['Total Revenue'] total_expenses = income_statement.loc['Total Expenses'] net_income = income_statement.loc['Net Income'] free_cash_flow = cash_flow.loc['Free Cash Flow'] ebitda = income_statement.loc['EBITDA'] interest_expense = income_statement.loc['Interest Expense'] ebit = income_statement.loc['EBIT'] total_debt = balance_sheet.loc['Total Debt'] total_assets = balance_sheet.loc['Total Assets'] total_liabilities = balance_sheet.loc['Total Debt'] shareholder_equity = total_assets - total_liabilities latest_ebitda = ebitda.iloc[0] enterprise_value = latest_ebitda * ebitda_multiple print(f"EBITDA-based valuation suggests an enterprise value of: ${enterprise_value:,.2f}") gross_profit = income_statement.loc['Gross Profit'] gross_margin = (gross_profit / total_revenue) * 100 print(f"Gross Profit Margin: {gross_margin.iloc[0]:.2f}%") ebit_margin = (ebit / total_revenue) * 100 print(f"EBIT Margin: {ebit_margin.iloc[0]:.2f}%") if interest_expense.iloc[0] > 0: interest_coverage = ebit.iloc[0] / interest_expense.iloc[0] print(f"Interest Coverage Ratio: {interest_coverage:.2f}") else: print("No interest expenses for the period.") # Price to Earnings (P/E) Ratio if market_cap and net_income.iloc[0] > 0: pe_ratio = market_cap / net_income.iloc[0] print(f"Price to Earnings (P/E) Ratio: {pe_ratio:.2f}") else: print("Market capitalization or net income unavailable for P/E calculation.") debt_to_equity = total_debt.iloc[0] / shareholder_equity print(f"Debt to Equity Ratio: {debt_to_equity}") roe = (net_income.iloc[0] / shareholder_equity) * 100 print(f"Return on Equity (ROE): {roe}%") roa = (net_income.iloc[0] / total_assets.iloc[0]) * 100 print(f"Return on Assets (ROA): {roa}%") roic = (net_income.iloc[0] - interest_expense.iloc[0]) / (total_assets.iloc[0] - total_liabilities.iloc[0]) print(f"Return on Invested Capital (ROIC): {roic:.2f}%") if (net_income > 0).all() and (free_cash_flow > 0).all(): print("The company has positive net income and free cash flow. Analyzing deeper metrics...") growth_rate = 0.05 projected_cash_flows = [] for year in range(1, 6): projected_cash_flow = free_cash_flow.iloc[0] * ((1 + growth_rate) ** year) discounted_cash_flow = projected_cash_flow / ((1 + discount_rate) ** year) projected_cash_flows.append(discounted_cash_flow) terminal_value = (projected_cash_flows[-1] * (1 + growth_rate)) / (discount_rate - growth_rate) total_dcf_value = np.sum(projected_cash_flows) + terminal_value print(f"DCF-based valuation suggests a value of: ${total_dcf_value:,.2f}") return True else: print("The financials are mixed. Further analysis needed.") return FalseThis function calculates key financial health indicators for the company. Step 6: Visualizing the Results Finally, we use Matplotlib to visualize future stock prices and free cash flow projections.
import matplotlib.pyplot as plt import numpy as np def plot_results(years_range, future_cash_flows, dcf_value, latest_free_cash_flow, future_stock_prices, latest_price, asset_ticker): fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(10, 15)) ax1.plot(years_range, future_stock_prices, marker='o', label='Predicted Future Stock Price', color='purple', linewidth=2) ax1.axhline(y=latest_price, color='blue', linestyle='--', label=f'Current Stock Price {round(latest_price, 2)}', linewidth=2) ax1.set_title(f'{asset_ticker} Stock Price and Future Price Projection', fontsize=16) ax1.set_xlabel('Years', fontsize=14) ax1.set_ylabel('Stock Price ($)', fontsize=14) ax1.set_xticks(years_range) ax1.yaxis.set_major_formatter(ticker.StrMethodFormatter('${x:,.0f}')) ax1.grid(True, linestyle='--', alpha=0.7) ax1.legend() ax1.set_ylim(bottom=0) ax2.plot(years_range, future_cash_flows, marker='o', label='Future Cash Flows', color='blue', linewidth=2) ax2.axhline(y=dcf_value, color='red', linestyle='--', label=f'DCF Value {round(dcf_value, 2):,.2f}') ax2.set_title(f'DCF Analysis', fontsize=16) ax2.set_xlabel('Years', fontsize=14) ax2.set_ylabel('Value ($)', fontsize=14) ax2.set_xticks(years_range) ax2.yaxis.set_major_formatter(ticker.StrMethodFormatter('${x:,.0f}')) ax2.grid(True, linestyle='--', alpha=0.7) ax2.legend() ax2.set_ylim(bottom=0) ax3.bar(years_range, future_cash_flows, label='Future Cash Flows', color='orange', alpha=0.7) ax3.axhline(y=latest_free_cash_flow, color='green', linestyle='--', label=f'Current Free Cash Flow {latest_free_cash_flow:,.2f}', linewidth=2) ax3.set_title(f'Free Cash Flow Analysis', fontsize=16) ax3.set_xlabel('Years', fontsize=14) ax3.set_ylabel('Free Cash Flow ($)', fontsize=14) ax3.yaxis.set_major_formatter(ticker.StrMethodFormatter('${x:,.0f}')) ax3.grid(True, linestyle='--', alpha=0.7) ax3.legend() ax3.set_ylim(bottom=0) plt.tight_layout() plt.show()This function generates a plot showing stock price projections over time. Step 7: Running the DCF Model We put everything together in the `main()` function and run the model.
def main(): asset_ticker = 'CVNA' income_statement, cash_flow, balance_sheet, info = fetch_financial_data(asset_ticker) latest_free_cash_flow, net_income_growth_rate, revenue_growth_rate, equity, latest_assets, latest_liabilities = prepare_data(cash_flow, income_statement, balance_sheet) discount_rate = 0.1 dcf_value, future_cash_flows, _ = calculate_dcf(latest_free_cash_flow, discount_rate, revenue_growth_rate, years=5) latest_price = info['currentPrice'] pe_ratio = calculate_pe_ratio(income_statement, info['sharesOutstanding']) future_stock_prices = calculate_future_stock_prices(latest_price, revenue_growth_rate, years=5) years_range = np.arange(1, 6) analyze_financials(income_statement, cash_flow, balance_sheet, discount_rate=0.1, ebitda_multiple=10, market_cap=info['marketCap']) plot_results(years_range, future_cash_flows, dcf_value, latest_free_cash_flow, future_stock_prices, latest_price, asset_ticker) if __name__ == "__main__": main()Conclusion With this Python script, you can automate DCF modeling and quickly estimate the intrinsic value of a stock. The model fetches financial data, calculates discounted cash flow, and visualizes future projections. Investors can use this as a starting point and tweak the parameters to refine their valuation models.
Full Code
import yfinance as yf import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.ticker as ticker def fetch_financial_data(ticker): stock = yf.Ticker(ticker) income_statement = stock.financials cash_flow = stock.cashflow balance_sheet = stock.balance_sheet info = stock.info return income_statement, cash_flow, balance_sheet, info def prepare_data(cash_flow, income_statement, balance_sheet): cash_flow = cash_flow.transpose() latest_free_cash_flow = cash_flow['Free Cash Flow'].iloc[0] latest_net_income = income_statement.loc['Net Income'].iloc[0] previous_net_income = income_statement.loc['Net Income'].iloc[1] net_income_growth_rate = (latest_net_income - previous_net_income) / previous_net_income latest_revenue = income_statement.loc['Total Revenue'].iloc[0] previous_revenue = income_statement.loc['Total Revenue'].iloc[1] revenue_growth_rate = (latest_revenue - previous_revenue) / previous_revenue latest_assets = balance_sheet.loc['Total Assets'].iloc[0] latest_liabilities = balance_sheet.loc['Total Debt'].iloc[0] equity = latest_assets - latest_liabilities return latest_free_cash_flow, net_income_growth_rate, revenue_growth_rate, equity, latest_assets, latest_liabilities def calculate_dcf(free_cash_flow, discount_rate, growth_rate, years=5): cash_flows = [] future_cash_flows = [] for year in range(1, years + 1): future_cash_flow = free_cash_flow * ((1 + growth_rate) ** year) discounted_cash_flow = future_cash_flow / ((1 + discount_rate) ** year) cash_flows.append(discounted_cash_flow) future_cash_flows.append(future_cash_flow) dcf_value = sum(cash_flows) return dcf_value, cash_flows, future_cash_flows def forecast_future_price(dcf_value, shares_issued, pe_ratio): future_price = (dcf_value / shares_issued) * pe_ratio return future_price def calculate_pe_ratio(income_statement, shares_issued): latest_net_income = income_statement.loc['Net Income'].iloc[0] pe_ratio = latest_net_income / shares_issued return pe_ratio def calculate_future_stock_prices(latest_price, growth_rate, years): future_prices = [] for year in range(1, years + 1): future_price = latest_price * ((1 + growth_rate) ** year) future_prices.append(future_price) return future_prices def analyze_financials(income_statement, cash_flow, balance_sheet, discount_rate=0.1, ebitda_multiple=10, market_cap=None): total_revenue = income_statement.loc['Total Revenue'] total_expenses = income_statement.loc['Total Expenses'] net_income = income_statement.loc['Net Income'] free_cash_flow = cash_flow.loc['Free Cash Flow'] ebitda = income_statement.loc['EBITDA'] interest_expense = income_statement.loc['Interest Expense'] ebit = income_statement.loc['EBIT'] total_debt = balance_sheet.loc['Total Debt'] total_assets = balance_sheet.loc['Total Assets'] total_liabilities = balance_sheet.loc['Total Debt'] shareholder_equity = total_assets - total_liabilities # EBITDA Multiples Valuation latest_ebitda = ebitda.iloc[0] enterprise_value = latest_ebitda * ebitda_multiple print(f"EBITDA-based valuation suggests an enterprise value of: ${enterprise_value:,.2f}") # Gross Profit and Margins gross_profit = income_statement.loc['Gross Profit'] gross_margin = (gross_profit / total_revenue) * 100 print(f"Gross Profit Margin: {gross_margin.iloc[0]:.2f}%") # EBIT Margin ebit_margin = (ebit / total_revenue) * 100 print(f"EBIT Margin: {ebit_margin.iloc[0]:.2f}%") # Interest Coverage Ratio if interest_expense.iloc[0] > 0: interest_coverage = ebit.iloc[0] / interest_expense.iloc[0] print(f"Interest Coverage Ratio: {interest_coverage:.2f}") else: print("No interest expenses for the period.") # Price to Earnings (P/E) Ratio if market_cap and net_income.iloc[0] > 0: pe_ratio = market_cap / net_income.iloc[0] print(f"Price to Earnings (P/E) Ratio: {pe_ratio:.2f}") else: print("Market capitalization or net income unavailable for P/E calculation.") # Debt Ratios and Return on Assets (ROA) debt_to_equity = total_debt.iloc[0] / shareholder_equity print(f"Debt to Equity Ratio: {debt_to_equity}") roe = (net_income.iloc[0] / shareholder_equity) * 100 print(f"Return on Equity (ROE): {roe}%") roa = (net_income.iloc[0] / total_assets.iloc[0]) * 100 print(f"Return on Assets (ROA): {roa}%") # Return on Invested Capital (ROIC) roic = (net_income.iloc[0] - interest_expense.iloc[0]) / (total_assets.iloc[0] - total_liabilities.iloc[0]) print(f"Return on Invested Capital (ROIC): {roic:.2f}%") # Checking for positive free cash flow and net income if (net_income > 0).all() and (free_cash_flow > 0).all(): print("The company has positive net income and free cash flow. Analyzing deeper metrics...") growth_rate = 0.05 projected_cash_flows = [] for year in range(1, 6): projected_cash_flow = free_cash_flow.iloc[0] * ((1 + growth_rate) ** year) discounted_cash_flow = projected_cash_flow / ((1 + discount_rate) ** year) projected_cash_flows.append(discounted_cash_flow) terminal_value = (projected_cash_flows[-1] * (1 + growth_rate)) / (discount_rate - growth_rate) total_dcf_value = np.sum(projected_cash_flows) + terminal_value print(f"DCF-based valuation suggests a value of: ${total_dcf_value:,.2f}") return True else: print("The financials are mixed. Further analysis needed.") return False def plot_results(years_range, future_cash_flows, dcf_value, latest_free_cash_flow, future_stock_prices, latest_price, asset_ticker): fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(10, 15)) ax1.plot(years_range, future_stock_prices, marker='o', label='Predicted Future Stock Price', color='purple', linewidth=2) ax1.axhline(y=latest_price, color='blue', linestyle='--', label=f'Current Stock Price {round(latest_price, 2)}', linewidth=2) ax1.set_title(f'{asset_ticker} Stock Price and Future Price Projection', fontsize=16) ax1.set_xlabel('Years', fontsize=14) ax1.set_ylabel('Stock Price ($)', fontsize=14) ax1.set_xticks(years_range) ax1.yaxis.set_major_formatter(ticker.StrMethodFormatter('${x:,.0f}')) ax1.grid(True, linestyle='--', alpha=0.7) ax1.legend() ax1.set_ylim(bottom=0) ax2.plot(years_range, future_cash_flows, marker='o', label='Future Cash Flows', color='blue', linewidth=2) ax2.axhline(y=dcf_value, color='red', linestyle='--', label=f'DCF Value {round(dcf_value, 2):,.2f}') ax2.set_title(f'DCF Analysis', fontsize=16) ax2.set_xlabel('Years', fontsize=14) ax2.set_ylabel('Value ($)', fontsize=14) ax2.set_xticks(years_range) ax2.yaxis.set_major_formatter(ticker.StrMethodFormatter('${x:,.0f}')) ax2.grid(True, linestyle='--', alpha=0.7) ax2.legend() ax2.set_ylim(bottom=0) ax3.bar(years_range, future_cash_flows, label='Future Cash Flows', color='orange', alpha=0.7) ax3.axhline(y=latest_free_cash_flow, color='green', linestyle='--', label=f'Current Free Cash Flow {latest_free_cash_flow:,.2f}', linewidth=2) ax3.set_title(f'Free Cash Flow Analysis', fontsize=16) ax3.set_xlabel('Years', fontsize=14) ax3.set_ylabel('Free Cash Flow ($)', fontsize=14) ax3.yaxis.set_major_formatter(ticker.StrMethodFormatter('${x:,.0f}')) ax3.grid(True, linestyle='--', alpha=0.7) ax3.legend() ax3.set_ylim(bottom=0) plt.tight_layout() plt.show() def main(): asset_ticker = 'CVNA' income_statement, cash_flow, balance_sheet, info = fetch_financial_data(asset_ticker) latest_free_cash_flow, net_income_growth_rate, revenue_growth_rate, equity, latest_assets, latest_liabilities = prepare_data(cash_flow, income_statement, balance_sheet) discount_rate = 0.1 dcf_value, future_cash_flows, _ = calculate_dcf(latest_free_cash_flow, discount_rate, revenue_growth_rate, years=5) latest_price = info['currentPrice'] pe_ratio = calculate_pe_ratio(income_statement, info['sharesOutstanding']) future_stock_prices = calculate_future_stock_prices(latest_price, revenue_growth_rate, years=5) years_range = np.arange(1, 6) analyze_financials(income_statement, cash_flow, balance_sheet, discount_rate=0.1, ebitda_multiple=10, market_cap=info['marketCap']) plot_results(years_range, future_cash_flows, dcf_value, latest_free_cash_flow, future_stock_prices, latest_price, asset_ticker) if __name__ == "__main__": main()