File size: 18,266 Bytes
7b73ee2
 
 
 
 
 
a7e2983
 
7b73ee2
a7e2983
24b153d
7b73ee2
 
 
a7e2983
7b73ee2
 
 
 
 
 
 
 
 
 
 
 
 
 
a7e2983
7b73ee2
a7e2983
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7b73ee2
 
a7e2983
 
 
 
 
 
 
7b73ee2
 
 
a7e2983
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7b73ee2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a7e2983
 
 
7b73ee2
6db809f
7ea108d
6db809f
 
 
a7e2983
7b73ee2
b23c14e
7b73ee2
 
 
6db809f
7b73ee2
 
 
 
 
 
 
 
6db809f
7b73ee2
 
 
 
 
 
 
 
6db809f
7b73ee2
 
 
 
 
 
 
 
6db809f
7b73ee2
 
 
 
 
 
 
 
6db809f
7b73ee2
 
 
 
 
 
 
 
 
 
 
 
6db809f
7ea108d
6db809f
 
adfd04e
a7e2983
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
adfd04e
a7e2983
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7b73ee2
a7e2983
 
 
 
 
 
 
 
 
 
7b73ee2
a7e2983
 
 
 
 
 
 
 
7b73ee2
a7e2983
7b73ee2
a7e2983
 
 
7b73ee2
6db809f
 
7ea108d
6db809f
 
7b73ee2
a7e2983
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7b73ee2
a7e2983
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6db809f
7b73ee2
 
 
 
 
4a687f8
7b73ee2
7ea108d
e2b227a
 
 
7b73ee2
 
 
 
 
6db809f
7b73ee2
 
 
 
 
6db809f
7b73ee2
7debc9f
7b73ee2
 
 
 
 
5e6be38
 
7b73ee2
b23c14e
7b73ee2
7a2dab7
7b73ee2
 
 
 
 
5e6be38
 
7b73ee2
b23c14e
7b73ee2
7debc9f
7b73ee2
 
 
 
6f30b70
7b73ee2
 
 
6db809f
7b73ee2
6f30b70
7b73ee2
 
 
6db809f
7b73ee2
6f30b70
7b73ee2
 
 
6db809f
7b73ee2
 
 
 
 
 
 
 
 
 
a7e2983
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
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)