KYTHY commited on
Commit
9ad2edc
·
verified ·
1 Parent(s): bf7c440

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +138 -142
app.py CHANGED
@@ -2,8 +2,6 @@ import streamlit as st
2
  import requests
3
  import pandas as pd
4
  from datetime import datetime, timedelta
5
- from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
6
- from textblob import TextBlob
7
  import nltk
8
  from wordcloud import WordCloud
9
  import base64
@@ -13,6 +11,8 @@ from sklearn.linear_model import LinearRegression
13
  import plotly.graph_objects as go
14
  from plotly.subplots import make_subplots
15
  import yfinance as yf
 
 
16
 
17
  # --------------------------
18
  # CONFIG
@@ -20,15 +20,41 @@ import yfinance as yf
20
  st.set_page_config(page_title="📰 News Sentiment Analysis for Young Investor", layout="wide")
21
  API_KEY = "88bc396d4eab4be494a4b86ec842db47"
22
 
 
 
 
 
 
 
 
 
 
 
 
23
  # --------------------------
24
  # UTILITIES
25
  # --------------------------
26
- def analyze_text(text, vader):
27
- if not text.strip():
 
28
  return 0
29
- vader_score = vader.polarity_scores(text)["compound"]
30
- textblob_score = TextBlob(text).sentiment.polarity
31
- return np.mean([vader_score, textblob_score])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
 
34
  def generate_wordcloud(text):
@@ -60,8 +86,8 @@ def resolve_company_symbol(keyword: str):
60
  q = res["quotes"][0]
61
  ticker = q.get("symbol")
62
  name = q.get("longname", q.get("shortname", keyword))
63
- except Exception as e:
64
- print("Lookup failed:", e)
65
 
66
  if not ticker:
67
  ticker = keyword.upper()
@@ -72,7 +98,7 @@ def resolve_company_symbol(keyword: str):
72
 
73
 
74
  # --------------------------
75
- # ดึงข่าว 7 วัน สำหรับ Company + Symbol
76
  # --------------------------
77
  @st.cache_data(ttl=3600)
78
  def fetch_financial_news(keyword):
@@ -119,29 +145,28 @@ def fetch_financial_news(keyword):
119
 
120
 
121
  # --------------------------
122
- # ดึงราคาหุ้นตามช่วงเวลาที่กำหนด (และ Flatten Header)
123
  # --------------------------
124
  @st.cache_data(ttl=3600)
125
  def fetch_stock_price(symbol, start_date, end_date):
126
  try:
127
  start_str = (start_date - timedelta(days=2)).strftime('%Y-%m-%d')
128
  end_str = (end_date + timedelta(days=1)).strftime('%Y-%m-%d')
129
-
130
  df = yf.download(symbol, start=start_str, end=end_str, interval="1d")
131
-
132
  if df.empty:
133
- st.warning("ไม่พบข้อมูลราคาหุ้นในช่วงเวลานี้")
134
  return pd.DataFrame()
135
-
136
  df = df.reset_index()
137
  df_subset = df[['Date', 'Close']]
138
  df_subset.columns = ['date', 'price']
139
  df_subset["date"] = pd.to_datetime(df_subset["date"].dt.date)
140
-
141
  return df_subset
142
-
143
  except Exception as e:
144
- st.warning(f"ไม่สามารถดึงราคาหุ้นได้: {e}")
145
  return pd.DataFrame()
146
 
147
 
@@ -150,7 +175,7 @@ def fetch_stock_price(symbol, start_date, end_date):
150
  # --------------------------
151
  def main():
152
  st.title("📰 News Sentiment Analysis for Young Investor")
153
- st.markdown("วิเคราะห์แนวโน้มอารมณ์ของข่าวย้อนหลัง 7 วัน พร้อมราคาหุ้น")
154
 
155
  # Sidebar
156
  with st.sidebar:
@@ -158,194 +183,165 @@ def main():
158
  analyze_btn = st.button("วิเคราะห์เลย")
159
 
160
  if not analyze_btn:
161
- st.info("กรอกคำค้นแล้วกด 'วิเคราะห์เลย' เพื่อเริ่มต้น")
162
  return
