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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +150 -110
app.py CHANGED
@@ -29,7 +29,7 @@ def load_finbert():
29
  tokenizer, model = load_finbert()
30
 
31
  # --------------------------
32
- # Zero-shot classifier สำหรับธีมข่าว
33
  # --------------------------
34
  @st.cache_resource
35
  def load_theme_classifier():
@@ -39,29 +39,38 @@ theme_classifier = load_theme_classifier()
39
  candidate_labels = ["Stock Movement", "Earnings", "M&A", "Regulation", "Product Launch", "Market Analysis"]
40
 
41
  # --------------------------
42
- # Summarizer model
43
  # --------------------------
44
  @st.cache_resource
45
  def load_summarizer():
 
46
  return pipeline("summarization", model="Nerdward/financial-summarization-pegasus-finetuned-pytorch-model")
47
 
48
  summarizer = load_summarizer()
49
 
50
  # --------------------------
51
- # Utilities
52
  # --------------------------
53
  def analyze_text(text):
 
54
  if not text or not text.strip():
55
  return 0
56
  inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True, max_length=512)
57
  with torch.no_grad():
58
  outputs = model(**inputs)
59
- logits = outputs.logits
60
- probs = torch.softmax(logits, dim=1).numpy()[0]
61
  score = (-1 * probs[0]) + (0 * probs[1]) + (1 * probs[2])
62
  return float(score)
63
 
 
 
 
 
 
 
 
64
  def summarize_themes(news_texts):
 
65
  themes = []
66
  for text in news_texts:
67
  if not text.strip():
@@ -70,20 +79,8 @@ def summarize_themes(news_texts):
70
  themes.append(result["labels"][0])
71
  return themes
72
 
73
- @st.cache_data(ttl=3600)
74
- def summarize_news(texts):
75
- """สรุปข่าวทีละข่าว ใช้ caching"""
76
- summaries = []
77
- for t in texts:
78
- if not t.strip():
79
- summaries.append("")
80
- continue
81
- summ = summarizer(t, max_length=150, min_length=50, do_sample=False)
82
- summaries.append(summ[0]["summary_text"])
83
- return summaries
84
-
85
  # --------------------------
86
- # Yahoo Finance helpers
87
  # --------------------------
88
  def resolve_company_symbol(keyword: str):
89
  keyword = keyword.strip()
@@ -110,6 +107,9 @@ def resolve_company_symbol(keyword: str):
110
  name = keyword.capitalize()
111
  return name, ticker
112
 
 
 
 
113
  @st.cache_data(ttl=3600)
114
  def fetch_financial_news(keyword):
115
  company, symbol = resolve_company_symbol(keyword)
@@ -147,6 +147,9 @@ def fetch_financial_news(keyword):
147
  page += 1
148
  return pd.DataFrame(all_articles)
149
 
 
 
 
150
  @st.cache_data(ttl=3600)
151
  def fetch_stock_price(symbol, start_date, end_date):
152
  try:
@@ -170,108 +173,145 @@ def fetch_stock_price(symbol, start_date, end_date):
170
  # --------------------------
171
  def main():
172
  st.title("📰 News Sentiment Analysis for Young Investor")
173
- st.markdown("วิเคราะห์ข่าวย้อนหลัง 7 วัน พร้อมราคาหุ้น")
174
 
175
  # Sidebar
176
  with st.sidebar:
177
- keyword = st.text_input("ค้นหา Stock Symbol (เช่น AAPL, TSLA):", "")
178
- analyze_btn = st.button("วิเคราะห์ sentiment & ราคา")
179
- summarize_btn = st.button("สรุปข่าว")
180
 
181
- if not keyword:
182
- st.info("กรอกคำค้นแล้วกดปุ่ม")
183
- return
184
-
185
- # ดึงข่าว (ใช้ cache เดียวกันสำหรับทั้งสองปุ่ม)
186
- news_df = fetch_financial_news(keyword)
187
- if news_df.empty:
188
- st.warning("ไม่พบบทความข่าว")
189
- return
190
 
 
191
  if analyze_btn:
