KYTHY commited on
Commit
c40e7ab
·
verified ·
1 Parent(s): 6a9e024

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +193 -169
app.py CHANGED
@@ -38,16 +38,6 @@ def load_theme_classifier():
38
  theme_classifier = load_theme_classifier()
39
  candidate_labels = ["Stock Movement", "Earnings", "M&A", "Regulation", "Product Launch", "Market Analysis"]
40
 
41
- # --------------------------
42
- # โหลด summarization model
43
- # --------------------------
44
- @st.cache_resource
45
- def load_summarizer():
46
- # เปลี่ยนเป็นโมเดลสรุปข่าวสายการเงิน
47
- return pipeline("summarization", model="Nerdward/financial-summarization-pegasus-finetuned-pytorch-model")
48
-
49
- summarizer = load_summarizer()
50
-
51
  # --------------------------
52
  # UTILITIES
53
  # --------------------------
@@ -55,33 +45,20 @@ def analyze_text(text):
55
  """วิเคราะห์อารมณ์ของข่าวด้วย FinBERT"""
56
  if not text or not text.strip():
57
  return 0
58
- inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True, max_length=512)
 
 
 
 
 
 
59
  with torch.no_grad():
60
  outputs = model(**inputs)
61
- probs = torch.softmax(outputs.logits, dim=1).numpy()[0]
62
- score = (-1 * probs[0]) + (0 * probs[1]) + (1 * probs[2])
63
- return float(score)
 
64
 
65
- def summarize_text(text):
66
- """สรุปข่าวเป็นย่อหน้าเดียว"""
67
- if not text or not text.strip():
68
- return ""
69
- result = summarizer(text, max_length=150, min_length=50, do_sample=False)
70
- return result[0]["summary_text"]
71
-
72
- def summarize_themes(news_texts):
73
- """สรุปธีมข่าวด้วย Zero-shot classification"""
74
- themes = []
75
- for text in news_texts:
76
- if not text.strip():
77
- continue
78
- result = theme_classifier(text, candidate_labels)
79
- themes.append(result["labels"][0])
80
- return themes
81
-
82
- # --------------------------
83
- # แปลงชื่อ/ตัวย่อ → (Company Name, Symbol)
84
- # --------------------------
85
  def resolve_company_symbol(keyword: str):
86
  keyword = keyword.strip()
87
  ticker = None
@@ -108,7 +85,7 @@ def resolve_company_symbol(keyword: str):
108
  return name, ticker
109
 
110
  # --------------------------
111
- # ดึงข่าว 7 วัน
112
  # --------------------------
113
  @st.cache_data(ttl=3600)
114
  def fetch_financial_news(keyword):
@@ -118,6 +95,8 @@ def fetch_financial_news(keyword):
118
  query_keyword = f"({company} OR {symbol}) finance stock"
119
  all_articles = []
120
  page = 1
 
 
121
  while True:
122
  url = (
123
  f"https://newsapi.org/v2/everything?"
@@ -142,11 +121,44 @@ def fetch_financial_news(keyword):
142
  "source": a["source"]["name"],
143
  "url": a["url"]
144
  })
 
145
  if len(articles) < 100:
146
  break
147
  page += 1
 
 
148
  return pd.DataFrame(all_articles)
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  # --------------------------
151
  # ดึงราคาหุ้น
152
  # --------------------------
@@ -173,145 +185,157 @@ def fetch_stock_price(symbol, start_date, end_date):
173
  # --------------------------
174
  def main():
175
  st.title("📰 News Sentiment Analysis for Young Investor")
176
- st.markdown("วิเคราะห์แนวโน้มอารมณ์ของข่าวย้อนหลัง 7 วัน พร้อมราคาหุ้น และสรุปข่าว")
177
 
178
  # Sidebar