163
 
164
- vader = SentimentIntensityAnalyzer()
165
-
166
  # ดึงข่าว
167
- st.info(f"กำลังดึงข่าวย้อนหลัง 7 วันสำหรับ '{keyword}' ...")
168
  news_df = fetch_financial_news(keyword)
169
  if news_df.empty:
170
- st.warning("ไม่พบบทความข่าวในช่วง 7 วันที่ผ่านมา")
171
  return
172
 
173
- # วิเคราะห์ sentiment
174
- st.info("กำลังวิเคราะห์อารมณ์ของข่าว...")
175
- news_df["sentiment"] = news_df["text"].apply(lambda x: analyze_text(x, vader))
176
  news_df["date"] = pd.to_datetime(news_df["date"])
177
 
178
- # แสดง Metric
179
  avg_sentiment = news_df["sentiment"].mean()
180
  pos_pct = (news_df["sentiment"] > 0.1).mean() * 100
181
  neg_pct = (news_df["sentiment"] < -0.1).mean() * 100
182
 
183
  col1, col2, col3 = st.columns(3)
184
- col1.metric("ค่าเฉลี่ยอารมณ์ข่าว", f"{avg_sentiment:.2f}",
185
- "Positive" if avg_sentiment > 0 else "Negative" if avg_sentiment < 0 else "Neutral")
186
  col2.metric("ข่าวเชิงบวก", f"{pos_pct:.1f}%")
187
  col3.metric("ข่าวเชิงลบ", f"{neg_pct:.1f}%")
188
 
189
- # Wordcloud
190
- st.subheader("☁️ Word Cloud ของข่าว")
191
  all_text = " ".join(news_df["text"].tolist())
192
  img = generate_wordcloud(all_text)
193
  st.image(f"data:image/png;base64,{img}", use_column_width=True)
194
 
195
- # -----------------------------------------------------------------
196
- # กราฟไฮบริด (Ref1 + Prediction)
197
- # -----------------------------------------------------------------
198
- st.subheader("📈 แนวโน้มอารมณ์ของข่าว & ราคาหุ้น")
199
-
200
- # 1. รวบรวมข้อมูลข่าวเป็นรายวัน (Daily Aggregation)
201
  news_df["date_day"] = pd.to_datetime(news_df["date"].dt.date)
202
 
203
  def sentiment_type(score):
204
- if score > 0.1: return "positive"
205
- if score < -0.1: return "negative"
 
 
206
  return "neutral"
207
-
208
- news_df["sentiment_type"] = news_df["sentiment"].apply(sentiment_type)
209
 
210
- daily_avg_sentiment = news_df.groupby("date_day").agg(
211
- avg_sentiment=('sentiment', 'mean')
212
- ).reset_index()
213
 
 
214
  daily_counts = news_df.groupby(["date_day", "sentiment_type"]).size().unstack(fill_value=0).reset_index()
215
-
216
- daily_data = pd.merge(daily_avg_sentiment, daily_counts, on="date_day", how="left").fillna(0)
217
-
218
- for col in ['positive', 'negative', 'neutral']:
219
- if col not in daily_data.columns:
220
- daily_data[col] = 0
221
-
222
- df_sorted = daily_data.sort_values("date_day").copy()
223
-
224
  if len(df_sorted) < 2:
225
- st.warning("มีข้อมูลข่าวไม่เพียงพอที่จะสร้างแนวโน้ม (น้อยกว่า 2 วัน)")
226
- st.subheader("📰 รายการข่าว")
227
- st.dataframe(news_df[["date", "source", "text", "sentiment", "url"]], use_container_width=True)
228
  return
229
 
230
- # 2. ดึงราคาหุ้น
231
  _, symbol = resolve_company_symbol(keyword)
232
- min_date = df_sorted["date_day"].min()
233
- max_date = df_sorted["date_day"].max()
234
-
235
- st.info(f"กำลังดึงราคาหุ้น {symbol} ระหว่างวันที่ {min_date.strftime('%Y-%m-%d')} ถึง {max_date.strftime('%Y-%m-%d')}...")
236
  stock_df = fetch_stock_price(symbol, min_date, max_date)
