import streamlit as st import yfinance as yf import numpy as np import pandas as pd import requests import plotly.graph_objects as go from bs4 import BeautifulSoup import warnings warnings.filterwarnings("ignore") import os # Financial Modeling Prep API Key FMP_API_KEY = os.getenv("FMP_API_KEY") # Function to get data from Finviz def get_finviz_data(ticker): url = f"https://finviz.com/quote.ashx?t={ticker}" headers = {'User-Agent': 'Mozilla/5.0'} response = requests.get(url, headers=headers) soup = BeautifulSoup(response.content, 'html.parser') data = {} try: eps_next_y_growth = soup.find_all(text="EPS next Y")[1].find_next(class_='snapshot-td2').text data['EPS Next Year Growth'] = float(eps_next_y_growth.strip('%')) / 100 eps_next_5y = soup.find(text="EPS next 5Y").find_next(class_='snapshot-td2').text data['EPS Next 5Y'] = float(eps_next_5y.strip('%')) / 100 except Exception as e: st.error(f"Error fetching data from Finviz: {e}") return data # Function to calculate beta def calculate_beta(ticker, start_date="2018-01-01", end_date=None, market_ticker="^GSPC"): stock_data = yf.download(ticker, start=start_date, end=end_date, auto_adjust=False) market_data = yf.download(market_ticker, start=start_date, end=end_date, auto_adjust=False) if isinstance(stock_data.columns, pd.MultiIndex): stock_data.columns = stock_data.columns.get_level_values(0) if isinstance(market_data.columns, pd.MultiIndex): market_data.columns = market_data.columns.get_level_values(0) if stock_data.empty or market_data.empty: raise ValueError(f"No data retrieved for {ticker} or {market_ticker}") if len(stock_data) < 2 or len(market_data) < 2: raise ValueError("Insufficient data points for beta calculation") stock_returns = stock_data['Adj Close'].pct_change().dropna() market_returns = market_data['Adj Close'].pct_change().dropna() combined_data = pd.concat([stock_returns, market_returns], axis=1).dropna() combined_data.columns = ['Stock_Returns', 'Market_Returns'] covariance_matrix = np.cov(combined_data['Stock_Returns'], combined_data['Market_Returns']) covariance = covariance_matrix[0, 1] market_variance = covariance_matrix[1, 1] beta = covariance / market_variance return beta # Function to get risk-free rate def get_risk_free_rate(risk_free_rate_ticker="^TYX"): treasury_data = yf.download(risk_free_rate_ticker, period="1d", auto_adjust=False) if isinstance(treasury_data.columns, pd.MultiIndex): treasury_data.columns = treasury_data.columns.get_level_values(0) if treasury_data.empty: raise ValueError(f"No data retrieved for {risk_free_rate_ticker}") risk_free_rate = treasury_data['Close'].iloc[-1] / 100 return risk_free_rate # Function to calculate market risk premium def calculate_market_risk_premium(market_ticker="^GSPC", start_date="2018-01-01", end_date=None, risk_free_rate_ticker="^TYX"): market_data = yf.download(market_ticker, start=start_date, end=end_date, auto_adjust=False) if isinstance(market_data.columns, pd.MultiIndex): market_data.columns = market_data.columns.get_level_values(0) if market_data.empty: raise ValueError(f"No data retrieved for {market_ticker}") if len(market_data) < 2: raise ValueError("Insufficient data points for market risk premium calculation") market_returns = market_data['Adj Close'].pct_change().dropna() average_annual_market_return = market_returns.mean() * 252 risk_free_rate = get_risk_free_rate(risk_free_rate_ticker) market_risk_premium = average_annual_market_return - risk_free_rate return average_annual_market_return, market_risk_premium # Function to get financial statements def get_financial_statements(ticker, start_date, end_date): cash_flow_url = f"https://financialmodelingprep.com/api/v3/cash-flow-statement/{ticker}?period=quarter&limit=4&apikey={FMP_API_KEY}" balance_sheet_url = f"https://financialmodelingprep.com/api/v3/balance-sheet-statement/{ticker}?period=quarter&limit=1&apikey={FMP_API_KEY}" income_statement_url = f"https://financialmodelingprep.com/api/v3/income-statement/{ticker}?period=quarter&limit=4&apikey={FMP_API_KEY}" cash_flow_response = requests.get(cash_flow_url) balance_sheet_response = requests.get(balance_sheet_url) income_statement_response = requests.get(income_statement_url) cash_flow_data = cash_flow_response.json() balance_sheet_data = balance_sheet_response.json() income_statement_data = income_statement_response.json() if not cash_flow_data or not balance_sheet_data or not income_statement_data: st.error("Error fetching financial statement data.") return None cash_flow_df = pd.DataFrame(cash_flow_data) balance_sheet_df = pd.DataFrame(balance_sheet_data) income_statement_df = pd.DataFrame(income_statement_data) # Calculate TTM cash flow by summing the last 4 quarters cash_flow_ttm = cash_flow_df.iloc[:4].sum(numeric_only=True) financials = { "cash_flow": cash_flow_ttm, "cash_flow_raw": cash_flow_df, "balance_sheet": balance_sheet_df, "income_statement": income_statement_df } return financials # Function to calculate effective tax rate def calculate_effective_tax_rate(financials): income_statement_df = financials['income_statement'] total_tax_expense = income_statement_df['incomeTaxExpense'].sum() total_pre_tax_income = income_statement_df['incomeBeforeTax'].sum() effective_tax_rate = total_tax_expense / total_pre_tax_income return effective_tax_rate # Function to calculate cost of debt def calculate_cost_of_debt(financials, effective_tax_rate): income_statement_df = financials['income_statement'] balance_sheet_df = financials['balance_sheet'] total_debt = balance_sheet_df.loc[0, 'totalDebt'] total_interest_expense = income_statement_df['interestExpense'].sum() pre_tax_cost_of_debt = total_interest_expense / total_debt after_tax_cost_of_debt = pre_tax_cost_of_debt * (1 - effective_tax_rate) return after_tax_cost_of_debt # Function to calculate required return def calculate_required_return(risk_free_rate, beta, market_risk_premium): required_return = risk_free_rate + beta * market_risk_premium return required_return # Function to calculate cost of equity def calculate_cost_of_equity(beta, market_risk_premium, risk_free_rate): cost_of_equity = risk_free_rate + beta * market_risk_premium return cost_of_equity # Function to calculate WACC def calculate_wacc(cost_of_equity, cost_of_debt, effective_tax_rate, financials, market_cap): total_debt = financials['balance_sheet'].loc[0, 'totalDebt'] equity_value = market_cap total_value = equity_value + total_debt weight_of_equity = equity_value / total_value weight_of_debt = total_debt / total_value wacc = (weight_of_equity * cost_of_equity) + (weight_of_debt * cost_of_debt) return wacc # Function to project free cash flows def project_free_cash_flows(financials, EPS_growth_5Y, projections_years=10): recent_fcf = financials['cash_flow']['freeCashFlow'] initial_growth_rate = EPS_growth_5Y later_growth_rate = initial_growth_rate / 2 projected_fcf = [] for year in range(1, projections_years + 1): if year <= 5: fcf = recent_fcf * (1 + initial_growth_rate) ** year else: fcf = projected_fcf[-1] * (1 + later_growth_rate) projected_fcf.append(fcf) projection_years = [pd.Timestamp.today().year + i for i in range(1, projections_years + 1)] projected_fcf_df = pd.DataFrame({ 'Year': projection_years, 'Projected_FCF': projected_fcf }) return projected_fcf_df # Function to calculate terminal value def calculate_terminal_value(last_projected_fcf, required_return, terminal_growth_rate): terminal_value = (last_projected_fcf * (1 + terminal_growth_rate)) / (required_return - terminal_growth_rate) return terminal_value # Function to discount cash flows def discount_cash_flows(projected_fcf_df, terminal_value, wacc): projected_fcf_df['Discount_Factor'] = 1 / ((1 + wacc) ** projected_fcf_df.index) projected_fcf_df['Discounted_FCF'] = projected_fcf_df['Projected_FCF'] * projected_fcf_df['Discount_Factor'] terminal_discount_factor = 1 / ((1 + wacc) ** projected_fcf_df.shape[0]) discounted_terminal_value = terminal_value * terminal_discount_factor total_present_value = projected_fcf_df['Discounted_FCF'].sum() + discounted_terminal_value return total_present_value, projected_fcf_df # Function to calculate intrinsic value def calculate_intrinsic_value(total_present_value, financials, outstanding_shares): balance_sheet_df = financials['balance_sheet'] total_debt = balance_sheet_df.loc[0, 'totalDebt'] cash_and_equivalents = balance_sheet_df.loc[0, 'cashAndShortTermInvestments'] equity_value = total_present_value - total_debt + cash_and_equivalents intrinsic_value_per_share = equity_value / outstanding_shares return intrinsic_value_per_share # Function to get outstanding shares def get_outstanding_shares(ticker): url = f"https://financialmodelingprep.com/api/v4/shares_float?symbol={ticker}&apikey={FMP_API_KEY}" response = requests.get(url) shares_data = response.json() if not shares_data or 'outstandingShares' not in shares_data[0]: st.error(f"Error retrieving outstanding shares for {ticker}") return None outstanding_shares = shares_data[0]['outstandingShares'] return outstanding_shares # Function to get the DCF Valuation def get_dcf_valuation(ticker, api_key): url = f"https://financialmodelingprep.com/api/v3/discounted-cash-flow/{ticker}?apikey={api_key}" response = requests.get(url) if response.status_code == 200: data = response.json() if data: return data[0]['dcf'] return None # Function to get the Levered DCF Valuation def get_levered_dcf_valuation(ticker, api_key): url = f"https://financialmodelingprep.com/api/v4/advanced_levered_discounted_cash_flow?symbol={ticker}&apikey={api_key}" response = requests.get(url) if response.status_code == 200: data = response.json() if data: return data[0]['equityValuePerShare'] return None # Function to plot intrinsic value vs stock price def plot_intrinsic_value_vs_stock_price(analysis_results): fig = go.Figure(data=[ go.Bar(name='Current Stock Price', x=[analysis_results['Ticker']], y=[analysis_results['Current_Price']], marker_color='orange', text=[f"${analysis_results['Current_Price']:.2f}"], textposition='auto'), go.Bar(name='Intrinsic Value per Share', x=[analysis_results['Ticker']], y=[analysis_results['Intrinsic_Value_Per_Share']], marker_color='blue', text=[f"${analysis_results['Intrinsic_Value_Per_Share']:.2f}"], textposition='auto'), go.Bar(name='Automatic DCF Valuation', x=[analysis_results['Ticker']], y=[analysis_results['Automatic_DCF_Valuation']], marker_color='purple', text=[f"${analysis_results['Automatic_DCF_Valuation']:.2f}"], textposition='auto'), go.Bar(name='Levered DCF Valuation', x=[analysis_results['Ticker']], y=[analysis_results['Levered_DCF_Valuation']], marker_color='red', text=[f"${analysis_results['Levered_DCF_Valuation']:.2f}"], textposition='auto') ]) fig.update_layout(barmode='group', title='Intrinsic Value, DCF Valuations vs Current Stock Price', xaxis_title='Ticker', yaxis_title='Value', yaxis=dict(tickformat=".2f")) st.plotly_chart(fig, use_container_width=True) # Function to print key metrics def print_key_metrics(analysis_results): st.subheader("Key Metrics") # Convert large numbers to billions terminal_value_billion = analysis_results['Terminal_Value'] / 1e9 total_present_value_billion = analysis_results['Total_Present_Value'] / 1e9 shares_outstanding_billion = analysis_results['Outstanding_Shares'] / 1e9 market_cap_billion = (analysis_results['Current_Price'] * analysis_results['Outstanding_Shares']) / 1e9 col1, col2, col3 = st.columns(3) with col1: st.metric(label="Beta", value=f"{analysis_results['Beta']:.2f}") st.metric(label="Cost of Equity", value=f"{analysis_results['Cost_of_Equity']*100:.2f}%") st.metric(label="WACC", value=f"{analysis_results['WACC']*100:.2f}%") st.metric(label="Shares Outstanding", value=f"{shares_outstanding_billion:,.2f}B") with col2: st.metric(label="Risk-Free Rate", value=f"{analysis_results['Risk_Free_Rate']*100:.2f}%") st.metric(label="Cost of Debt (After-Tax)", value=f"{analysis_results['Cost_of_Debt']*100:.2f}%") st.metric(label="Terminal Value", value=f"${terminal_value_billion:,.2f}B") st.metric(label="Market Cap", value=f"${market_cap_billion:,.2f}B") with col3: st.metric(label="Expected Market Return", value=f"{analysis_results['Expected_Market_Return']*100:.2f}%") st.metric(label="Market Risk Premium", value=f"{analysis_results['Market_Risk_Premium']*100:.2f}%") st.metric(label="Total Present Value of Cash Flows", value=f"${total_present_value_billion:,.2f}B") st.metric(label="Effective Tax Rate", value=f"{analysis_results['Effective_Tax_Rate']*100:.2f}%") # Function to display financial dataframes def display_financial_dataframes(analysis_results): st.subheader("Cash Flow Statement") st.dataframe(analysis_results['Financials']['cash_flow_raw']) st.subheader("Income Statement") st.dataframe(analysis_results['Financials']['income_statement']) st.subheader("Balance Sheet") st.dataframe(analysis_results['Financials']['balance_sheet']) st.subheader("Projected and Discounted Free Cash Flows") st.dataframe(analysis_results['Projected_FCF']) # Function to perform DCF analysis def perform_dcf_analysis(ticker, start_date="2018-01-01", end_date=None, market_ticker="^GSPC", terminal_growth_rate=0.025, risk_free_rate_ticker="^TYX"): financials = get_financial_statements(ticker, start_date, end_date) if financials is None: return None effective_tax_rate = calculate_effective_tax_rate(financials) cost_of_debt = calculate_cost_of_debt(financials, effective_tax_rate) finviz_data = get_finviz_data(ticker) EPS_growth_5Y = finviz_data.get('EPS Next 5Y', 0.05) beta = calculate_beta(ticker, start_date=start_date, end_date=end_date, market_ticker=market_ticker) risk_free_rate = get_risk_free_rate(risk_free_rate_ticker) expected_market_return, market_risk_premium = calculate_market_risk_premium(market_ticker=market_ticker, start_date=start_date, end_date=end_date, risk_free_rate_ticker=risk_free_rate_ticker) required_return = calculate_required_return(risk_free_rate, beta, market_risk_premium) cost_of_equity = calculate_cost_of_equity(beta, market_risk_premium, risk_free_rate) current_price_data = yf.download(ticker, period="1d", auto_adjust=False) if isinstance(current_price_data.columns, pd.MultiIndex): current_price_data.columns = current_price_data.columns.get_level_values(0) if current_price_data.empty: raise ValueError(f"No current price data retrieved for {ticker}") current_price = current_price_data['Adj Close'].iloc[-1] outstanding_shares = get_outstanding_shares(ticker) market_cap = current_price * outstanding_shares wacc = calculate_wacc(cost_of_equity, cost_of_debt, effective_tax_rate, financials, market_cap) projected_fcf_df = project_free_cash_flows(financials, EPS_growth_5Y) last_projected_fcf = projected_fcf_df.iloc[-1]['Projected_FCF'] terminal_value = calculate_terminal_value(last_projected_fcf, required_return, terminal_growth_rate) total_present_value, discounted_fcf_df = discount_cash_flows(projected_fcf_df, terminal_value, wacc) intrinsic_value_per_share = calculate_intrinsic_value(total_present_value, financials, outstanding_shares) results = { "Ticker": ticker, "Beta": beta, "Risk_Free_Rate": risk_free_rate, "Expected_Market_Return": expected_market_return, "Market_Risk_Premium": market_risk_premium, "Required_Return": required_return, "Cost_of_Equity": cost_of_equity, "Cost_of_Debt": cost_of_debt, "WACC": wacc, "Terminal_Value": terminal_value, "Total_Present_Value": total_present_value, "Intrinsic_Value_Per_Share": intrinsic_value_per_share, "Current_Price": current_price, "Effective_Tax_Rate": effective_tax_rate, "Financials": financials, "Projected_FCF": projected_fcf_df, "Discounted_FCF": discounted_fcf_df, "Outstanding_Shares": outstanding_shares, } return results # Streamlit App st.set_page_config(layout="wide") st.title("Stock Price Fair Valuation with DCF") # Explanation of the analysis st.markdown(""" This app performs a Discounted Cash Flow (DCF) analysis to estimate the intrinsic value of a stock. It calculates the present value of expected future cash flows, factoring in growth rates, beta, cost of equity, cost of debt, and market conditions. """) with st.expander("DCF Analysis Overview", expanded=False): st.latex(r""" \text{DCF} = \sum_{t=1}^{n} \frac{\text{FCF}_t}{(1 + r)^t} + \frac{\text{TV}}{(1 + r)^n} """) st.markdown(""" - **Cost of Equity** is calculated using CAPM, considering the risk-free rate, the stock’s beta, and the market risk premium. - **Cost of Debt** is the effective interest rate on debt, adjusted for the tax benefit. - **WACC** combines the cost of equity and after-tax cost of debt, weighted by the company’s capital structure. It’s used as the discount rate in the DCF analysis. - **Free Cash Flow (FCF)** is the cash available after capital expenditures, used for projecting future cash flows. - **Terminal Value (TV)** estimates the company’s value beyond the forecast period using a terminal growth rate, which is a conservative long-term rate. - **Growth Rates** are the analysts' forecasts for EPS growth over the next 5 years, which are used to project near-term FCF. - **Intrinsic Value per Share** is the total present value of future cash flows divided by shares outstanding. - **Margin of Safety** is the difference between the intrinsic value and the market price, providing a buffer for investment decisions. For more details on the methodology, [click here](https://entreprenerdly.com/calculate-fair-value-of-stocks-using-dcf-with-public-data/). """) with st.sidebar: with st.expander("How to Use the App", expanded=False): st.markdown(""" 1. **Ticker Symbol**: Enter the stock ticker to analyze. 2. **Terminal Growth Rate**: Set the long-term growth rate. 3. **Start Date for Beta**: Choose the start date for beta calculation. 4. **Market Ticker**: Use the market index (e.g., `^GSPC`). 5. **Risk-Free Rate**: Select the ticker for the risk-free rate (e.g., `^TYX`). """) with st.expander("Input Parameters", expanded=True): ticker_symbol = st.text_input("Ticker Symbol", "NVDA", help="Enter the stock ticker symbol, e.g., 'AAPL' for Apple.") terminal_growth_rate = st.slider("Terminal Growth Rate", min_value=0.0, max_value=0.1, value=0.05, help="Adjust the terminal growth rate for the company's cash flows.") start_date_for_beta = st.date_input("Start Date for Beta Calculation", pd.to_datetime("2018-01-01"), help="Select the start date for calculating the stock's beta.") market_ticker_for_beta = st.text_input("Market Ticker for Beta Calculation", "^GSPC", help="Enter the market index ticker, e.g., '^GSPC' for S&P 500.") risk_free_rate_ticker = st.text_input("Risk-Free Rate Ticker", "^TYX", help="Enter the ticker for the risk-free rate, e.g., '^TYX' for the 30-year Treasury yield.") if st.sidebar.button("Run DCF Analysis"): try: with st.spinner("Performing DCF analysis..."): analysis_results = perform_dcf_analysis( ticker_symbol, start_date=start_date_for_beta.strftime('%Y-%m-%d'), market_ticker=market_ticker_for_beta, terminal_growth_rate=terminal_growth_rate, risk_free_rate_ticker=risk_free_rate_ticker ) if analysis_results: # Retrieve DCF and Levered DCF valuations automatic_dcf_valuation = get_dcf_valuation(ticker_symbol, FMP_API_KEY) levered_dcf_valuation = get_levered_dcf_valuation(ticker_symbol, FMP_API_KEY) # Add DCF valuations to the analysis results analysis_results['Automatic_DCF_Valuation'] = automatic_dcf_valuation analysis_results['Levered_DCF_Valuation'] = levered_dcf_valuation # Plot the results plot_intrinsic_value_vs_stock_price(analysis_results) # Print key metrics print_key_metrics(analysis_results) # Display financial dataframes display_financial_dataframes(analysis_results) else: st.error("DCF analysis failed. Check ticker or API key.") except Exception as e: st.error(f"An error occurred while running the analysis: {e}") hide_streamlit_style = """ """ st.markdown(hide_streamlit_style, unsafe_allow_html=True)