KYTHY commited on
Commit
2c9423f
·
verified ·
1 Parent(s): 98d1784

Update app.py

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