237
 
238
- # 3. Merge ข้อมูล 2 ชุด (Sentiment & Stock)
239
- plot_data = pd.merge(
240
- df_sorted,
241
- stock_df,
242
- left_on="date_day",
243
- right_on="date",
244
- how="left"
245
- )
246
 
247
- # 4. (*** ใหม่ ***) คำนวณและตีความ Correlation
 
 
248
  correlation = plot_data['price'].corr(plot_data['avg_sentiment'])
249
-
250
- corr_text = "ไม่มีความสัมพันธ์กัน"
251
- corr_delta = f"Correlation = {correlation:.2f}"
252
-
253
- if pd.isna(correlation):
254
- corr_text = "ไม่สามารถคำนวณได้"
255
- corr_delta = "N/A"
256
- elif correlation > 0.3:
257
- corr_text = "มีความสัมพันธ์กันในทิศทางเดียวกัน"
258
- elif correlation < -0.3:
259
- corr_text = "มีความสัมพันธ์กันในทิศทางตรงข้าม"
260
-
261
- # 5. เทรนโมเดล Prediction (ใช้ข้อมูลที่ Merge แล้ว)
262
  plot_data["timestamp"] = (plot_data["date_day"] - plot_data["date_day"].min()).dt.days
263
-
264
- # แก้ปัญหา .fit() ถ้ามี NaN ใน sentiment
265
- train_data = plot_data.dropna(subset=['avg_sentiment', 'timestamp'])
266
- if len(train_data) < 2:
267
- st.warning("มีข้อมูลไม่พอสำหรับเทรนโมเดล")
268
- else:
269
- model = LinearRegression()
270
- model.fit(train_data[["timestamp"]], train_data["avg_sentiment"])
271
 
272
  future_days = 7
273
- future_timestamps = np.arange(plot_data["timestamp"].max() + 1, plot_data["timestamp"].max() + future_days + 1)
 
 
 
274
  future_dates = [plot_data["date_day"].max() + timedelta(days=i) for i in range(1, future_days + 1)]
275
- future_preds = model.predict(future_timestamps.reshape(-1, 1))
276
-
277
- # 6. (*** ใหม่ ***) แสดงผล Correlation Metric
278
- st.metric(
279
- label="วิเคราะห์ความสัมพันธ์ (Sentiment vs Price)",
280
- value=corr_text,
281
- delta=corr_delta
282
- )
283
 
284
- # 7. สร้างกราฟ (Plot) ด้วย Subplots (ใช้ 'plot_data' เป็นหลัก)
 
 
285
  fig = make_subplots(rows=2, cols=1, specs=[[{"secondary_y": True}], [{}]],
286
- row_heights=[0.7, 0.3], vertical_spacing=0.1,
287
- shared_xaxes=True)
288
 
289
- # --- กราฟส่วนบน (ราคา, Sentiment, Prediction) ---
290
  fig.add_trace(
291
  go.Scatter(
292
  x=plot_data["date_day"], y=plot_data["price"],
293
- name=f"{symbol} Stock Price",
294
- mode="lines+markers",
295
- connectgaps=True,
296
- line=dict(color="orange", width=2)
297
  ),
298
  row=1, col=1, secondary_y=False
299
  )
 
 
300
  fig.add_trace(
301
  go.Scatter(
302
  x=plot_data["date_day"], y=plot_data["avg_sentiment"],
303
- name="Actual Sentiment (Daily Avg)",
304
- mode="lines+markers",
305
- line=dict(color="blue", width=2)
306
  ),
307
  row=1, col=1, secondary_y=True
308
  )
309
-
310
- # เพิ่มการตรวจสอบว่า future_preds ถูกสร้างหรือยัง
311
- if 'future_preds' in locals():
312
- fig.add_trace(go.Scatter(
313
- x=future_dates, y=future_preds,
314
- mode="lines+markers", name="Predicted Sentiment (7-day Forecast)",
315
- line=dict(color="#02caf7", dash="dash")
316
- ),
317
- row=1, col=1,
318
- secondary_y=True
319
  )
