Space61 / app.py
QuantumLearner's picture
Update app.py
c6925d9 verified
import streamlit as st
import pandas as pd
import requests
import plotly.express as px
from datetime import datetime, timedelta
import textwrap
import os
API_KEY = os.getenv("FMP_API_KEY")
# Set wide page layout
st.set_page_config(page_title="Analyst Recommendations", layout="wide")
# Sidebar: Global ticker and page navigation
st.sidebar.header("Inputs")
with st.sidebar.expander("Ticker and Page Settings", expanded=True):
ticker = st.text_input(
"Enter Ticker Symbol",
value="AAPL",
help="Input the stock ticker symbol (e.g., AAPL, MSFT)."
)
page = st.radio(
"Select Page",
["Historical Ratings", "Recommendations"],
help="Choose which analysis page to view."
)
# Reset stored results if ticker changes
if "ticker" not in st.session_state or st.session_state.ticker != ticker:
st.session_state.ticker = ticker
st.session_state.historical_data = None
st.session_state.analyst_data = None
st.session_state.run_pressed_hist = False
st.session_state.run_pressed_analyst = False
# Cached function for historical data
@st.cache_data(show_spinner=False)
def load_historical_data(ticker):
try:
url = f"https://financialmodelingprep.com/api/v3/historical-rating/{ticker}?apikey={API_KEY}"
response = requests.get(url)
if response.status_code != 200:
st.error("Error retrieving historical data.")
return None
data = response.json()
df = pd.DataFrame(data)
# Define required columns.
req_cols = [
'date', 'rating', 'ratingScore', 'ratingRecommendation', 'ratingDetailsDCFScore',
'ratingDetailsROEScore', 'ratingDetailsROERecommendation',
'ratingDetailsROAScore', 'ratingDetailsROARecommendation',
'ratingDetailsDEScore', 'ratingDetailsDERecommendation',
'ratingDetailsPEScore', 'ratingDetailsPERecommendation',
'ratingDetailsPBScore', 'ratingDetailsPBRecommendation'
]
# Use alternative column if available.
if 'ratingDetailsROCFRecommendation' in df.columns:
req_cols.insert(4, 'ratingDetailsROCFRecommendation')
else:
req_cols.insert(4, 'ratingDetailsDCFRecommendation')
df = df[req_cols]
df['date'] = pd.to_datetime(df['date'])
df = df.sort_values('date')
return df
except Exception:
st.error("An error occurred while loading historical data.")
return None
# Cached function for analyst recommendations data
@st.cache_data(show_spinner=False)
def load_analyst_data(ticker):
try:
url = f"https://financialmodelingprep.com/api/v3/analyst-stock-recommendations/{ticker}?apikey={API_KEY}"
response = requests.get(url)
if response.status_code != 200:
st.error("Error retrieving analyst data.")
return None
data = response.json()
if isinstance(data, dict):
data = [data]
df = pd.DataFrame(data)
df['date'] = pd.to_datetime(df['date'])
df = df.sort_values('date')
return df
except Exception:
st.error("An error occurred while loading analyst data.")
return None
# Main area explanation
#st.title("Analysts Recommendations")
#st.write("This app displays historical rating scores and analyst recommendations for the specified ticker.")
# PAGE: Historical Ratings
if page == "Historical Ratings":
st.header("Historical Ratings")
# Sidebar inputs for this page
st.write("Below are a series of bar charts that show historical trends in key financial metrics.")
with st.expander("Category Description", expanded=False):
description = textwrap.dedent("""
- **Overall Rating Score**: Reflects the general analyst rating, summarizing overall performance.
- **DCF Score**: Represents the discounted cash flow valuation, which estimates a stock's intrinsic value.
- **ROE Score**: Measures return on equity to assess how efficiently a company uses shareholder funds.
- **ROA Score**: Indicates return on assets to gauge the effectiveness of asset use in generating profits.
- **DE Score**: Shows the debt-to-equity ratio, highlighting the financial leverage of the company.
- **PE Score**: Provides the price-to-earnings ratio, indicating market valuation relative to earnings.
- **PB Score**: Measures the price-to-book ratio to assess if the stock is undervalued compared to its book value.
Each chart displays the numerical score with a text recommendation. Colors denote recommendations like "Strong Buy", "Buy", "Neutral", "Sell", and "Strong Sell".
""")
st.markdown(description)
default_date = datetime.today() - timedelta(days=365)
with st.sidebar.expander("Date Settings", expanded=True):
start_date = st.date_input(
"Start Date",
value=default_date,
help="Select a start date for filtering historical data."
)
# Place run button below the start date input
if st.sidebar.button("Run Analysis", key="hist_run_button"):
st.session_state.run_pressed_hist = True
if st.session_state.run_pressed_hist:
# Load data if not already loaded
if st.session_state.historical_data is None:
st.session_state.historical_data = load_historical_data(ticker)
df_hist = st.session_state.historical_data
if df_hist is not None:
# Filter data based on start date.
df_filtered = df_hist[df_hist['date'] >= pd.to_datetime(start_date)]
#st.subheader(f"Historical Ratings for {ticker}")
#st.write("Bar charts below show various score metrics over time.")
recommendation_colors = {
"Strong Buy": "green",
"Buy": "lightgreen",
"Neutral": "orange",
"Sell": "lightcoral",
"Strong Sell": "red"
}
categories = [
'ratingScore', 'ratingDetailsDCFScore', 'ratingDetailsROEScore',
'ratingDetailsROAScore', 'ratingDetailsDEScore', 'ratingDetailsPEScore',
'ratingDetailsPBScore'
]
titles = [
'Overall Rating Score', 'DCF Score', 'ROE Score',
'ROA Score', 'DE Score', 'PE Score', 'PB Score'
]
for category, title in zip(categories, titles):
recommendation_col = category.replace("Score", "Recommendation")
st.markdown(f"**{title} Chart**")
#st.write("This chart shows the score over time with its associated recommendation.")
try:
fig = px.bar(
df_filtered,
x='date',
y=category,
text=category,
labels={'date': 'Date', category: 'Score'},
title=title,
color=recommendation_col,
color_discrete_map=recommendation_colors,
custom_data=['rating', recommendation_col]
)
fig.update_traces(
texttemplate="%{text}<br>%{customdata[0]} (%{customdata[1]})",
textposition='outside',
hovertemplate="<b>Date</b>: %{x}<br><b>Score</b>: %{y}<br><b>Rating</b>: %{customdata[0]}<br><b>Recommendation</b>: %{customdata[1]}<extra></extra>"
)
st.plotly_chart(fig, use_container_width=True)
except Exception:
st.error("Error displaying the chart. Please check the data and inputs.")
with st.expander("Show Detailed Historical Data"):
st.dataframe(df_filtered)
else:
st.info("Press 'Run Analysis' in the sidebar to load historical ratings data.")
# PAGE: Analyst Recommendations
elif page == "Recommendations":
st.header("Analyst Recommendations")
st.write("This section presents the monthly analyst recommendations for the selected ticker. The stacked bar chart aggregates various recommendation types over time.")
with st.expander("Category Description", expanded=False):
description = textwrap.dedent("""
- **Strong Buy**: Indicates that analysts are very confident the stock will perform strongly.
- **Buy**: Reflects a positive outlook from analysts regarding future performance.
- **Hold**: Suggests that analysts expect the stock to maintain its current performance.
- **Sell**: Signals a negative outlook, indicating the stock may underperform.
- **Strong Sell**: Represents a very bearish sentiment, with analysts expecting significant underperformance.
The chart lets you observe shifts in market sentiment over time and compare the prevalence of each recommendation type.
""")
st.markdown(description)
# No additional page-specific inputs; thus, no expander is shown.
if st.sidebar.button("Run Analysis", key="analyst_run_button"):
st.session_state.run_pressed_analyst = True
if st.session_state.run_pressed_analyst:
if st.session_state.analyst_data is None:
st.session_state.analyst_data = load_analyst_data(ticker)
df_analyst = st.session_state.analyst_data
if df_analyst is not None:
st.subheader(f"Analyst Recommendations for {ticker}")
st.write("The stacked bar chart below shows monthly analyst recommendations.")
rating_cols = [
"analystRatingsStrongBuy",
"analystRatingsbuy",
"analystRatingsHold",
"analystRatingsSell",
"analystRatingsStrongSell"
]
df_melted = pd.melt(
df_analyst,
id_vars=["date"],
value_vars=rating_cols,
var_name="RatingType",
value_name="Count"
)
color_map = {
"analystRatingsStrongBuy": "green",
"analystRatingsbuy": "lightgreen",
"analystRatingsHold": "orange",
"analystRatingsSell": "lightcoral",
"analystRatingsStrongSell": "red"
}
rating_order = [
"analystRatingsStrongBuy",
"analystRatingsbuy",
"analystRatingsHold",
"analystRatingsSell",
"analystRatingsStrongSell"
]
try:
fig2 = px.bar(
df_melted,
x="date",
y="Count",
color="RatingType",
text="Count",
title=f"Monthly Analyst Recommendations for {ticker.upper()}",
color_discrete_map=color_map,
category_orders={"RatingType": rating_order},
custom_data=["RatingType"],
labels={"date": "Date", "Count": "Number of Recommendations", "RatingType": "Recommendation Type"}
)
fig2.update_layout(barmode="stack")
fig2.update_traces(
texttemplate="%{text}",
textposition="inside",
hovertemplate="<b>Date</b>: %{x}<br><b>Count</b>: %{y}<br><b>Rating Type</b>: %{customdata[0]}<extra></extra>"
)
st.plotly_chart(fig2, use_container_width=True)
except Exception:
st.error("Error displaying the chart. Please check the data and inputs.")
with st.expander("Show Detailed Analyst Data"):
st.dataframe(df_analyst)
else:
st.info("Press 'Run Analysis' in the sidebar to load analyst recommendations data.")
# Hide default Streamlit style
st.markdown(
"""
<style>
#MainMenu {visibility: hidden;}
footer {visibility: hidden;}
</style>
""",
unsafe_allow_html=True
)