179
- with st.sidebar:
180
- st.header("ปุ่มวิเคราะห์ข่าว")
181
- keyword1 = st.text_input("ค้นหา Stock Symbol สำหรับวิเคราะห์:", key="keyword1")
182
- analyze_btn = st.button("วิเคราะห์ข่าว + Sentiment + ราคาหุ้น", key="analyze_btn")
183
-
184
- st.markdown("---")
185
- st.header("ปุ่มสรุปข่าว")
186
- keyword2 = st.text_input("ค้นหา Stock Symbol สำหรับสรุปข่าว:", key="keyword2")
187
- from_date = st.date_input("จากวันที่", datetime.now() - timedelta(days=7), key="from_date")
188
- to_date = st.date_input("ถึงวันที่", datetime.now(), key="to_date")
189
- max_news = st.number_input("จำนวนข่าวสูงสุดที่จะสรุป", min_value=1, max_value=50, value=10, key="max_news")
190
- summarize_btn = st.button("สรุปข่าว", key="summarize_btn")
191
-
192
- # ------------------ ปุ่ม 1 ------------------
193
- if analyze_btn:
194
- if not keyword1:
195
- st.warning("กรุณากรอก Stock Symbol")
196
- else:
197
- st.info(f"กำลังดึงข่าวย้อนหลัง 7 วันสำหรับ '{keyword1}'...")
198
- news_df = fetch_financial_news(keyword1)
199
- if news_df.empty:
200
- st.warning("ไม่พบบทความข่าว")
201
- else:
202
- st.info("กำลังวิเคราะห์อารมณ์ของข่าว...")
203
- news_df["sentiment"] = news_df["text"].apply(analyze_text)
204
- news_df["date"] = pd.to_datetime(news_df["date"])
205
-
206
- # Metrics
207
- avg_sentiment = news_df["sentiment"].mean()
208
- pos_pct = (news_df["sentiment"] > 0.1).mean() * 100
209
- neg_pct = (news_df["sentiment"] < -0.1).mean() * 100
210
- col1, col2, col3 = st.columns(3)
211
- col1.metric("ค่าเฉลี่ยอารมณ์ข่าว", f"{avg_sentiment:.2f}")
212
- col2.metric("ข่าวเชิงบวก", f"{pos_pct:.1f}%")
213
- col3.metric("ข่าวเชิงลบ", f"{neg_pct:.1f}%")
214
-
215
- # Theme
216
- news_df["theme"] = summarize_themes(news_df["text"].tolist())
217
-
218
- # Sentiment & Price
219
- news_df["date_day"] = pd.to_datetime(news_df["date"].dt.date)
220
- def sentiment_type(score):
221
- if score > 0.1: return "positive"
222
- if score < -0.1: return "negative"
223
- return "neutral"
224
- news_df["sentiment_type"] = news_df["sentiment"].apply(sentiment_type)
225
-
226
- daily_avg = news_df.groupby("date_day")["sentiment"].mean().reset_index(name="avg_sentiment")
227
- daily_counts = news_df.groupby(["date_day", "sentiment_type"]).size().unstack(fill_value=0).reset_index()
228
- df_sorted = pd.merge(daily_avg, daily_counts, on="date_day").sort_values("date_day")
229
-
230
- # ราคาหุ้น
231
- _, symbol = resolve_company_symbol(keyword1)
232
- min_date, max_date = df_sorted["date_day"].min(), df_sorted["date_day"].max()
233
- st.info(f"กำลังดึงราคาหุ้น {symbol} ...")
234
- stock_df = fetch_stock_price(symbol, min_date, max_date)
235
- plot_data = pd.merge(df_sorted, stock_df, left_on="date_day", right_on="date", how="left")
236
-
237
- # Correlation
238
- correlation = plot_data['price'].corr(plot_data['avg_sentiment'])
239
- corr_text = "ไม่มีความสัมพันธ์"
240
- if correlation > 0.5:
241
- corr_text = "มีความสัมพันธ์ในทิศทางเดียวกัน"
242
- elif correlation < -0.5:
243
- corr_text = "มีความสัมพันธ์ในทิศทางตรงข้าม"
244
- st.metric("วิเคราะห์ความสัมพันธ์ระหว่างอารมณ์ของข่าวกับราคาหุ้น (Correlation)", corr_text, f"{correlation:.2f}")
245
-
246
- # Forecast Sentiment
247
- plot_data["timestamp"] = (plot_data["date_day"] - plot_data["date_day"].min()).dt.days
248
- train_data = plot_data.dropna(subset=['avg_sentiment'])
249
- if len(train_data) >= 2:
250
- model_lr = LinearRegression()
251
- model_lr.fit(train_data[["timestamp"]], train_data["avg_sentiment"])
252
- future_days = 7
253
- future_timestamps = np.arange(plot_data["timestamp"].max() + 1, plot_data["timestamp"].max() + future_days + 1)
254
- future_dates = [plot_data["date_day"].max() + timedelta(days=i) for i in range(1, future_days + 1)]
255
- future_preds = model_lr.predict(future_timestamps.reshape(-1, 1))
256
-
257
- # Plot
258
- fig = make_subplots(rows=2, cols=1, specs=[[{"secondary_y": True}], [{}]],
259
- row_heights=[0.7, 0.3], vertical_spacing=0.1, shared_xaxes=True)
260
-
261
- fig.add_trace(go.Scatter(x=plot_data["date_day"], y=plot_data["price"], name=f"{symbol} Price",
262
- mode="lines+markers", line=dict(color="orange")), row=1, col=1, secondary_y=False)
263
- fig.add_trace(go.Scatter(x=plot_data["date_day"], y=plot_data["avg_sentiment"], name="Actual Sentiment",
264
- mode="lines+markers", line=dict(color="blue")), row=1, col=1, secondary_y=True)
265
- if "future_preds" in locals():
266
- fig.add_trace(go.Scatter(x=future_dates, y=future_preds, name="Predicted Sentiment",
267
- mode="lines+markers", line=dict(color="#05a0fa", dash="dash")), row=1, col=1, secondary_y=True)
268
- last_actual_date = plot_data["date_day"].max()
269
- last_actual_value = plot_data["avg_sentiment"].iloc[-1]
270
- first_pred_date = future_dates[0]
271
- first_pred_value = future_preds[0]
272
- fig.add_trace(go.Scatter(x=[last_actual_date, first_pred_date],
273
- y=[last_actual_value, first_pred_value],
274
- mode="lines", line=dict(color="#05a0fa", dash="dot"),
275
- name="Connector Actual→Predicted"), row=1, col=1, secondary_y=True)
276
-
277
- for col in ["neutral", "negative", "positive"]:
278
- if col not in plot_data.columns:
279
- plot_data[col] = 0
280
- fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["neutral"], name="Neutral",
281
- marker_color='rgba(128, 128, 128, 0.7)'), row=2, col=1)
282
- fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["negative"], name="Negative",
283
- marker_color='rgba(255, 0, 0, 0.7)'), row=2, col=1)
284
- fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["positive"], name="Positive",
285
- marker_color='rgba(0, 128, 0, 0.7)'), row=2, col=1)
286
-
287
- fig.update_layout(title=f"แนวโน้มอารมณ์ของข่าว + ราคาหุ้น ({symbol})",
288
- barmode="stack", height=650, hovermode="x unified", template="plotly_white")
289
- st.plotly_chart(fig, use_container_width=True)
290
-
291
- st.subheader("📰 รายการข่าวทั้งหมด")
292
- st.dataframe(news_df[["date", "source", "text", "sentiment", "theme", "url"]], use_container_width=True)
293
-
294
- # ------------------ ปุ่ม 2 ------------------
295
- if summarize_btn:
296
- if not keyword2:
297
- st.warning("กรุณากรอก Stock Symbol")
298
- else:
299
- news_df = fetch_financial_news(keyword2)
300
- if news_df.empty:
301
- st.warning("ไม่พบบทความข่าว")
302
- else:
303
- # กรองตามช่วงวันที่
304
- news_df = news_df[(news_df["date"].dt.date >= from_date) & (news_df["date"].dt.date <= to_date)]
305
- news_df = news_df.head(max_news)
306
-
307
- # สรุปข่าว
308
- st.info("กำลังสรุปข่าว...")
309
- news_df["summary"] = news_df["text"].apply(summarize_text)
310
 