320
 
321
- # --- กราFส่วนล่าง (จำนวนข่าว) ---
322
- 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)
323
- 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)
324
- 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)
325
-
326
- # 8. ตกแต่ง Layout
 
 
 
327
  fig.update_layout(
328
- title=f"แนวโน้มอารมณ์ข่าว & ราคาหุ้น '{keyword}'",
329
- template="plotly_white",
 
330
  hovermode="x unified",
331
- barmode='stack',
332
- legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1),
333
- height=600,
334
- margin=dict(l=20, r=20, t=80, b=20)
335
  )
336
-
337
- fig.update_yaxes(title_text="Stock Price", row=1, col=1, secondary_y=False)
338
- fig.update_yaxes(title_text="Sentiment Score", range=[-1, 1], row=1, col=1, secondary_y=True)
339
- fig.update_yaxes(title_text="Article Count", row=2, col=1)
340
- fig.update_xaxes(title_text="วันที่", row=2, col=1)
341
 
342
  st.plotly_chart(fig, use_container_width=True)
343
 
344
- # แสดงข่าว (ยังใช้ news_df ตัวเต็ม)
345
- st.subheader("📰 รายการข่าว")
346
  st.dataframe(news_df[["date", "source", "text", "sentiment", "url"]], use_container_width=True)
347
 
348
 
 
 
 
349
  if __name__ == "__main__":
350
  nltk.download("stopwords", quiet=True)
351
- main()
 
2
  import requests
3
  import pandas as pd
4
  from datetime import datetime, timedelta
 
 
5
  import nltk
6
  from wordcloud import WordCloud
7
  import base64
 
11
  import plotly.graph_objects as go
12
  from plotly.subplots import make_subplots
13
  import yfinance as yf
14
+ import torch
15
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification
16
 
17
  # --------------------------
18
  # CONFIG
 
20
  st.set_page_config(page_title="📰 News Sentiment Analysis for Young Investor", layout="wide")
21
  API_KEY = "88bc396d4eab4be494a4b86ec842db47"
22
 
23
+ # --------------------------
24
+ # โหลด FinBERT model
25
+ # --------------------------
26
+ @st.cache_resource
27
+ def load_finbert():
28
+ tokenizer = AutoTokenizer.from_pretrained("project-aps/finbert-finetune")
29
+ model = AutoModelForSequenceClassification.from_pretrained("project-aps/finbert-finetune")
30
+ return tokenizer, model
31
+
32
+ tokenizer, model = load_finbert()
33
+
34
  # --------------------------
35
  # UTILITIES
36
  # --------------------------
37
+ def analyze_text(text):
38
+ """วิเคราะห์อารมณ์ข่าวด้วย FinBERT"""
39
+ if not text or not text.strip():
40
  return 0
41
+
42
+ inputs = tokenizer(
43
+ text,
44
+ return_tensors="pt",
45
+ padding=True,
46
+ truncation=True,
47
+ max_length=512
48
+ )
49
+
50
+ with torch.no_grad():
51
+ outputs = model(**inputs)
52
+ logits = outputs.logits
53
+ probs = torch.softmax(logits, dim=1).numpy()[0]
54
+
55
+ # FinBERT = [negative, neutral, positive]
56
+ score = (-1 * probs[0]) + (0 * probs[1]) + (1 * probs[2])
57
+ return float(score)
58
 
59
 
60
  def generate_wordcloud(text):
 
86
  q = res["quotes"][0]
87
  ticker = q.get("symbol")
88
  name = q.get("longname", q.get("shortname", keyword))
89
+ except:
90
+ pass
91
 
92
  if not ticker:
93
  ticker = keyword.upper()
 
98
 
99
 
100
  # --------------------------
101
+ # ดึงข่าว 7 วัน
102
  # --------------------------
103
  @st.cache_data(ttl=3600)
104
  def fetch_financial_news(keyword):
 
145
 
146
 
147
  # --------------------------
148
+ # ดึงราคาหุ้น
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
+
157
  if df.empty:
158
+ st.warning("ไม่พบข้อมูลราคาหุ้น")
159
  return pd.DataFrame()
160
+
161
  df = df.reset_index()
162
  df_subset = df[['Date', 'Close']]
163
  df_subset.columns = ['date', 'price']
164
  df_subset["date"] = pd.to_datetime(df_subset["date"].dt.date)
165
+
166
  return df_subset
167
+
168
  except Exception as e:
169
+ st.warning(f"ดึงราคาหุ้นล้มเหลว: {e}")
170
  return pd.DataFrame()
171
 
172
 
 
175
  # --------------------------
176
  def main():
177
  st.title("📰 News Sentiment Analysis for Young Investor")
178
+ st.markdown("วิเคราะห์แนวโน้มอารมณ์ของข่าวย้อนหลัง 7 วัน พร้อมราคาหุ้น (FinBERT)")
179
 
180
  # Sidebar
181
  with st.sidebar:
 
183
  analyze_btn = st.button("วิเคราะห์เลย")
184
 
185
  if not analyze_btn:
186
+ st.info("กรอกคำค้นแล้วกด 'วิเคราะห์เลย'")
187
  return
188
 
 
 
189
  # ดึงข่าว
190
+ st.info(f"กำลังดึงข่าวย้อนหลัง 7 วันสำหรับ '{keyword}'...")
191
  news_df = fetch_financial_news(keyword)
192
  if news_df.empty:
193
+ st.warning("ไม่พบบทความข่าว")
194
  return
195
 
196
+ # วิเคราะห์ Sentiment
197
+ st.info("กำลังวิเคราะห์อารมณ์ข่าวด้วย FinBERT...")
198
+ news_df["sentiment"] = news_df["text"].apply(analyze_text)
199
  news_df["date"] = pd.to_datetime(news_df["date"])
200
 
201
+ # Metrics
202
  avg_sentiment = news_df["sentiment"].mean()
203
  pos_pct = (news_df["sentiment"] > 0.1).mean() * 100
204
  neg_pct = (news_df["sentiment"] < -0.1).mean() * 100
205
 
206
  col1, col2, col3 = st.columns(3)
207
+ col1.metric("ค่าเฉลี่ยอารมณ์ข่าว", f"{avg_sentiment:.2f}")
 
208
  col2.metric("ข่าวเชิงบวก", f"{pos_pct:.1f}%")
209
  col3.metric("ข่าวเชิงลบ", f"{neg_pct:.1f}%")
210
 
211
+ # WordCloud
212
+ st.subheader("☁️ Word Cloud")
213
  all_text = " ".join(news_df["text"].tolist())
214
  img = generate_wordcloud(all_text)
215
  st.image(f"data:image/png;base64,{img}", use_column_width=True)
216
 
217
+ # ---------------------------------------------------------
218
+ # เตรียมข้อมูลสำหรับกราฟ Sentiment & Price
219
+ # ---------------------------------------------------------
220
+ st.subheader("📈 แนวโน้มอารมณ์ข่าว & ราคาหุ้น")
221
+
 
222
  news_df["date_day"] = pd.to_datetime(news_df["date"].dt.date)
223
 
224
  def sentiment_type(score):
225
+ if score > 0.1:
226
+ return "positive"
227
+ if score < -0.1:
228
+ return "negative"
229
  return "neutral"
 
 
230
 
231
+ news_df["sentiment_type"] = news_df["sentiment"].apply(sentiment_type)
 
 
232
 
233
+ daily_avg = news_df.groupby("date_day")["sentiment"].mean().reset_index(name="avg_sentiment")
234
  daily_counts = news_df.groupby(["date_day", "sentiment_type"]).size().unstack(fill_value=0).reset_index()
235
+
236
+ df_sorted = pd.merge(daily_avg, daily_counts, on="date_day").sort_values("date_day")
237
+
 
 
 
 
 
 
238
  if len(df_sorted) < 2:
239
+ st.warning("ข้อมูลไม่พอสร้างแนวโน้ม")
240
+ st.dataframe(news_df)
 
241
  return
