LinkLinkWu commited on
Commit
829be4d
·
verified ·
1 Parent(s): e179b75

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +51 -117
app.py CHANGED
@@ -1,143 +1,77 @@
1
- """app.py – Streamlit front‑end for EquiPulse (refactored 2025‑05‑18)
2
-
3
- Key fixes / improvements
4
- ------------------------
5
- * **Updated API** – compatible with new `func.analyze_sentiment` (returns label
6
- string; optional prob via `return_prob=True`).
7
- * **Robust counting**ensure list of labels correct `.count()` stats.
8
- * Cleaner progress indication and error handling.
9
- * Minor UI tweaks (lighter colours, wider title, link targets `_blank`).
10
-
11
- Run via: ``streamlit run app.py``
 
12
  """
13
 
14
  from __future__ import annotations
15
 
16
- import time
17
  import streamlit as st
18
-
19
  from func import (
20
- get_sentiment_pipeline,
21
- get_ner_pipeline,
22
- fetch_news,
23
  analyze_sentiment,
 
 
24
  extract_org_entities,
25
  )
26
 
27
- # ---------------------------------------------------------------------------
28
- # Streamlit page configuration & CSS
29
- # ---------------------------------------------------------------------------
30
- st.set_page_config(
31
- page_title="EquiPulse: Stock Sentiment Tracker",
32
- layout="centered",
33
- initial_sidebar_state="collapsed",
34
- )
35
 
36
- _PRIMARY = "#002B45" # brand colour
37
-
38
- st.markdown(
39
- f"""
40
- <style>
41
- body {{ background-color: #ffffff; }}
42
- /* Title */
43
- .centered-title {{
44
- text-align: center;
45
- font-size: 38px;
46
- font-weight: 800;
47
- font-family: 'Segoe UI', sans-serif;
48
- color: {_PRIMARY};
49
- margin: 24px 0 16px;
50
- }}
51
- /* Inputs & buttons */
52
- .stTextInput > div > div > input,
53
- .stTextArea textarea {{ font-size: 16px; }}
54
- .stButton > button {{
55
- background-color: {_PRIMARY};
56
- color: #fff;
57
- font-size: 16px;
58
- padding: 0.4rem 1.1rem;
59
- border-radius: 6px;
60
- }}
61
- .stButton > button:hover {{ background-color: #004b78; }}
62
- </style>
63
- """,
64
- unsafe_allow_html=True,
65
- )
66
 
67
- st.markdown(
68
- f"<div class='centered-title'>📊 EquiPulse: Stock Sentiment Tracker</div>",
69
- unsafe_allow_html=True,
70
- )
71
-
72
- st.markdown(
73
- """
74
- <div style='font-size:16px; line-height:1.6; color:#333;'>
75
- Analyze real‑time financial sentiment from recent news headlines.
76
- </div>
77
- """,
78
- unsafe_allow_html=True,
79
- )
80
-
81
- # ---------------------------------------------------------------------------
82
- # Input area
83
- # ---------------------------------------------------------------------------
84
- st.markdown("#### 🎯 Enter Company Names or Tickers")
85
- user_text = st.text_area("Example: Apple, AAPL, Tesla, NVDA", height=90)
86
-
87
- # Extract tickers via NER (rough heuristic)
88
  ner_pipe = get_ner_pipeline()
89
- extracted = extract_org_entities(user_text, ner_pipe)
90
-
91
  if extracted:
92
- st.markdown(f"✅ <strong>Recognized Tickers:</strong> {', '.join(extracted)}", unsafe_allow_html=True)
93
 
94
- # ---------------------------------------------------------------------------
95
- # Action – fetch news & analyse sentiment
96
- # ---------------------------------------------------------------------------
97
- if st.button("🔍 Get News and Sentiment"):
98
  if not extracted:
99
- st.warning("⚠️ Please enter at least one recognizable company or ticker.")
100
  st.stop()
101
 
102
- sent_pipe = get_sentiment_pipeline() # For potential use via `pipe=` arg
103
  progress = st.progress(0.0)
104
 
105
  for idx, ticker in enumerate(extracted, start=1):
106
- st.markdown(f"---\n#### 🔍 Analysing `{ticker}`")
107
  news_items = fetch_news(ticker)
108
 