311
- st.subheader("📰 ข่าวที่สรุปแล้ว")
312
- st.dataframe(news_df[["date", "source", "summary", "url"]], use_container_width=True)
 
 
 
 
 
 
 
 
 
313
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
 
 
 
 
315
  if __name__ == "__main__":
316
  nltk.download("stopwords", quiet=True)
317
  main()
 
38
  theme_classifier = load_theme_classifier()
39
  candidate_labels = ["Stock Movement", "Earnings", "M&A", "Regulation", "Product Launch", "Market Analysis"]
40
 
 
 
 
 
 
 
 
 
 
 
41
  # --------------------------
42
  # UTILITIES
43
  # --------------------------
 
45
  """วิเคราะห์อารมณ์ของข่าวด้วย FinBERT"""
46
  if not text or not text.strip():
47
  return 0
48
+ inputs = tokenizer(
49
+ text,
50
+ return_tensors="pt",
51
+ padding=True,
52
+ truncation=True,
53
+ max_length=512
54
+ )
55
  with torch.no_grad():
56
  outputs = model(**inputs)
57
+ logits = outputs.logits
58
+ probs = torch.softmax(logits, dim=1).numpy()[0] # FinBERT = [negative, neutral, positive]
59
+ score = (-1 * probs[0]) + (0 * probs[1]) + (1 * probs[2])
60
+ return float(score)
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  def resolve_company_symbol(keyword: str):
63
  keyword = keyword.strip()