242
 
243
+ # ดึงราคาหุ้น
244
  _, symbol = resolve_company_symbol(keyword)
245
+ min_date, max_date = df_sorted["date_day"].min(), df_sorted["date_day"].max()
246
+
247
+ st.info(f"กำลังดึงราคาหุ้น {symbol} ...")
 
248
  stock_df = fetch_stock_price(symbol, min_date, max_date)
249
 
250
+ plot_data = pd.merge(df_sorted, stock_df, left_on="date_day", right_on="date", how="left")
 
 
 
 
 
 
 
251
 
252
+ # ---------------------------------------------------------
253
+ # Correlation
254
+ # ---------------------------------------------------------
255
  correlation = plot_data['price'].corr(plot_data['avg_sentiment'])
256
+
257
+ corr_text = "ไม่มีความสัมพันธ์"
258
+ if correlation > 0.5:
259
+ corr_text = "มีความสัมพันธ์เชิงบวก"
260
+ elif correlation < -0.5:
261
+ corr_text = "มีความสัมพันธ์เชิงลบ"
262
+
263
+ st.metric("ความสัมพันธ์ระหว่าง Sentiment Score กับราคาหุ้น", corr_text, f"{correlation:.2f}")
264
+
265
+ # ---------------------------------------------------------
266
+ # Forecast Sentiment
267
+ # ---------------------------------------------------------
 
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
+ # ---------------------------------------------------------
284
+ # Plot
285
+ # ---------------------------------------------------------
286
  fig = make_subplots(rows=2, cols=1, specs=[[{"secondary_y": True}], [{}]],
287
+ row_heights=[0.7, 0.3], vertical_spacing=0.1,
288
+ shared_xaxes=True)
289
 
290
+ # ราคาหุ้น
291
  fig.add_trace(
292
  go.Scatter(
293
  x=plot_data["date_day"], y=plot_data["price"],
294
+ name=f"{symbol} Price", mode="lines+markers", line=dict(color="orange")
 
 
 
295
  ),
296
  row=1, col=1, secondary_y=False
297
  )
298
+
299
+ # Sentiment จริง
300
  fig.add_trace(
301
  go.Scatter(
302
  x=plot_data["date_day"], y=plot_data["avg_sentiment"],
303
+ name="Actual Sentiment", mode="lines+markers", line=dict(color="blue")
 
 
304
  ),
305
  row=1, col=1, secondary_y=True
306
  )
307
+
308
+ # Sentiment พยากรณ์
309
+ if "future_preds" in locals():
310
+ fig.add_trace(
311
+ go.Scatter(
312
+ x=future_dates, y=future_preds,
313
+ name="Predicted Sentiment", mode="lines+markers", line=dict(dash="dash")
314
+ ),
315
+ row=1, col=1, secondary_y=True
 
316
  )
317
 
318
+ # จำนวนข่าว
319
+ for col in ["neutral", "negative", "positive"]:
320
+ if col not in plot_data.columns:
321
+ plot_data[col] = 0
322
+
323
+ fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["neutral"], name="Neutral"), row=2, col=1)
324
+ fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["negative"], name="Negative"), row=2, col=1)
325
+ fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["positive"], name="Positive"), row=2, col=1)
326
+
327
  fig.update_layout(
328
+ title=f"แนวโน้มอารมณ์ข่าว + ราคาหุ้น ({symbol})",
329
+ barmode="stack",
330
+ height=650,
331
  hovermode="x unified",
332
+ template="plotly_white"
 
 
 
333
  )
 
 
 
 
 
334
 
335
  st.plotly_chart(fig, use_container_width=True)
336
 
337
+ # แสดงรายการข่าว
338
+ st.subheader("📰 รายการข่าวทั้งหมด")
339
  st.dataframe(news_df[["date", "source", "text", "sentiment", "url"]], use_container_width=True)
340
 
341
 
342
+ # ---------------------------------------------------------
343
+ # RUN APP
344
+ # ---------------------------------------------------------
345
  if __name__ == "__main__":
346
  nltk.download("stopwords", quiet=True)
347
+ main()