KYTHY commited on
Commit
96780bb
·
verified ·
1 Parent(s): 5665b46

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +123 -180
app.py CHANGED
@@ -9,7 +9,7 @@ import plotly.graph_objects as go
9
  from plotly.subplots import make_subplots
10
  import yfinance as yf
11
  import torch
12
- from transformers import AutoTokenizer, AutoModelForSequenceClassification, AutoModelForSeq2SeqLM, pipeline
13
 
14
  # --------------------------
15
  # CONFIG
@@ -29,62 +29,66 @@ def load_finbert():
29
  tokenizer, model = load_finbert()
30
 
31
  # --------------------------
32
- # โหลด Pegasus summarizer (slow tokenizer)
 
 
 
 
 
 
 
 
 
 
33
  # --------------------------
34
  @st.cache_resource
35
  def load_summarizer():
36
- tokenizer_sum = AutoTokenizer.from_pretrained(
37
- "Nerdward/financial-summarization-pegasus-finetuned-pytorch-model",
38
- use_fast=False # ใช้ slow tokenizer
39
- )
40
- model_sum = AutoModelForSeq2SeqLM.from_pretrained(
41
- "Nerdward/financial-summarization-pegasus-finetuned-pytorch-model"
42
- )
43
- summarizer = pipeline("summarization", model=model_sum, tokenizer=tokenizer_sum)
44
- return summarizer
45
 
46
  summarizer = load_summarizer()
47
 
48
  # --------------------------
49
- # UTILITIES
50
  # --------------------------
51
  def analyze_text(text):
52
- """วิเคราะห์อารมณ์ของข่าวด้วย FinBERT"""
53
  if not text or not text.strip():
54
  return 0
55
-
56
- inputs = tokenizer(
57
- text,
58
- return_tensors="pt",
59
- padding=True,
60
- truncation=True,
61
- max_length=512
62
- )
63
-
64
  with torch.no_grad():
65
  outputs = model(**inputs)
66
  logits = outputs.logits
67
  probs = torch.softmax(logits, dim=1).numpy()[0]
68
-
69
- # FinBERT = [negative, neutral, positive]
70
  score = (-1 * probs[0]) + (0 * probs[1]) + (1 * probs[2])
71
  return float(score)
72
 
73
- def summarize_article(text):
74
- """สรุปข่าวเป็น 1 พารากราฟ"""
75
- if not text.strip():
76
- return ""
77
- summary_list = summarizer(text, max_length=150, min_length=50, do_sample=False)
78
- return summary_list[0]['summary_text']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
 
80
  # --------------------------
81
- # แปลงชื่อ/ตัวย่อ (Company Name, Symbol)
82
  # --------------------------
83
  def resolve_company_symbol(keyword: str):
84
  keyword = keyword.strip()
85
  ticker = None
86
  name = None
87
-
88
  try:
89
  data = yf.Ticker(keyword)
90
  info = data.info
@@ -100,25 +104,18 @@ def resolve_company_symbol(keyword: str):
100
  name = q.get("longname", q.get("shortname", keyword))
101
  except:
102
  pass
103
-
104
  if not ticker:
105
  ticker = keyword.upper()
106
  if not name:
107
  name = keyword.capitalize()
108
-
109
  return name, ticker
110
 
111
- # --------------------------
112
- # ดึงข่าว 7 วัน
113
- # --------------------------
114
  @st.cache_data(ttl=3600)
115
  def fetch_financial_news(keyword):
116
  company, symbol = resolve_company_symbol(keyword)
117
  to_date = datetime.now().strftime('%Y-%m-%d')
118
  from_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d')
119
-
120
  query_keyword = f"({company} OR {symbol}) finance stock"
121
-
122
  all_articles = []
123
  page = 1
124
  while True:
@@ -134,11 +131,9 @@ def fetch_financial_news(keyword):
134
  if data.get("status") != "ok":
135
  st.error(f"API Error: {data}")
136
  break
137
-
138
  articles = data.get("articles", [])
139
  if not articles:
140
  break
141
-
142
  for a in articles:
143
  if a["description"]:
144
  all_articles.append({
@@ -147,34 +142,25 @@ def fetch_financial_news(keyword):
147
  "source": a["source"]["name"],
148
  "url": a["url"]
149
  })
150
-
151
  if len(articles) < 100:
152
  break
153
  page += 1
154
-
155
  return pd.DataFrame(all_articles)
156
 
157
- # --------------------------
158
- # ดึงราคาหุ้น
159
- # --------------------------
160
  @st.cache_data(ttl=3600)
161
  def fetch_stock_price(symbol, start_date, end_date):
162
  try:
163
  start_str = (start_date - timedelta(days=2)).strftime('%Y-%m-%d')
164
  end_str = (end_date + timedelta(days=1)).strftime('%Y-%m-%d')
165
  df = yf.download(symbol, start=start_str, end=end_str, interval="1d")
166
-
167
  if df.empty:
168
  st.warning("ไม่พบข้อมูลราคาหุ้น")
169
  return pd.DataFrame()
170
-
171
  df = df.reset_index()
172
  df_subset = df[['Date', 'Close']]
173
  df_subset.columns = ['date', 'price']
174
  df_subset["date"] = pd.to_datetime(df_subset["date"].dt.date)
175
-
176
  return df_subset
177
-
178
  except Exception as e:
179
  st.warning(f"ดึงราคาหุ้นล้มเหลว: {e}")
180
  return pd.DataFrame()
@@ -184,151 +170,108 @@ def fetch_stock_price(symbol, start_date, end_date):
184
  # --------------------------
185
  def main():
186
  st.title("📰 News Sentiment Analysis for Young Investor")
187
- st.markdown("วิเคราะห์แนวโน้มอารมณ์ของข่าวย้อนหลัง 7 วัน พร้อมราคาหุ้น")
188
 
189
  # Sidebar
190
  with st.sidebar:
191
  keyword = st.text_input("ค้นหา Stock Symbol (เช่น AAPL, TSLA):", "")
192
- analyze_btn = st.button("วิเคราะห์เลย")
 
193
 
194
- if not analyze_btn:
195
- st.info("กรอกคำค้นแล้วกด 'วิเคราะห์เลย'")
196
  return
197
 
198
- # ดึงข่าว
199
- st.info(f"กำลังดึงข่าวย้อนหลัง 7 วันสำหรับ '{keyword}'...")
200
  news_df = fetch_financial_news(keyword)
201
  if news_df.empty:
202
  st.warning("ไม่พบบทความข่าว")
203
  return
204
 
