Space47 / app.py
QuantumLearner's picture
Update app.py
a7e2983 verified
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
@st.cache_data(show_spinner=False)
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()
@st.cache_data(show_spinner=False)
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()
@st.cache_data(show_spinner=False)
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)