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}
%{customdata[0]} (%{customdata[1]})", textposition='outside', hovertemplate="Date: %{x}
Score: %{y}
Rating: %{customdata[0]}
Recommendation: %{customdata[1]}" ) 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="Date: %{x}
Count: %{y}
Rating Type: %{customdata[0]}" ) 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( """ """, unsafe_allow_html=True )