205
- # วิเคราะห์ Sentiment
206
- st.info("กำลังวิเคราะห์อารมณ์ของข่าว...")
207
- news_df["sentiment"] = news_df["text"].apply(analyze_text)
208
- news_df["date"] = pd.to_datetime(news_df["date"])
209
-
210
- # Metrics
211
- avg_sentiment = news_df["sentiment"].mean()
212
- pos_pct = (news_df["sentiment"] > 0.1).mean() * 100
213
- neg_pct = (news_df["sentiment"] < -0.1).mean() * 100
214
-
215
- col1, col2, col3 = st.columns(3)
216
- col1.metric("ค่าเฉลี่ยอารมณ์ข่าว", f"{avg_sentiment:.2f}")
217
- col2.metric("ข่าวเชิงบวก", f"{pos_pct:.1f}%")
218
- col3.metric("ข่าวเชิงลบ", f"{neg_pct:.1f}%")
219
-
220
- # สรุปข่าว 1 พารากราฟ
221
- st.info("กำลังสรุปข่าวเป็น 1 พารากราฟต่อข่าว...")
222
- news_df["text"] = news_df["text"].apply(lambda x: summarize_article(x) if x.strip() else "")
223
-
224
- # ---------------------------------------------------------
225
- # ส่วนกราฟ Sentiment & Price (เหมือนเดิม)
226
- # ---------------------------------------------------------
227
- st.subheader("📈 แนวโน้มอารมณ์ของข่าว & ราคาหุ้น")
228
-
229
- news_df["date_day"] = pd.to_datetime(news_df["date"].dt.date)
230
-
231
- def sentiment_type(score):
232
- if score > 0.1:
233
- return "positive"
234
- if score < -0.1:
235
- return "negative"
236
- return "neutral"
237
-
238
- news_df["sentiment_type"] = news_df["sentiment"].apply(sentiment_type)
239
-
240
- daily_avg = news_df.groupby("date_day")["sentiment"].mean().reset_index(name="avg_sentiment")
241
- daily_counts = news_df.groupby(["date_day", "sentiment_type"]).size().unstack(fill_value=0).reset_index()
242
-
243
- df_sorted = pd.merge(daily_avg, daily_counts, on="date_day").sort_values("date_day")
244
-
245
- if len(df_sorted) < 2:
246
- st.warning("ข้อมูลไม่พอสร้างแนวโน้ม")
247
- st.dataframe(news_df)
248
- return
249
-
250
- # ดึงราคาหุ้น
251
- _, symbol = resolve_company_symbol(keyword)
252
- min_date, max_date = df_sorted["date_day"].min(), df_sorted["date_day"].max()
253
- st.info(f"กำลังดึงราคาหุ้น {symbol} ...")
254
- stock_df = fetch_stock_price(symbol, min_date, max_date)
255
-
256
- plot_data = pd.merge(df_sorted, stock_df, left_on="date_day", right_on="date", how="left")
257
-
258
- # Correlation
259
- correlation = plot_data['price'].corr(plot_data['avg_sentiment'])
260
- corr_text = "ไม่มีความสัมพันธ์"
261
- if correlation > 0.5:
262
- corr_text = "มีความสัมพันธ์ในทิศทางเดียวกัน"
263
- elif correlation < -0.5:
264
- corr_text = "มีความสัมพันธ์ในทิศทางตรงข้าม"
265
- st.metric("วิเคราะห์ความสัมพันธ์ระหว่างอารมณ์ของข่าวกับราคาหุ้น (Correlation)", corr_text, f"{correlation:.2f}")
266
-
267
- # Forecast Sentiment
268
- plot_data["timestamp"] = (plot_data["date_day"] - plot_data["date_day"].min()).dt.days
269
- train_data = plot_data.dropna(subset=['avg_sentiment'])
270
-
271
- if len(train_data) >= 2:
272
- model_lr = LinearRegression()
273
- model_lr.fit(train_data[["timestamp"]], train_data["avg_sentiment"])
274
-
275
- future_days = 7
276
- future_timestamps = np.arange(
277
- plot_data["timestamp"].max() + 1,
278
- plot_data["timestamp"].max() + future_days + 1
279
- )
280
- future_dates = [plot_data["date_day"].max() + timedelta(days=i) for i in range(1, future_days + 1)]
281
- future_preds = model_lr.predict(future_timestamps.reshape(-1, 1))
282
-
283
- # Plot
284
- fig = make_subplots(rows=2, cols=1, specs=[[{"secondary_y": True}], [{}]],
285
- row_heights=[0.7, 0.3], vertical_spacing=0.1,
286
- shared_xaxes=True)
287
-
288
- # ราคาหุ้น
289
- fig.add_trace(go.Scatter(x=plot_data["date_day"], y=plot_data["price"], name=f"{symbol} Price",
290
- mode="lines+markers", line=dict(color="orange")), row=1, col=1)
291
- # Sentiment จริง
292
- fig.add_trace(go.Scatter(x=plot_data["date_day"], y=plot_data["avg_sentiment"], name="Actual Sentiment",
293
- mode="lines+markers", line=dict(color="blue")), row=1, col=1, secondary_y=True)
294
- # Sentiment พยากรณ์
295
- if "future_preds" in locals():
296
- fig.add_trace(go.Scatter(x=future_dates, y=future_preds, name="Predicted Sentiment",
297
- mode="lines+markers", line=dict(color="#05a0fa", dash="dash")), row=1, col=1, secondary_y=True)
298
- # เส้นเชื่อม Actual -> Predicted
299
- last_actual_date = plot_data["date_day"].max()
300
- last_actual_value = plot_data["avg_sentiment"].iloc[-1]
301
- first_pred_date = future_dates[0]
302
- first_pred_value = future_preds[0]
303
- fig.add_trace(go.Scatter(x=[last_actual_date, first_pred_date],
304
- y=[last_actual_value, first_pred_value],
305
- mode="lines",
306
- line=dict(color="#05a0fa", dash="dot"),
307
- name="Connector Actual→Predicted"), row=1, col=1, secondary_y=True)
308
-
309
- # จำนวนข่าว
310
- for col in ["neutral", "negative", "positive"]:
311
- if col not in plot_data.columns:
312
- plot_data[col] = 0
313
- fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["neutral"], name="Neutral",
314
- marker_color='rgba(128, 128, 128, 0.7)'), row=2, col=1)
315
- fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["negative"], name="Negative",
316
- marker_color='rgba(255, 0, 0, 0.7)'), row=2, col=1)
317
- fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["positive"], name="Positive",
318
- marker_color='rgba(0, 128, 0, 0.7)'), row=2, col=1)
319
-
320
- fig.update_layout(title=f"แนวโน้มอารมณ์ของข่าว + ราคาหุ้น ({symbol})",
321
- barmode="stack", height=650, hovermode="x unified", template="plotly_white")
322
-
323
- st.plotly_chart(fig, use_container_width=True)
324
-
325
- # แสดงรายการข่าวทั้งหมด (text เป็นสรุปแล้ว)
326
  st.subheader("📰 รายการข่าวทั้งหมด")