64
  ticker = None
 
85
  return name, ticker
86
 
87
  # --------------------------
88
+ # ดึงข่าว 7 วัน (เพิ่ม progress bar)
89
  # --------------------------
90
  @st.cache_data(ttl=3600)
91
  def fetch_financial_news(keyword):
 
95
  query_keyword = f"({company} OR {symbol}) finance stock"
96
  all_articles = []
97
  page = 1
98
+ progress_bar = st.progress(0)
99
+
100
  while True:
101
  url = (
102
  f"https://newsapi.org/v2/everything?"
 
121
  "source": a["source"]["name"],
122
  "url": a["url"]
123
  })
124
+ progress_bar.progress(min(page * 10, 100)) # อัปเดต progress bar แบบหยาบ ๆ
125
  if len(articles) < 100:
126
  break
127
  page += 1
128
+
129
+ progress_bar.progress(100)
130
  return pd.DataFrame(all_articles)
131
 
132
+ # --------------------------
133
+ # วิเคราะห์ Sentiment (เพิ่ม progress bar)
134
+ # --------------------------
135
+ def analyze_news_sentiment(news_df):
136
+ sentiments = []
137
+ progress_bar = st.progress(0)
138
+ total = len(news_df)
139
+ for i, text in enumerate(news_df["text"]):
140
+ sentiments.append(analyze_text(text))
141
+ progress_bar.progress(int((i + 1) / total * 100))
142
+ progress_bar.progress(100)
143
+ return sentiments
144
+
145
+ # --------------------------
146
+ # สรุปธีมข่าว (เพิ่ม progress bar)
147
+ # --------------------------
148
+ def summarize_themes(news_texts):
149
+ themes = []
150
+ progress_bar = st.progress(0)
151
+ total = len(news_texts)
152
+ for i, text in enumerate(news_texts):
153
+ if not text.strip():
154
+ themes.append("Unknown")
155
+ else:
156
+ result = theme_classifier(text, candidate_labels)
157
+ themes.append(result["labels"][0])
158
+ progress_bar.progress(int((i + 1) / total * 100))
159
+ progress_bar.progress(100)
160
+ return themes
161
+
162
  # --------------------------
163
  # ดึงราคาหุ้น
164
  # --------------------------
 
185
  # --------------------------
186
  def main():
187
  st.title("📰 News Sentiment Analysis for Young Investor")
188
+ st.markdown("วิเคราะห์แนวโน้มอารมณ์ของข่าวย้อนหลัง 7 วัน พร้อมราคาหุ้น")
189
 
190
  # Sidebar
