Space29 / app.py
QuantumLearner's picture
Update app.py
e8235fe verified
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)