Spaces:
Sleeping
Sleeping
| 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 = """ | |
| <style> | |
| #MainMenu {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| </style> | |
| """ | |
| st.markdown(hide_streamlit_style, unsafe_allow_html=True) | |