192
- # วิเคราะห์ sentiment
193
- st.info("กำลังวิเคราะห์อารมณ์ของข่าว...")
194
- news_df["sentiment"] = news_df["text"].apply(analyze_text)
195
- news_df["date"] = pd.to_datetime(news_df["date"])
196
-
197
- # Metrics
198
- avg_sentiment = news_df["sentiment"].mean()
199
- pos_pct = (news_df["sentiment"] > 0.1).mean() * 100
200
- neg_pct = (news_df["sentiment"] < -0.1).mean() * 100
201
- col1, col2, col3 = st.columns(3)
202
- col1.metric("ค่าเฉลี่ยอารมณ์ข่าว", f"{avg_sentiment:.2f}")
203
- col2.metric("ข่าวเชิงบวก", f"{pos_pct:.1f}%")
204
- col3.metric("ข่าวเชิงลบ", f"{neg_pct:.1f}%")
205
-
206
- # ธีมข่าว
207
- news_df["theme"] = summarize_themes(news_df["text"].tolist())
208
-
209
- # ส่วนกราฟ sentiment & price
210
- st.subheader("📈 แนวโน้มอารมณ์ของข่าว & ราคาหุ้น")
211
- news_df["date_day"] = pd.to_datetime(news_df["date"].dt.date)
212
- def sentiment_type(score):
213
- if score > 0.1: return "positive"
214
- if score < -0.1: return "negative"
215
- return "neutral"
216
- news_df["sentiment_type"] = news_df["sentiment"].apply(sentiment_type)
217
- daily_avg = news_df.groupby("date_day")["sentiment"].mean().reset_index(name="avg_sentiment")
218
- daily_counts = news_df.groupby(["date_day", "sentiment_type"]).size().unstack(fill_value=0).reset_index()
219
- df_sorted = pd.merge(daily_avg, daily_counts, on="date_day").sort_values("date_day")
220
- if len(df_sorted) < 2:
221
- st.warning("ข้อมูลไม่พอสร้างแนวโน้ม")
222
- st.dataframe(news_df)
223
- return
224
-
225
- _, symbol = resolve_company_symbol(keyword)
226
- min_date, max_date = df_sorted["date_day"].min(), df_sorted["date_day"].max()
227
- st.info(f"กำลังดึงราคาหุ้น {symbol} ...")
228
- stock_df = fetch_stock_price(symbol, min_date, max_date)
229
- plot_data = pd.merge(df_sorted, stock_df, left_on="date_day", right_on="date", how="left")
230
-
231
- correlation = plot_data['price'].corr(plot_data['avg_sentiment'])
232
- corr_text = "ไม่มีความสัมพันธ์"
233
- if correlation > 0.5:
234
- corr_text = "มีความสัมพันธ์ในทิศทางเดียวกัน"
235
- elif correlation < -0.5:
236
- corr_text = "มีความสัมพันธ์ในทิศทางตรงข้าม"
237
- st.metric("Correlation อารมณ์ข่าว vs ราคาหุ้น", corr_text, f"{correlation:.2f}")
238
-
239
- # Forecast Sentiment
240
- plot_data["timestamp"] = (plot_data["date_day"] - plot_data["date_day"].min()).dt.days
241
- train_data = plot_data.dropna(subset=['avg_sentiment'])
242
- if len(train_data) >= 2:
243
- model_lr = LinearRegression()
244
- model_lr.fit(train_data[["timestamp"]], train_data["avg_sentiment"])
245
- future_days = 7
246
- future_timestamps = np.arange(plot_data["timestamp"].max()+1, plot_data["timestamp"].max()+future_days+1)
247
- future_dates = [plot_data["date_day"].max() + timedelta(days=i) for i in range(1, future_days+1)]
248
- future_preds = model_lr.predict(future_timestamps.reshape(-1,1))
249
-
250
- # Plot
251
- fig = make_subplots(rows=2, cols=1, specs=[[{"secondary_y": True}], [{}]], row_heights=[0.7,0.3], vertical_spacing=0.1, shared_xaxes=True)
252
- fig.add_trace(go.Scatter(x=plot_data["date_day"], y=plot_data["price"], name=f"{symbol} Price", mode="lines+markers", line=dict(color="orange")), row=1, col=1, secondary_y=False)
253
- fig.add_trace(go.Scatter(x=plot_data["date_day"], y=plot_data["avg_sentiment"], name="Actual Sentiment", mode="lines+markers", line=dict(color="blue")), row=1, col=1, secondary_y=True)
254
- if "future_preds" in locals():
255
- fig.add_trace(go.Scatter(x=future_dates, y=future_preds, name="Predicted Sentiment", mode="lines+markers", line=dict(color="#05a0fa", dash="dash")), row=1, col=1, secondary_y=True)
256
- for col in ["neutral","negative","positive"]:
257
- if col not in plot_data.columns: plot_data[col]=0
258
- fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["neutral"], name="Neutral", marker_color='rgba(128,128,128,0.7)'), row=2, col=1)
259
- fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["negative"], name="Negative", marker_color='rgba(255,0,0,0.7)'), row=2, col=1)
260
- fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["positive"], name="Positive", marker_color='rgba(0,128,0,0.7)'), row=2, col=1)
261
- fig.update_layout(title=f"แนวโน้มอารมณ์ของข่าว + ราคาหุ้น ({symbol})", barmode="stack", height=650, hovermode="x unified", template="plotly_white")
262
- st.plotly_chart(fig, use_container_width=True)
263
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  if summarize_btn:
265
- st.info("กำลังสรุปข่าวแต่ละข่าว...")
266
- news_df["text"] = summarize_news(news_df["text"].tolist())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
267
 
268
- # แสดงรายการข่าว (เหมือนกันทั้งสองปุ่ม)
269
- st.subheader("📰 รายการข่าวทั้งหมด")
270
- st.dataframe(news_df[["date","source","text","sentiment"]].fillna(""), use_container_width=True)
271
 
272
- # --------------------------
273
- # RUN APP
274
- # --------------------------
275
  if __name__ == "__main__":
276
  nltk.download("stopwords", quiet=True)
277
  main()
 
29
  tokenizer, model = load_finbert()
30
 
31
  # --------------------------
32
+ # โหลด Zero-shot classifier สำหรับธีมข่าว
33
  # --------------------------
34
  @st.cache_resource
35
  def 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
  # --------------------------
54
  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():
 
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()
 
107
  name = keyword.capitalize()
108
  return name, ticker
109
 
110
+ # --------------------------
111
+ # ดึงข่าว 7 วัน
112
+ # --------------------------
113
  @st.cache_data(ttl=3600)
114
  def fetch_financial_news(keyword):
115
  company, symbol = resolve_company_symbol(keyword)
 
147
  page += 1
148
  return pd.DataFrame(all_articles)
149
 
150
+ # --------------------------
151
+ # ดึงราคาหุ้น
152
+ # --------------------------
153
  @st.cache_data(ttl=3600)
154
  def fetch_stock_price(symbol, start_date, end_date):
155
  try:
 
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()