KYTHY commited on
Commit
c1b77e9
·
verified ·
1 Parent(s): 5bb810d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +157 -166
app.py CHANGED
@@ -2,14 +2,17 @@ import streamlit as st
2
  import requests
3
  import pandas as pd
4
  from datetime import datetime, timedelta
 
 
5
  import nltk
 
 
 
6
  import numpy as np
7
  from sklearn.linear_model import LinearRegression
8
  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, pipeline
13
 
14
  # --------------------------
15
  # CONFIG
@@ -17,62 +20,22 @@ from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipe
17
  st.set_page_config(page_title="📰 News Sentiment Analysis for Young Investor", layout="wide")
18
  API_KEY = "88bc396d4eab4be494a4b86ec842db47"
19
 
20
- # --------------------------
21
- # โหลด FinBERT model
22
- # --------------------------
23
- @st.cache_resource
24
- def load_finbert():
25
- tokenizer = AutoTokenizer.from_pretrained("project-aps/finbert-finetune")
26
- model = AutoModelForSequenceClassification.from_pretrained("project-aps/finbert-finetune")
27
- return tokenizer, model
28
-
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
  # UTILITIES
43
  # --------------------------
44
- def analyze_text(text):
45
- """วิเคราะห์อารมณ์ของข่าวด้วย FinBERT"""
46
- if not text or not text.strip():
47
  return 0
 
 
 
48
 
49
- inputs = tokenizer(
50
- text,
51
- return_tensors="pt",
52
- padding=True,
53
- truncation=True,
54
- max_length=512
55
- )
56
-
57
- with torch.no_grad():
58
- outputs = model(**inputs)
59
- logits = outputs.logits
60
- probs = torch.softmax(logits, dim=1).numpy()[0]
61
-
62
- # FinBERT = [negative, neutral, positive]
63
- score = (-1 * probs[0]) + (0 * probs[1]) + (1 * probs[2])
64
- return float(score)
65
-
66
-
67
- def summarize_themes(news_texts):
68
- """สรุปธีมข่าวด้วย Zero-shot classification"""
69
- themes = []
70
- for text in news_texts:
71
- if not text.strip():
72
- continue
73
- result = theme_classifier(text, candidate_labels)
74
- themes.append(result["labels"][0])
75
- return themes
76
 
77
  # --------------------------
78
  # แปลงชื่อ/ตัวย่อ → (Company Name, Symbol)
@@ -81,7 +44,6 @@ def resolve_company_symbol(keyword: str):
81
  keyword = keyword.strip()
82
  ticker = None
83
  name = None
84
-
85
  try:
86
  data = yf.Ticker(keyword)
87
  info = data.info
@@ -95,27 +57,23 @@ def resolve_company_symbol(keyword: str):
95
  q = res["quotes"][0]
96
  ticker = q.get("symbol")
97
  name = q.get("longname", q.get("shortname", keyword))
98
- except:
99
- pass
100
-
101
  if not ticker:
102
  ticker = keyword.upper()
103
  if not name:
104
  name = keyword.capitalize()
105
-
106
  return name, ticker
107
 
108
  # --------------------------
109
- # ดึงข่าว 7 วัน
110
  # --------------------------
111
  @st.cache_data(ttl=3600)
112
  def fetch_financial_news(keyword):
113
  company, symbol = resolve_company_symbol(keyword)
114
  to_date = datetime.now().strftime('%Y-%m-%d')
115
  from_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d')
116
-
117
  query_keyword = f"({company} OR {symbol}) finance stock"
118
-
119
  all_articles = []
120
  page = 1
121
  while True:
@@ -131,11 +89,9 @@ def fetch_financial_news(keyword):
131
  if data.get("status") != "ok":
132
  st.error(f"API Error: {data}")
133
  break
134
-
135
  articles = data.get("articles", [])
136
  if not articles:
137
  break
138
-
139
  for a in articles:
140
  if a["description"]:
141
  all_articles.append({
@@ -144,15 +100,13 @@ def fetch_financial_news(keyword):
144
  "source": a["source"]["name"],
145
  "url": a["url"]
146
  })
147
-
148
  if len(articles) < 100:
149
  break
150
  page += 1
151
-
152
  return pd.DataFrame(all_articles)
153
 
154
  # --------------------------
155
- # ดึงราคาหุ้น
156
  # --------------------------
157
  @st.cache_data(ttl=3600)
158
  def fetch_stock_price(symbol, start_date, end_date):
@@ -160,20 +114,16 @@ def fetch_stock_price(symbol, start_date, end_date):
160
  start_str = (start_date - timedelta(days=2)).strftime('%Y-%m-%d')
161
  end_str = (end_date + timedelta(days=1)).strftime('%Y-%m-%d')
