Spaces:
Running
Running
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 | |
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 | |
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 | |
) | |