191
+ keyword = st.text_input("ค้นหา Stock Symbol (เช่น AAPL, TSLA):", "")
192
+ analyze_btn = st.button("วิเคราะห์เลย")
193
+ if not analyze_btn:
194
+ st.info("กรอกคำค้นแล้วกด 'วิเคราะห์เลย'")
195
+ return
196
+
197
+ # ดึงข่าว
198
+ st.info(f"กำลังดึงข่าวย้อนหลัง 7 วันสำหรับ '{keyword}'...")
199
+ news_df = fetch_financial_news(keyword)
200
+ if news_df.empty:
201
+ st.warning("ไม่พบบทความข่าว")
202
+ return
203
+
204
+ # วิเคราะห์ Sentiment
205
+ st.info("กำลังวิเคราะห์อารมณ์ของข่าว...")
206
+ news_df["sentiment"] = analyze_news_sentiment(news_df)
207
+
208
+ # สรุปธีมข่าว
209
+ st.info("กำลังสรุปธีมข่าว...")
210
+ news_df["theme"] = summarize_themes(news_df["text"].tolist())
211
+
212
+ news_df["date"] = pd.to_datetime(news_df["date"])
213
+
214
+ # Metrics
215
+ avg_sentiment = news_df["sentiment"].mean()
216
+ pos_pct = (news_df["sentiment"] > 0.1).mean() * 100
217
+ neg_pct = (news_df["sentiment"] < -0.1).mean() * 100
218
+ col1, col2, col3 = st.columns(3)
219
+ col1.metric("ค่าเฉลี่ยอารมณ์ข่าว", f"{avg_sentiment:.2f}")
220
+ col2.metric("ข่าวเชิงบวก", f"{pos_pct:.1f}%")
221
+ col3.metric("ข่าวเชิงลบ", f"{neg_pct:.1f}%")
222
+
223
+ # ส่วนกราฟ Sentiment & Price
224
+ st.subheader("📈 แนวโน้มอารมณ์ของข่าว & ราคาหุ้น")
225
+ news_df["date_day"] = pd.to_datetime(news_df["date"].dt.date)
226
+
227
+ def sentiment_type(score):
228
+ if score > 0.1:
229
+ return "positive"
230
+ if score < -0.1:
231
+ return "negative"
232
+ return "neutral"
233
+
234
+ news_df["sentiment_type"] = news_df["sentiment"].apply(sentiment_type)
235
+ daily_avg = news_df.groupby("date_day")["sentiment"].mean().reset_index(name="avg_sentiment")
236
+ daily_counts = news_df.groupby(["date_day", "sentiment_type"]).size().unstack(fill_value=0).reset_index()
237
+ df_sorted = pd.merge(daily_avg, daily_counts, on="date_day").sort_values("date_day")
238
+
239
+ if len(df_sorted) < 2:
240
+ st.warning("ข้อมูลไม่พอสร้างแนวโน้ม")
241
+ st.dataframe(news_df)
242
+ return
243
+
244
+ # ดึงราคาหุ้น
245
+ _, symbol = resolve_company_symbol(keyword)
246
+ min_date, max_date = df_sorted["date_day"].min(), df_sorted["date_day"].max()
247
+ st.info(f"กำลังดึงราคาหุ้น {symbol} ...")
248
+ stock_df = fetch_stock_price(symbol, min_date, max_date)
249
+ plot_data = pd.merge(df_sorted, stock_df, left_on="date_day", right_on="date", how="left")
250
+
251
+ # Correlation
252
+ correlation = plot_data['price'].corr(plot_data['avg_sentiment'])
253
+ corr_text = "ไม่มีความสัมพันธ์"
254
+ if correlation > 0.5:
255
+ corr_text = "มีความสัมพันธ์ในทิศทางเดียวกัน"
256
+ elif correlation < -0.5:
257
+ corr_text = "มีความสัมพันธ์ในทิศทางตรงข้าม"
258
+ st.metric("วิเคราะห์ความสัมพันธ์ระหว่างอารมณ์ของข่าวกับราคาหุ้น (Correlation)", corr_text, f"{correlation:.2f}")
259
+
260
+ # Forecast Sentiment
261
+ plot_data["timestamp"] = (plot_data["date_day"] - plot_data["date_day"].min()).dt.days
262
+ train_data = plot_data.dropna(subset=['avg_sentiment'])
263
+ if len(train_data) >= 2:
264
+ model_lr = LinearRegression()
265
+ model_lr.fit(train_data[["timestamp"]], train_data["avg_sentiment"])
266
+ future_days = 7
267
+ future_timestamps = np.arange(
268
+ plot_data["timestamp"].max() + 1,
269
+ plot_data["timestamp"].max() + future_days + 1
270
+ )
271
+ future_dates = [plot_data["date_day"].max() + timedelta(days=i) for i in range(1, future_days + 1)]
272
+ future_preds = model_lr.predict(future_timestamps.reshape(-1, 1))
273
+
274
+ # Plot
275
+ fig = make_subplots(
276
+ rows=2, cols=1,
277
+ specs=[[{"secondary_y": True}], [{}]],
278
+ row_heights=[0.7, 0.3],
279
+ vertical_spacing=0.1,
280
+ shared_xaxes=True
281
+ )
282
+
283
+ # ราคาหุ้น
284
+ fig.add_trace(
285
+ go.Scatter(x=plot_data["date_day"], y=plot_data["price"], name=f"{symbol} Price",
286
+ mode="lines+markers", line=dict(color="orange")),
287
+ row=1, col=1, secondary_y=False
288
+ )
289
+
290
+ # Sentiment จริง
291
+ fig.add_trace(
292
+ go.Scatter(x=plot_data["date_day"], y=plot_data["avg_sentiment"], name="Actual Sentiment",
293
+ mode="lines+markers", line=dict(color="blue")),
294
+ row=1, col=1, secondary_y=True
295
+ )
296
+
297
+ # Sentiment พยากรณ์
298
+ if "future_preds" in locals():
299
+ fig.add_trace(
300
+ go.Scatter(x=future_dates, y=future_preds, name="Predicted Sentiment",
301
+ mode="lines+markers", line=dict(color="#05a0fa", dash="dash")),
302
+ row=1, col=1, secondary_y=True
303
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
+ # เส้นเชื่อม Actual -> Predicted
306
+ last_actual_date = plot_data["date_day"].max()
307
+ last_actual_value = plot_data["avg_sentiment"].iloc[-1]
308
+ first_pred_date = future_dates[0]
309
+ first_pred_value = future_preds[0]
310
+ fig.add_trace(
311
+ go.Scatter(x=[last_actual_date, first_pred_date], y=[last_actual_value, first_pred_value],
312
+ mode="lines", line=dict(color="#05a0fa", dash="dot"),
313
+ name="Connector Actual→Predicted"),
314
+ row=1, col=1, secondary_y=True
315
+ )
316
 
317
+ # จำนวนข่าว
318
+ for col in ["neutral", "negative", "positive"]:
319
+ if col not in plot_data.columns:
320
+ plot_data[col] = 0
321
+ fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["neutral"], name="Neutral",
322
+ marker_color='rgba(128, 128, 128, 0.7)'), row=2, col=1)
323
+ fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["negative"], name="Negative",
324
+ marker_color='rgba(255, 0, 0, 0.7)'), row=2, col=1)
325
+ fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["positive"], name="Positive",
326
+ marker_color='rgba(0, 128, 0, 0.7)'), row=2, col=1)
327
+
328
+ fig.update_layout(title=f"แนวโน้มอารมณ์ของข่าว + ราคาหุ้น ({symbol})",
329
+ barmode="stack", height=650, hovermode="x unified", template="plotly_white")
330
+ st.plotly_chart(fig, use_container_width=True)
331
+
332
+ # แสดงรายการข่าว
333
+ st.subheader("📰 รายการข่าวทั้งหมด")
334
+ st.dataframe(news_df[["date", "source", "text", "sentiment", "theme", "url"]], use_container_width=True)
335
 
336
+ # --------------------------
337
+ # RUN APP
338
+ # --------------------------
339
  if __name__ == "__main__":
340
  nltk.download("stopwords", quiet=True)
341
  main()