162
  df = yf.download(symbol, start=start_str, end=end_str, interval="1d")
163
-
164
  if df.empty:
165
- st.warning("ไม่พบข้อมูลราคาหุ้น")
166
  return pd.DataFrame()
167
-
168
  df = df.reset_index()
169
  df_subset = df[['Date', 'Close']]
170
  df_subset.columns = ['date', 'price']
171
  df_subset["date"] = pd.to_datetime(df_subset["date"].dt.date)
172
-
173
  return df_subset
174
-
175
  except Exception as e:
176
- st.warning(f"ดึงราคาหุ้นล้มเหลว: {e}")
177
  return pd.DataFrame()
178
 
179
  # --------------------------
@@ -189,44 +139,44 @@ def main():
189
  analyze_btn = st.button("วิเคราะห์เลย")
190
 
191
  if not analyze_btn:
192
- st.info("กรอกคำค้นแล้วกด 'วิเคราะห์เลย'")
193
  return
194
 
 
195
  # ดึงข่าว
196
- st.info(f"กำลังดึงข่าวย้อนหลัง 7 วันสำหรับ '{keyword}'...")
197
  news_df = fetch_financial_news(keyword)
 
198
  if news_df.empty:
199
- st.warning("ไม่พบบทความข่าว")
200
  return
201
 
202
- # วิเคราะห์ Sentiment
203
  st.info("กำลังวิเคราะห์อารมณ์ของข่าว...")
204
- news_df["sentiment"] = news_df["text"].apply(analyze_text)
205
  news_df["date"] = pd.to_datetime(news_df["date"])
206
 
207
- # Metrics
208
  avg_sentiment = news_df["sentiment"].mean()
209
  pos_pct = (news_df["sentiment"] > 0.1).mean() * 100
210
  neg_pct = (news_df["sentiment"] < -0.1).mean() * 100
211
 
212
  col1, col2, col3 = st.columns(3)
213
- col1.metric("ค่าเฉลี่ยอารมณ์ข่าว", f"{avg_sentiment:.2f}")
214
  col2.metric("ข่าวเชิงบวก", f"{pos_pct:.1f}%")
215
  col3.metric("ข่าวเชิงลบ", f"{neg_pct:.1f}%")
216
 
217
- # ---------------------------------------------------------
218
- # ธีมข่าวแทน Word Cloud
219
- # ---------------------------------------------------------
220
- st.subheader("📰 ธีมข่าว (Top Theme per Article)")
221
- news_df["theme"] = summarize_themes(news_df["text"].tolist())
222
- theme_counts = news_df["theme"].value_counts()
223
- st.bar_chart(theme_counts)
224
-
225
- # ---------------------------------------------------------
226
- # ส่วนกราฟ Sentiment & Price (เหมือนเดิม)
227
- # ---------------------------------------------------------
228
- st.subheader("📈 แนวโน้มอารมณ์ของข่าว & ราคาหุ้น")
229
 
 
 
 
 
 
230
  news_df["date_day"] = pd.to_datetime(news_df["date"].dt.date)
231
 
232
  def sentiment_type(score):
@@ -237,99 +187,140 @@ def main():
237
  return "neutral"
238
 
239
  news_df["sentiment_type"] = news_df["sentiment"].apply(sentiment_type)
240
-
241
- daily_avg = news_df.groupby("date_day")["sentiment"].mean().reset_index(name="avg_sentiment")
 
242
  daily_counts = news_df.groupby(["date_day", "sentiment_type"]).size().unstack(fill_value=0).reset_index()
 
243
 
244
- df_sorted = pd.merge(daily_avg, daily_counts, on="date_day").sort_values("date_day")
 
 
 
245
 
246
  if len(df_sorted) < 2:
247
- st.warning("ข้อมูลไม่พอสร้างแนวโน้ม")
248
- st.dataframe(news_df)
 
249
  return
250
 
251
- # ดึงราคาหุ้น
252
  _, symbol = resolve_company_symbol(keyword)
253
- min_date, max_date = df_sorted["date_day"].min(), df_sorted["date_day"].max()
254
- st.info(f"กำลังดึงราคาหุ้น {symbol} ...")
 
255
  stock_df = fetch_stock_price(symbol, min_date, max_date)
256
 
257
- plot_data = pd.merge(df_sorted, stock_df, left_on="date_day", right_on="date", how="left")
 
 
 
 
 
 
 
258
 
259
- # Correlation
260
  correlation = plot_data['price'].corr(plot_data['avg_sentiment'])