327
- st.dataframe(news_df[["date", "source", "text", "sentiment", "url"]], use_container_width=True)
328
 
329
- # ---------------------------------------------------------
330
  # RUN APP
331
- # ---------------------------------------------------------
332
  if __name__ == "__main__":
333
  nltk.download("stopwords", quiet=True)
334
  main()
 
9
  from plotly.subplots import make_subplots
10
  import yfinance as yf
11
  import torch
12
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline
13
 
14
  # --------------------------
15
  # CONFIG
 
29
  tokenizer, model = load_finbert()
30
 
31
  # --------------------------
32
+ # Zero-shot classifier สำหรับธีมข่าว
33
+ # --------------------------
34
+ @st.cache_resource
35
+ def load_theme_classifier():
36
+ return pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
37
+
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():
68
+ continue
69
+ result = theme_classifier(text, candidate_labels)
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()
90
  ticker = None
91
  name = None
 
92
  try:
93
  data = yf.Ticker(keyword)
94
  info = data.info
 
104
  name = q.get("longname", q.get("shortname", keyword))
105
  except:
106
  pass
 
107
  if not ticker:
108
  ticker = keyword.upper()
109
  if not name:
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)
116
  to_date = datetime.now().strftime('%Y-%m-%d')
117
  from_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d')
 
118
  query_keyword = f"({company} OR {symbol}) finance stock"
 
119
  all_articles = []
120
  page = 1
121
  while True:
 
131
  if data.get("status") != "ok":
132
  st.error(f"API Error: {data}")
133
  break
 
134
  articles = data.get("articles", [])
135
  if not articles:
136
  break
 
137
  for a in articles:
138
  if a["description"]:
139
  all_articles.append({
 
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
  @st.cache_data(ttl=3600)
151
  def fetch_stock_price(symbol, start_date, end_date):
152
  try:
153
  start_str = (start_date - timedelta(days=2)).strftime('%Y-%m-%d')
154
  end_str = (end_date + timedelta(days=1)).strftime('%Y-%m-%d')
155
  df = yf.download(symbol, start=start_str, end=end_str, interval="1d")
 
156
  if df.empty:
157
  st.warning("ไม่พบข้อมูลราคาหุ้น")
158
  return pd.DataFrame()
 
159
  df = df.reset_index()
160
  df_subset = df[['Date', 'Close']]
161
  df_subset.columns = ['date', 'price']
162
  df_subset["date"] = pd.to_datetime(df_subset["date"].dt.date)
 
163
  return df_subset
 
164
  except Exception as e:
165
  st.warning(f"ดึงราคาหุ้นล้มเหลว: {e}")
166
  return pd.DataFrame()
 
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()