Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import requests | |
| import pandas as pd | |
| import plotly.graph_objects as go | |
| from plotly.subplots import make_subplots | |
| import os | |
| import asyncio | |
| import aiohttp | |
| # Global parameters | |
| API_KEY = os.getenv("FMP_API_KEY") | |
| MAX_PAGES = 5 | |
| DEFAULT_TOP_N = 10 | |
| # Initialize session state run flags and inputs if not already set | |
| if "historical_run" not in st.session_state: | |
| st.session_state.historical_run = False | |
| if "trending_run" not in st.session_state: | |
| st.session_state.trending_run = False | |
| if "change_run" not in st.session_state: | |
| st.session_state.change_run = False | |
| if "historical_ticker" not in st.session_state: | |
| st.session_state.historical_ticker = "AAPL" | |
| if "trending_top_n" not in st.session_state: | |
| st.session_state.trending_top_n = DEFAULT_TOP_N | |
| if "change_top_n" not in st.session_state: | |
| st.session_state.change_top_n = DEFAULT_TOP_N | |
| ############################# | |
| # ASYNC FUNCTIONS FOR HISTORICAL SENTIMENT | |
| ############################# | |
| async def fetch_historical_page(session, ticker, page): | |
| url = ( | |
| f"https://financialmodelingprep.com/api/v4/historical/social-sentiment?" | |
| f"symbol={ticker}&page={page}&apikey={API_KEY}" | |
| ) | |
| try: | |
| async with session.get(url) as resp: | |
| if resp.status == 200: | |
| data = await resp.json() | |
| return pd.DataFrame(data) | |
| else: | |
| return pd.DataFrame() | |
| except Exception: | |
| return pd.DataFrame() | |
| async def fetch_all_historical(ticker, pages): | |
| async with aiohttp.ClientSession() as session: | |
| tasks = [fetch_historical_page(session, ticker, page) for page in range(pages)] | |
| results = [] | |
| progress_bar = st.progress(0) | |
| completed = 0 | |
| for coro in asyncio.as_completed(tasks): | |
| df = await coro | |
| results.append(df) | |
| completed += 1 | |
| progress_bar.progress(completed / pages) | |
| return results | |
| def get_historical_sentiment_async(ticker, pages=MAX_PAGES): | |
| results = asyncio.run(fetch_all_historical(ticker, pages)) | |
| if results: | |
| full_df = pd.concat(results, ignore_index=True) | |
| if "date" in full_df.columns: | |
| full_df["date"] = pd.to_datetime(full_df["date"], errors="coerce") | |
| full_df.sort_values("date", inplace=True) | |
| return full_df | |
| return pd.DataFrame() | |
| ############################# | |
| # ASYNC FUNCTION FOR TRENDING SENTIMENT | |
| ############################# | |
| async def fetch_trending_async(sentiment_type="bullish", source="stocktwits"): | |
| url = ( | |
| f"https://financialmodelingprep.com/api/v4/social-sentiments/trending?" | |
| f"type={sentiment_type}&source={source}&apikey={API_KEY}" | |
| ) | |
| async with aiohttp.ClientSession() as session: | |
| async with session.get(url) as resp: | |
| if resp.status == 200: | |
| data = await resp.json() | |
| return pd.DataFrame(data) | |
| else: | |
| return pd.DataFrame() | |
| def get_trending_sentiment_async(sentiment_type="bullish", source="stocktwits"): | |
| return asyncio.run(fetch_trending_async(sentiment_type, source)) | |
| ############################# | |
| # ASYNC FUNCTION FOR SENTIMENT CHANGE | |
| ############################# | |
| async def fetch_change_async(sentiment_type="bullish", source="stocktwits"): | |
| url = ( | |
| f"https://financialmodelingprep.com/api/v4/social-sentiments/change?" | |
| f"type={sentiment_type}&source={source}&apikey={API_KEY}" | |
| ) | |
| async with aiohttp.ClientSession() as session: | |
| async with session.get(url) as resp: | |
| if resp.status == 200: | |
| data = await resp.json() | |
| return pd.DataFrame(data) | |
| else: | |
| return pd.DataFrame() | |
| def get_change_sentiment_async(sentiment_type="bullish", source="stocktwits"): | |
| return asyncio.run(fetch_change_async(sentiment_type, source)) | |
| ############################# | |
| # PLOTTING FUNCTION (unchanged) | |
| ############################# | |
| def plot_dual_axes( | |
| df, x_col, y_left, y_right, | |
| title, y_left_label, y_right_label, | |
| left_color="red", right_color="blue", | |
| date_range=None | |
| ): | |
| fig = make_subplots(specs=[[{"secondary_y": True}]]) | |
| fig.add_trace( | |
| go.Scatter( | |
| x=df[x_col], | |
| y=df[y_left], | |
| mode="lines+markers", | |
| name=y_left_label, | |
| line=dict(color=left_color) | |
| ), | |
| secondary_y=False | |
| ) | |
| fig.add_trace( | |
| go.Scatter( | |
| x=df[x_col], | |
| y=df[y_right], | |
| mode="lines+markers", | |
| name=y_right_label, | |
| line=dict(color=right_color) | |
| ), | |
| secondary_y=True | |
| ) | |
| fig.update_layout( | |
| title=title, | |
| xaxis=dict(range=date_range), | |
| xaxis_title="Date", | |
| legend=dict( | |
| orientation="h", | |
| yanchor="bottom", | |
| y=1.02, | |
| xanchor="center", | |
| x=0.5 | |
| ), | |
| margin=dict(l=60, r=60, t=80, b=60), | |
| width=1600, | |
| height=800 | |
| ) | |
| fig.update_yaxes( | |
| title_text=y_left_label, | |
| secondary_y=False, | |
| tickfont_color=left_color, | |
| titlefont_color=left_color | |
| ) | |
| fig.update_yaxes( | |
| title_text=y_right_label, | |
| secondary_y=True, | |
| tickfont_color=right_color, | |
| titlefont_color=right_color | |
| ) | |
| return fig | |
| ############################# | |
| # PAGE 1: Historical Sentiment | |
| ############################# | |
| def run_historical_sentiment(ticker): | |
| st.write( | |
| "See how sentiment changes over time for a specific ticker. " | |
| "It retrieves past data on sentiment, posts, comments, likes, and impressions " | |
| "from two social platforms. This can help spot long-term shifts or trends." | |
| ) | |
| df = get_historical_sentiment_async(ticker) | |
| if df.empty: | |
| st.error("No data found for " + ticker) | |
| return | |
| common_range = [df["date"].min(), df["date"].max()] | |
| st.subheader(f"{ticker} Sentiment Scores") | |
| st.write("Compares sentiment scores from two social channels over the selected date range.") | |
| fig1 = plot_dual_axes( | |
| df, "date", "stocktwitsSentiment", "twitterSentiment", | |
| f"{ticker} Sentiment Scores", | |
| "Stocktwits Sentiment", "Twitter Sentiment", | |
| date_range=common_range | |
| ) | |
| st.plotly_chart(fig1, use_container_width=True) | |
| st.subheader(f"{ticker} Posts") | |
| st.write("Tracks the number of posts on each platform. High volumes can hint at heightened interest.") | |
| fig2 = plot_dual_axes( | |
| df, "date", "stocktwitsPosts", "twitterPosts", | |
| f"{ticker} Posts", | |
| "Stocktwits Posts", "Twitter Posts", | |
| date_range=common_range | |
| ) | |
| st.plotly_chart(fig2, use_container_width=True) | |
| st.subheader(f"{ticker} Comments") | |
| st.write("Shows how many comments occurred. This may reveal engagement depth.") | |
| fig3 = plot_dual_axes( | |
| df, "date", "stocktwitsComments", "twitterComments", | |
| f"{ticker} Comments", | |
| "Stocktwits Comments", "Twitter Comments", | |
| date_range=common_range | |
| ) | |
| st.plotly_chart(fig3, use_container_width=True) | |
| st.subheader(f"{ticker} Likes") | |
| st.write("Compares the likes on each platform. More likes can signal positive reception.") | |
| fig4 = plot_dual_axes( | |
| df, "date", "stocktwitsLikes", "twitterLikes", | |
| f"{ticker} Likes", | |
| "Stocktwits Likes", "Twitter Likes", | |
| date_range=common_range | |
| ) | |
| st.plotly_chart(fig4, use_container_width=True) | |
| st.subheader(f"{ticker} Impressions") | |
| st.write("Shows overall impressions. Higher impressions may reflect broader visibility.") | |
| fig5 = plot_dual_axes( | |
| df, "date", "stocktwitsImpressions", "twitterImpressions", | |
| f"{ticker} Impressions", | |
| "Stocktwits Impressions", "Twitter Impressions", | |
| date_range=common_range | |
| ) | |
| st.plotly_chart(fig5, use_container_width=True) | |
| ############################# | |
| # PAGE 2: Trending Sentiment | |
| ############################# | |
| def run_trending_sentiment(top_n): | |
| st.write( | |
| "See which symbols rank highest in bullish or bearish sentiment right now. " | |
| "This helps pinpoint active tickers with strong positive or negative views." | |
| ) | |
| st.write("## Bullish Trending") | |
| bullish_df = get_trending_sentiment_async(sentiment_type="bullish", source="stocktwits") | |
| if bullish_df.empty: | |
| st.error("No bullish trending data available.") | |
| else: | |
| bullish_df = bullish_df.sort_values("rank").reset_index(drop=True) | |
| df_bull = bullish_df.head(top_n) | |
| fig = go.Figure() | |
| fig.add_trace( | |
| go.Bar( | |
| x=df_bull["symbol"], | |
| y=df_bull["sentiment"], | |
| name="Sentiment", | |
| text=df_bull["sentiment"], | |
| textposition="auto", | |
| marker_color="red" | |
| ) | |
| ) | |
| fig.add_trace( | |
| go.Bar( | |
| x=df_bull["symbol"], | |
| y=df_bull["lastSentiment"], | |
| name="Last Sentiment", | |
| text=df_bull["lastSentiment"], | |
| textposition="auto", | |
| marker_color="blue", | |
| yaxis="y2" | |
| ) | |
| ) | |
| fig.update_layout( | |
| title="Bullish Trending", | |
| barmode="group", | |
| xaxis=dict(title="Ticker", type="category", categoryorder="array", categoryarray=df_bull["symbol"].tolist()), | |
| yaxis=dict(title="Sentiment", titlefont=dict(color="red"), tickfont=dict(color="red")), | |
| yaxis2=dict(title="Last Sentiment", titlefont=dict(color="blue"), tickfont=dict(color="blue"), anchor="x", overlaying="y", side="right"), | |
| width=1000 + 100 * len(df_bull), | |
| height=600 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| st.write("## Bearish Trending") | |
| bearish_df = get_trending_sentiment_async(sentiment_type="bearish", source="stocktwits") | |
| if bearish_df.empty: | |
| st.error("No bearish trending data available.") | |
| else: | |
| bearish_df = bearish_df.sort_values("rank").reset_index(drop=True) | |
| df_bear = bearish_df.head(top_n) | |
| fig = go.Figure() | |
| fig.add_trace( | |
| go.Bar( | |
| x=df_bear["symbol"], | |
| y=df_bear["sentiment"], | |
| name="Sentiment", | |
| text=df_bear["sentiment"], | |
| textposition="auto", | |
| marker_color="red" | |
| ) | |
| ) | |
| fig.add_trace( | |
| go.Bar( | |
| x=df_bear["symbol"], | |
| y=df_bear["lastSentiment"], | |
| name="Last Sentiment", | |
| text=df_bear["lastSentiment"], | |
| textposition="auto", | |
| marker_color="blue", | |
| yaxis="y2" | |
| ) | |
| ) | |
| fig.update_layout( | |
| title="Bearish Trending", | |
| barmode="group", | |
| xaxis=dict(title="Ticker", type="category", categoryorder="array", categoryarray=df_bear["symbol"].tolist()), | |
| yaxis=dict(title="Sentiment", titlefont=dict(color="red"), tickfont=dict(color="red")), | |
| yaxis2=dict(title="Last Sentiment", titlefont=dict(color="blue"), tickfont=dict(color="blue"), anchor="x", overlaying="y", side="right"), | |
| width=1000 + 100 * len(df_bear), | |
| height=600 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| ############################# | |
| # PAGE 3: Sentiment Change | |
| ############################# | |
| def run_change_sentiment(top_n): | |
| st.write("**Sentiment Change**") | |
| st.write( | |
| "See if sentiment is trending up or down for each ticker. " | |
| "Compare the current sentiment to how much it has shifted. This can reveal rapid changes." | |
| ) | |
| st.write("## Bullish Sentiment Change") | |
| bullish_df = get_change_sentiment_async(sentiment_type="bullish", source="stocktwits") | |
| if bullish_df.empty: | |
| st.error("No bullish sentiment change data available.") | |
| else: | |
| bullish_df = bullish_df.sort_values("rank").reset_index(drop=True) | |
| df_bull = bullish_df.head(top_n) | |
| fig = go.Figure() | |
| fig.add_trace( | |
| go.Bar( | |
| x=df_bull["symbol"], | |
| y=df_bull["sentiment"], | |
| name="Sentiment", | |
| text=df_bull["sentiment"], | |
| textposition="auto", | |
| marker_color="red" | |
| ) | |
| ) | |
| fig.add_trace( | |
| go.Bar( | |
| x=df_bull["symbol"], | |
| y=df_bull["sentimentChange"], | |
| name="Sentiment Change", | |
| text=df_bull["sentimentChange"], | |
| textposition="auto", | |
| marker_color="blue", | |
| yaxis="y2" | |
| ) | |
| ) | |
| fig.update_layout( | |
| title="Bullish Sentiment Change", | |
| barmode="group", | |
| xaxis=dict(title="Ticker", type="category", categoryorder="array", categoryarray=df_bull["symbol"].tolist()), | |
| yaxis=dict(title="Sentiment", titlefont=dict(color="red"), tickfont=dict(color="red")), | |
| yaxis2=dict(title="Sentiment Change", titlefont=dict(color="blue"), tickfont=dict(color="blue"), anchor="x", overlaying="y", side="right"), | |
| width=1000 + 100 * len(df_bull), | |
| height=600 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| st.write("## Bearish Sentiment Change") | |
| bearish_df = get_change_sentiment_async(sentiment_type="bearish", source="stocktwits") | |
| if bearish_df.empty: | |
| st.error("No bearish sentiment change data available.") | |
| else: | |
| bearish_df = bearish_df.sort_values("rank").reset_index(drop=True) | |
| df_bear = bearish_df.head(top_n) | |
| fig = go.Figure() | |
| fig.add_trace( | |
| go.Bar( | |
| x=df_bear["symbol"], | |
| y=df_bear["sentiment"], | |
| name="Sentiment", | |
| text=df_bear["sentiment"], | |
| textposition="auto", | |
| marker_color="red" | |
| ) | |
| ) | |
| fig.add_trace( | |
| go.Bar( | |
| x=df_bear["symbol"], | |
| y=df_bear["sentimentChange"], | |
| name="Sentiment Change", | |
| text=df_bear["sentimentChange"], | |
| textposition="auto", | |
| marker_color="blue", | |
| yaxis="y2" | |
| ) | |
| ) | |
| fig.update_layout( | |
| title="Bearish Sentiment Change", | |
| barmode="group", | |
| xaxis=dict(title="Ticker", type="category", categoryorder="array", categoryarray=df_bear["symbol"].tolist()), | |
| yaxis=dict(title="Sentiment", titlefont=dict(color="red"), tickfont=dict(color="red")), | |
| yaxis2=dict(title="Sentiment Change", titlefont=dict(color="blue"), tickfont=dict(color="blue"), anchor="x", overlaying="y", side="right"), | |
| width=1000 + 100 * len(df_bear), | |
| height=600 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| ############################# | |
| # MAIN APP | |
| ############################# | |
| def main(): | |
| st.set_page_config(page_title="Social Sentiment Analysis", layout="wide") | |
| st.title("Social Sentiment Analysis") | |
| st.write( | |
| "Track social sentiment in three ways: " | |
| "**Historical Sentiment** shows how sentiment evolved over time for a specific stock. " | |
| "**Trending Sentiment** highlights the most talked-about stocks right now. " | |
| "**Sentiment Change** shows which stocks have had the biggest sentiment shifts recently. " | |
| ) | |
| with st.sidebar.expander("Navigation and Options", expanded=True): | |
| page = st.radio( | |
| "Select Analysis Page", | |
| ("Historical Sentiment", "Trending Sentiment", "Sentiment Change"), | |
| help="Choose which sentiment analysis you want." | |
| ) | |
| if page == "Historical Sentiment": | |
| ticker = st.text_input( | |
| "Ticker Symbol", | |
| value=st.session_state.historical_ticker, | |
| help="Enter a ticker symbol for historical data." | |
| ) | |
| if st.button("Run Analysis"): | |
| st.session_state.historical_run = True | |
| st.session_state.historical_ticker = ticker | |
| elif page == "Trending Sentiment": | |
| top_n = st.number_input( | |
| "Select Top N Stocks", | |
| min_value=1, | |
| max_value=100, | |
| value=st.session_state.trending_top_n, | |
| help="Number of top stocks to show based on rank." | |
| ) | |
| if st.button("Run Analysis"): | |
| st.session_state.trending_run = True | |
| st.session_state.trending_top_n = top_n | |
| elif page == "Sentiment Change": | |
| top_n = st.number_input( | |
| "Select Top N Stocks", | |
| min_value=1, | |
| max_value=100, | |
| value=st.session_state.change_top_n, | |
| help="Number of top stocks to show based on rank." | |
| ) | |
| if st.button("Run Analysis"): | |
| st.session_state.change_run = True | |
| st.session_state.change_top_n = top_n | |
| if page == "Historical Sentiment": | |
| st.header("Historical Social Sentiment") | |
| if st.session_state.historical_run: | |
| run_historical_sentiment(st.session_state.historical_ticker) | |
| else: | |
| st.info("Enter a ticker and click the button.") | |
| elif page == "Trending Sentiment": | |
| st.header("Trending Social Sentiment") | |
| if st.session_state.trending_run: | |
| run_trending_sentiment(st.session_state.trending_top_n) | |
| else: | |
| st.info("Pick a top N and click the button.") | |
| elif page == "Sentiment Change": | |
| st.header("Social Sentiment Change") | |
| if st.session_state.change_run: | |
| run_change_sentiment(st.session_state.change_top_n) | |
| else: | |
| st.info("Pick a top N and click the button.") | |
| if __name__ == "__main__": | |
| main() | |
| hide_streamlit_style = """ | |
| <style> | |
| #MainMenu {visibility: hidden;} | |
| footer {visibility: hidden;} | |
| </style> | |
| """ | |
| st.markdown(hide_streamlit_style, unsafe_allow_html=True) |