261
- corr_text = "ไม่มีความสัมพันธ์"
262
- if correlation > 0.5:
263
- corr_text = "มีความสัมพันธ์ในทิศทางเดียวกัน"
264
- elif correlation < -0.5:
265
- corr_text = "มีความสัมพันธ์ในทิศทางตรงข้าม"
266
- st.metric("วิเคราะห์ความสัมพันธ์ระหว่างอารมณ์ของข่าวกับราคาหุ้น", corr_text, f"{correlation:.2f}")
267
-
268
- # Forecast Sentiment
 
 
 
269
  plot_data["timestamp"] = (plot_data["date_day"] - plot_data["date_day"].min()).dt.days
270
- train_data = plot_data.dropna(subset=['avg_sentiment'])
 
 
 
 
 
 
 
 
 
 
 
271
 
272
- if len(train_data) >= 2:
273
- model_lr = LinearRegression()
274
- model_lr.fit(train_data[["timestamp"]], train_data["avg_sentiment"])
 
 
 
275
 
276
- future_days = 7
277
- future_timestamps = np.arange(
278
- plot_data["timestamp"].max() + 1,
279
- plot_data["timestamp"].max() + future_days + 1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  )
281
- future_dates = [plot_data["date_day"].max() + timedelta(days=i) for i in range(1, future_days + 1)]
282
- future_preds = model_lr.predict(future_timestamps.reshape(-1, 1))
283
-
284
- # Plot
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
- # ราคาหุ้น
290
- fig.add_trace(go.Scatter(x=plot_data["date_day"], y=plot_data["price"], name=f"{symbol} Price",
291
- mode="lines+markers", line=dict(color="orange")), row=1, col=1, secondary_y=False)
292
- # Sentiment จริง
293
- fig.add_trace(go.Scatter(x=plot_data["date_day"], y=plot_data["avg_sentiment"], name="Actual Sentiment",
294
- mode="lines+markers", line=dict(color="blue")), row=1, col=1, secondary_y=True)
295
- # Sentiment พยากรณ์
296
- if "future_preds" in locals():
297
- fig.add_trace(go.Scatter(x=future_dates, y=future_preds, name="Predicted Sentiment",
298
- mode="lines+markers", line=dict(color="#05a0fa", dash="dash")), row=1, col=1, secondary_y=True)
299
- # เส้นเชื่อม Actual -> Predicted
300
- last_actual_date = plot_data["date_day"].max()
301
- last_actual_value = plot_data["avg_sentiment"].iloc[-1]
302
- first_pred_date = future_dates[0]
303
- first_pred_value = future_preds[0]
304
- fig.add_trace(go.Scatter(x=[last_actual_date, first_pred_date],
305
- y=[last_actual_value, first_pred_value],
306
- mode="lines",
307
- line=dict(color="#05a0fa", dash="dot"),
308
- name="Connector Actual→Predicted"), row=1, col=1, secondary_y=True)
309
-
310
- # จำนวนข่าว
311
- for col in ["neutral", "negative", "positive"]:
312
- if col not in plot_data.columns:
313
- plot_data[col] = 0
314
- fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["neutral"], name="Neutral",
315
- marker_color='rgba(128, 128, 128, 0.7)'), row=2, col=1)
316
- fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["negative"], name="Negative",
317
- marker_color='rgba(255, 0, 0, 0.7)'), row=2, col=1)
318
- fig.add_trace(go.Bar(x=plot_data["date_day"], y=plot_data["positive"], name="Positive",
319
- marker_color='rgba(0, 128, 0, 0.7)'), row=2, col=1)
320
-
321
- fig.update_layout(title=f"แนวโน้มอารมณ์ของข่าว + ราคาหุ้น ({symbol})",
322
- barmode="stack", height=650, hovermode="x unified", template="plotly_white")
323
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  st.plotly_chart(fig, use_container_width=True)
325
 
326
- # แสดงรายการข่าว
327
- st.subheader("📰 รายการข่าวทั้งหมด")
328
- st.dataframe(news_df[["date", "source", "text", "sentiment", "theme", "url"]], use_container_width=True)
329
 
330
- # ---------------------------------------------------------
331
- # RUN APP
332
- # ---------------------------------------------------------
333
  if __name__ == "__main__":
334
  nltk.download("stopwords", quiet=True)
335
- main()
 
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
10
+ from io import BytesIO
11
  import numpy as np
12
  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
  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')
35
+ wordcloud = WordCloud(width=800, height=400, background_color="white", stopwords=stopwords).generate(text)
36
+ buf = BytesIO()
37
+ wordcloud.to_image().save(buf, format="PNG")
38
+ return base64.b64encode(buf.getvalue()).decode()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  # --------------------------
41
  # แปลงชื่อ/ตัวย่อ → (Company Name, Symbol)
 
44
  keyword = keyword.strip()
45
  ticker = None
46
  name = None
 
47
  try:
48
  data = yf.Ticker(keyword)
49
  info = data.info
 
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
  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
  "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
  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
  # --------------------------
 
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
  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()