109
- if news_items:
110
- # Per‑headline sentiment labels
111
- sentiments = [analyze_sentiment(item["title"]) for item in news_items]
112
-
113
- pos_cnt = sentiments.count("Positive")
114
- neg_cnt = sentiments.count("Negative")
115
- total = len(sentiments)
116
- pos_ratio = pos_cnt / total if total else 0.0
117
- neg_ratio = neg_cnt / total if total else 0.0
118
-
119
- # Heuristic overall sentiment
120
- if pos_ratio >= 0.25:
121
- overall = "Positive"
122
- elif neg_ratio >= 0.75:
123
- overall = "Negative"
124
- else:
125
- overall = "Neutral"
126
-
127
- # Display top‑3 headlines
128
- st.markdown(f"**📰 Top News for `{ticker}`:**")
129
- for i, item in enumerate(news_items[:3]):
130
- label = sentiments[i]
131
- st.markdown(
132
- f"{i+1}. <a href='{item['link']}' target='_blank'>{item['title']}</a> — **{label}**",
133
- unsafe_allow_html=True,
134
- )
135
-
136
- st.success(f"📈 **Overall Sentiment for `{ticker}`: {overall}**")
137
- else:
138
- st.info(f"No recent news found for `{ticker}`.")
139
 
 
 
 
140
  progress.progress(idx / len(extracted))
141
- time.sleep(0.1)
142
 
143
- progress.empty() # remove bar when done
 
1
+ """app.py – Streamlit front‑end for EquiPulse
2
+ Logic tweak: remove possibility of overall "Neutral" (2025‑05‑18).
3
+ UI unchanged from prior version.
4
+
5
+ Changes
6
+ =======
7
+ * Overall sentiment now binary Positive or Negative based on which
8
+ ratio is higher (≥50%).
9
+ * Headline‑level labels already binary via `func.analyze_sentiment`, so
10
+ neutral will never appear anywhere.
11
+
12
+ Run with `streamlit run app.py`.
13
  """
14
 
15
  from __future__ import annotations
16
 
 
17
  import streamlit as st
 
18
  from func import (
 
 
 
19
  analyze_sentiment,
20
+ fetch_news,
21
+ get_ner_pipeline,
22
  extract_org_entities,
23
  )
24
 
25
+ # ------------------------------------------------------------
26
+ # Page title & instructions (original minimalist UI)
27
+ # ------------------------------------------------------------
28
+ st.title("📊 EquiPulse – Stock Sentiment Tracker")
29
+ st.write("Enter company names or tickers (comma‑separated) and click **Analyze**.")
 
 
 
30
 
31
+ user_input = st.text_input("Companies / Tickers", placeholder="Apple, AAPL, Tesla, NVDA")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
+ # Ticker extraction
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  ner_pipe = get_ner_pipeline()
35
+ extracted = extract_org_entities(user_input, ner_pipe)
 
36
  if extracted:
37
+ st.info(f"Recognized tickers: {', '.join(extracted)}")
38
 
39
+ # ------------------------------------------------------------
40
+ # Fetch news & sentiment on button click
41
+ # ------------------------------------------------------------
42
+ if st.button("Analyze"):
43
  if not extracted:
44
+ st.warning("Please enter at least one valid company or ticker.")
45
  st.stop()
46
 
 
47
  progress = st.progress(0.0)
48
 
49
  for idx, ticker in enumerate(extracted, start=1):
50
+ st.subheader(f"Results for {ticker}")
51
  news_items = fetch_news(ticker)
52
 
53
+ if not news_items:
54
+ st.write("No recent news found.")
55
+ progress.progress(idx / len(extracted))
56
+ continue
57
+
58
+ # Headline‑level sentiment (binary)
59
+ sentiments = [analyze_sentiment(item["title"]) for item in news_items]
60
+
61
+ pos_cnt = sentiments.count("Positive")
62
+ neg_cnt = sentiments.count("Negative")
63
+ total = len(sentiments)
64
+
65
+ # Binary overall judgement – whichever ratio is higher
66
+ overall = "Positive" if pos_cnt >= neg_cnt else "Negative"
67
+
68
+ # Display first few headlines
69
+ for i, item in enumerate(news_items[:5]):
70
+ st.write(f"{i+1}. {item['title']} — **{sentiments[i]}**")
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
+ st.success(
73
+ f"Overall Sentiment: {overall} (Pos: {pos_cnt}/{total}, Neg: {neg_cnt}/{total})"
74
+ )
75
  progress.progress(idx / len(extracted))
 
76
 
77
+ progress.empty()