KYTHY commited on
Commit
983bbc8
·
verified ·
1 Parent(s): b3c91f0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +82 -10
app.py CHANGED
@@ -10,12 +10,16 @@ import yfinance as yf
10
  import torch
11
  from transformers import AutoTokenizer, AutoModelForSequenceClassification
12
 
 
13
  # --------------------------
14
- # CONFIG
15
  # --------------------------
16
  st.set_page_config(page_title="📰 News Sentiment Analysis for Young Investor", layout="wide")
 
 
17
  API_KEY = "88bc396d4eab4be494a4b86ec842db47"
18
 
 
19
  # --------------------------
20
  # โหลด FinBERT model
21
  # --------------------------
@@ -27,14 +31,18 @@ def load_finbert():
27
 
28
  tokenizer, model = load_finbert()
29
 
 
30
  # --------------------------
31
- # UTILITIES
32
  # --------------------------
 
 
33
  def analyze_text(text):
34
  """วิเคราะห์อารมณ์ของข่าว"""
35
  if not text or not text.strip():
36
  return 0
37
 
 
38
  inputs = tokenizer(
39
  text,
40
  return_tensors="pt",
@@ -43,30 +51,43 @@ def analyze_text(text):
43
  max_length=512
44
  )
45
 
 
46
  with torch.no_grad():
47
  outputs = model(**inputs)
48
  logits = outputs.logits
49
  probs = torch.softmax(logits, dim=1).numpy()[0]
50
 
 
51
  # FinBERT = [negative, neutral, positive]
 
52
  score = (-1 * probs[0]) + (0 * probs[1]) + (1 * probs[2])
53
  return float(score)
54
 
55
 
56
  # --------------------------
57
- # แปลงชื่อ/ตัวย่อ (Company Name, Symbol)
58
  # --------------------------
 
 
 
59
  def resolve_company_symbol(keyword: str):
 
 
60
  keyword = keyword.strip()
61
  ticker = None
62
  name = None
63
 
 
64
  try:
65
  data = yf.Ticker(keyword)
66
  info = data.info
 
 
67
  if "symbol" in info and info["symbol"]:
68
  ticker = info["symbol"]
69
  name = info.get("longName", info.get("shortName", keyword))
 
 
70
  else:
71
  url = f"https://query2.finance.yahoo.com/v1/finance/search?q={keyword}"
72
  res = requests.get(url).json()
@@ -82,20 +103,30 @@ def resolve_company_symbol(keyword: str):
82
  if not name:
83
  name = keyword.capitalize()
84
 
 
85
  return name, ticker
86
 
87
 
88
  # --------------------------
89
  # ดึงข่าว 7 วัน
90
  # --------------------------
 
 
91
  @st.cache_data(ttl=3600)
92
  def fetch_financial_news(keyword):
 
 
93
  company, symbol = resolve_company_symbol(keyword)
 
 
94
  to_date = datetime.now().strftime('%Y-%m-%d')
95
  from_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d')
96
 
 
 
97
  query_keyword = f"({company} OR {symbol}) finance stock"
98
 
 
99
  all_articles = []
100
  page = 1
101
  while True:
@@ -116,6 +147,7 @@ def fetch_financial_news(keyword):
116
  if not articles:
117
  break
118
 
 
119
  for a in articles:
120
  if a["description"]:
121
  all_articles.append({
@@ -135,11 +167,22 @@ def fetch_financial_news(keyword):
135
  # --------------------------
136
  # ดึงราคาหุ้น
137
  # --------------------------
 
 
 
 
 
 
138
  @st.cache_data(ttl=3600)
139
  def fetch_stock_price(symbol, start_date, end_date):
 
140
  try:
 
 
141
  start_str = (start_date - timedelta(days=2)).strftime('%Y-%m-%d')
142
  end_str = (end_date + timedelta(days=1)).strftime('%Y-%m-%d')
 
 
143
  df = yf.download(symbol, start=start_str, end=end_str, interval="1d")
144
 
145
  if df.empty:
@@ -151,6 +194,7 @@ def fetch_stock_price(symbol, start_date, end_date):
151
  df_subset.columns = ['date', 'price']
152
  df_subset["date"] = pd.to_datetime(df_subset["date"].dt.date)
153
 
 
154
  return df_subset
155
 
156
  except Exception as e:
@@ -161,11 +205,17 @@ def fetch_stock_price(symbol, start_date, end_date):
161
  # --------------------------
162
  # MAIN APP
163
  # --------------------------
 
 
164
  def main():
 
 
165
  st.title("📰 News Sentiment Analysis for Young Investor")
 
 
166
  st.markdown("วิเคราะห์แนวโน้มอารมณ์ของข่าวย้อนหลัง 7 วัน พร้อมราคาหุ้น")
167
 
168
- # Sidebar
169
  with st.sidebar:
170
  keyword = st.text_input("ค้นหา Stock Symbol (เช่น AAPL, TSLA):", "")
171
  analyze_btn = st.button("วิเคราะห์เลย")
@@ -186,13 +236,14 @@ def main():
186
  news_df["sentiment"] = news_df["text"].apply(analyze_text)
187
  news_df["date"] = pd.to_datetime(news_df["date"])
188
 
189
- # Metrics
190
  avg_sentiment = news_df["sentiment"].mean()
191
  pos_pct = (news_df["sentiment"] > 0.1).mean() * 100
192
  neg_pct = (news_df["sentiment"] < -0.1).mean() * 100
193
 
 
194
  col1, col2, col3 = st.columns(3)
195
- col1.metric("ค่าเฉลี่ยอารมณ์ข่าว", f"{avg_sentiment:.2f}")
196
  col2.metric("ข่าวเชิงบวก", f"{pos_pct:.1f}%")
197
  col3.metric("ข่าวเชิงลบ", f"{neg_pct:.1f}%")
198
 
@@ -202,8 +253,10 @@ def main():
202
  # ---------------------------------------------------------
203
  st.subheader("📈 แนวโน้มอารมณ์ของข่าว & ราคาหุ้น")
204
 
 
205
  news_df["date_day"] = pd.to_datetime(news_df["date"].dt.date)
206
 
 
207
  def sentiment_type(score):
208
  if score > 0.1:
209
  return "positive"
@@ -213,9 +266,13 @@ def main():
213
 
214
  news_df["sentiment_type"] = news_df["sentiment"].apply(sentiment_type)
215
 
 
216
  daily_avg = news_df.groupby("date_day")["sentiment"].mean().reset_index(name="avg_sentiment")
 
 
217
  daily_counts = news_df.groupby(["date_day", "sentiment_type"]).size().unstack(fill_value=0).reset_index()
218
 
 
219
  df_sorted = pd.merge(daily_avg, daily_counts, on="date_day").sort_values("date_day")
220
 
221
  if len(df_sorted) < 2:
@@ -230,14 +287,19 @@ def main():
230
  st.info(f"กำลังดึงราคาหุ้น {symbol} ...")
231
  stock_df = fetch_stock_price(symbol, min_date, max_date)
232
 
 
 
233
  plot_data = pd.merge(df_sorted, stock_df, left_on="date_day", right_on="date", how="left")
234
 
 
235
  # ---------------------------------------------------------
236
  # Correlation
237
  # ---------------------------------------------------------
 
 
238
  correlation = plot_data['price'].corr(plot_data['avg_sentiment'])
239
 
240
- # หาข้อความอธิบาย (ข้อความใหญ่ด้านบน)
241
  if correlation > 0.7:
242
  corr_label = "มีความสัมพันธ์กันอย่างมากในทิศทางเดียวกัน"
243
  elif correlation > 0.4:
@@ -261,24 +323,34 @@ def main():
261
  corr_value_text # ตัวล่าง (สีเขียว/แดง)
262
  )
263
 
 
264
  # ---------------------------------------------------------
265
  # Forecast Sentiment
266
  # ---------------------------------------------------------
 
 
267
  plot_data["timestamp"] = (plot_data["date_day"] - plot_data["date_day"].min()).dt.days
268
  train_data = plot_data.dropna(subset=['avg_sentiment'])
269
 
 
270
  if len(train_data) >= 2:
 
 
271
  model_lr = LinearRegression()
272
  model_lr.fit(train_data[["timestamp"]], train_data["avg_sentiment"])
273
 
 
274
  future_days = 7
275
  future_timestamps = np.arange(
276
  plot_data["timestamp"].max() + 1,
277
  plot_data["timestamp"].max() + future_days + 1
278
  )
 
 
279
  future_dates = [plot_data["date_day"].max() + timedelta(days=i) for i in range(1, future_days + 1)]
280
  future_preds = model_lr.predict(future_timestamps.reshape(-1, 1))
281
 
 
282
  # ---------------------------------------------------------
283
  # Plot
284
  # ---------------------------------------------------------
@@ -315,7 +387,7 @@ def main():
315
  )
316
 
317
  # ---------------------------------------------------------
318
- # เส้นเชื่อม Actual -> Predicted
319
  # ---------------------------------------------------------
320
  last_actual_date = plot_data["date_day"].max()
321
  last_actual_value = plot_data["avg_sentiment"].iloc[-1]
@@ -334,7 +406,7 @@ def main():
334
  )
335
 
336
 
337
- # จำนวนข่าว
338
  for col in ["neutral", "negative", "positive"]:
339
  if col not in plot_data.columns:
340
  plot_data[col] = 0
@@ -357,7 +429,7 @@ def main():
357
 
358
  st.plotly_chart(fig, use_container_width=True)
359
 
360
- # แสดงรายการข่าว
361
  st.subheader("📰 รายการข่าวทั้งหมด")
362
  st.dataframe(news_df[["date", "source", "text", "sentiment", "url"]], use_container_width=True)
363
 
 
10
  import torch
11
  from transformers import AutoTokenizer, AutoModelForSequenceClassification
12
 
13
+
14
  # --------------------------
15
+ # CONFIG ตั้งค่า Streamlit
16
  # --------------------------
17
  st.set_page_config(page_title="📰 News Sentiment Analysis for Young Investor", layout="wide")
18
+
19
+ # API จากเว็บ newsapi.org
20
  API_KEY = "88bc396d4eab4be494a4b86ec842db47"
21
 
22
+
23
  # --------------------------
24
  # โหลด FinBERT model
25
  # --------------------------
 
31
 
32
  tokenizer, model = load_finbert()
33
 
34
+
35
  # --------------------------
36
+ # UTILITIES
37
  # --------------------------
38
+
39
+ # ฟังก์ชันวิเคราะห์ Sentiment
40
  def analyze_text(text):
41
  """วิเคราะห์อารมณ์ของข่าว"""
42
  if not text or not text.strip():
43
  return 0
44
 
45
+ # แปลงข้อความเป็นตัวเลข
46
  inputs = tokenizer(
47
  text,
48
  return_tensors="pt",
 
51
  max_length=512
52
  )
53
 
54
+ #ส่งข้อความเข้า Model
55
  with torch.no_grad():
56
  outputs = model(**inputs)
57
  logits = outputs.logits
58
  probs = torch.softmax(logits, dim=1).numpy()[0]
59
 
60
+ # โมเดล FinBERT แบ่งออกเป็น 3 คลาส
61
  # FinBERT = [negative, neutral, positive]
62
+ # คูณความน่าจะเป็นของแต่ละคลาสกับ -1, 0, 1 แล้วรวมกันเป็นคะแนน
63
  score = (-1 * probs[0]) + (0 * probs[1]) + (1 * probs[2])
64
  return float(score)
65
 
66
 
67
  # --------------------------
68
+ # แปลงชื่อบริษัท/ชื่อหุ้น เพื่อให้ไม่ว่าจะค้นหาด้วยชื่อไหน จะต้องได้ผลลัพธ์เหมือนกัน
69
  # --------------------------
70
+
71
+ # ฟังก์ชันสำหรับหาชื่อหุ้นหรือชื่อบริษัท
72
+ # ถ้า Keyword เป็นชื่อบริษัท ให้หาชื่อหุ้น, ถ้า Keyword เป็นชื่อหุ้น ให้หาชื่อบริษัท
73
  def resolve_company_symbol(keyword: str):
74
+
75
+ # รับ Keyword เป็นชื่อบริษัท หรือ ชื่อหุ้น อย่างใดอย่างหนึ่ง
76
  keyword = keyword.strip()
77
  ticker = None
78
  name = None
79
 
80
+ # ดึงข้อมูลชื่อบริษัท/ชื่อหุ้นจาก yfinance
81
  try:
82
  data = yf.Ticker(keyword)
83
  info = data.info
84
+
85
+ # ตรวจสอบข้อมูลใน Info (ข้อมูลหุ้นของ keyword ที่ส่งเข้าไป)
86
  if "symbol" in info and info["symbol"]:
87
  ticker = info["symbol"]
88
  name = info.get("longName", info.get("shortName", keyword))
89
+
90
+ # ถ้าไม่หาไม่เจอให้ค้นหาผ่าน Yahoo Finance API
91
  else:
92
  url = f"https://query2.finance.yahoo.com/v1/finance/search?q={keyword}"
93
  res = requests.get(url).json()
 
103
  if not name:
104
  name = keyword.capitalize()
105
 
106
+ # คืนค่าเป็น tuple (ชื่อบริษัท, ชื่อหุ้น) เช่น (Apple Inc., AAPL)
107
  return name, ticker
108
 
109
 
110
  # --------------------------
111
  # ดึงข่าว 7 วัน
112
  # --------------------------
113
+
114
+ # ฟังก์ชันสำหรับดึงข่าว
115
  @st.cache_data(ttl=3600)
116
  def fetch_financial_news(keyword):
117
+
118
+ # หลังจากรับ Keyword เป็นชื่อบริษัท/ชื่อหุ้นอย่างใดอย่างหนึ่ง จะเรียกฟังชันก์ resolve_company_symbol เพื่อให้คืนค่าทั้งชื่อบริษัทและชื่อหุ้น
119
  company, symbol = resolve_company_symbol(keyword)
120
+
121
+ # กำหนดช่วงวันที่ค้นหาเป็น 7 วันล่าสุด
122
  to_date = datetime.now().strftime('%Y-%m-%d')
123
  from_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d')
124
 
125
+ # สร้าง query สำหรับ News API เพื่อให้สามารถค้นเจอข่าวที่มีชื่อบริษัทหรือชื่อหุ้นก็ได้
126
+ # เช่น กรอก AAPL ต้องเจอข่าวที่มีคำว่า Apple Inc. ด้วย แม้จะไม่มีคำว่า AAPL อยู่ในข่าวก็ตาม
127
  query_keyword = f"({company} OR {symbol}) finance stock"
128
 
129
+ # ดึงข่าวทุกหน้าจนกว่าข่าวจะหมด โดยดึงข่าวจาก API ของเว็บ newsapi.org
130
  all_articles = []
131
  page = 1
132
  while True:
 
147
  if not articles:
148
  break
149
 
150
+ # เก็บข่าวลง list
151
  for a in articles:
152
  if a["description"]:
153
  all_articles.append({
 
167
  # --------------------------
168
  # ดึงราคาหุ้น
169
  # --------------------------
170
+
171
+ # ฟังก์ชันสำหรับดึงราคาหุ้น
172
+ # รับพารามิเตอร์:
173
+ # symbol → ชื่อหุ้น (Ticker)
174
+ # start_date → วันที่เริ่มต้น (datetime object)
175
+ # end_date → วันที่สิ้นสุด (datetime object)
176
  @st.cache_data(ttl=3600)
177
  def fetch_stock_price(symbol, start_date, end_date):
178
+
179
  try:
180
+
181
+ # ขยายช่วงวันที่เล็กน้อย เนื่องจากบางครั้งตลาดหุ้นปิดวันเสาร์-อาทิตย์ และวันหยุดนนักขัตฤกษ์ การขยายช่วงจะช่วยให้ได้ข้อมูลครบ
182
  start_str = (start_date - timedelta(days=2)).strftime('%Y-%m-%d')
183
  end_str = (end_date + timedelta(days=1)).strftime('%Y-%m-%d')
184
+
185
+ # ดึงราคาหุ้นจาก yfinance โดยใช้ราคาปิดรายวัน
186
  df = yf.download(symbol, start=start_str, end=end_str, interval="1d")
187
 
188
  if df.empty:
 
194
  df_subset.columns = ['date', 'price']
195
  df_subset["date"] = pd.to_datetime(df_subset["date"].dt.date)
196
 
197
+ # คืนค่าเป็น DataFrame ที่มี 2 คอลัมน์ คือ date และ price
198
  return df_subset
199
 
200
  except Exception as e:
 
205
  # --------------------------
206
  # MAIN APP
207
  # --------------------------
208
+
209
+ # ฟังก์ชันหลักของ Streamlit app
210
  def main():
211
+
212
+ # หัวข้อใหญ่ของแอป
213
  st.title("📰 News Sentiment Analysis for Young Investor")
214
+
215
+ # ข้อความอธิบาย
216
  st.markdown("วิเคราะห์แนวโน้มอารมณ์ของข่าวย้อนหลัง 7 วัน พร้อมราคาหุ้น")
217
 
218
+ # Sidebar สำหรับ Input
219
  with st.sidebar:
220
  keyword = st.text_input("ค้นหา Stock Symbol (เช่น AAPL, TSLA):", "")
221
  analyze_btn = st.button("วิเคราะห์เลย")
 
236
  news_df["sentiment"] = news_df["text"].apply(analyze_text)
237
  news_df["date"] = pd.to_datetime(news_df["date"])
238
 
239
+ # คำนวณ Metrics
240
  avg_sentiment = news_df["sentiment"].mean()
241
  pos_pct = (news_df["sentiment"] > 0.1).mean() * 100
242
  neg_pct = (news_df["sentiment"] < -0.1).mean() * 100
243
 
244
+ # แสดง Metrics บน App เป็น 3 คอลัมน์ ได้แก่ ค่าเฉลี่ย Sentment, % ข่าวเชิงบวก, % ข่าวเชิงลบ
245
  col1, col2, col3 = st.columns(3)
246
+ col1.metric("ค่าเฉลี่ยอารมณ์ของข่าว", f"{avg_sentiment:.2f}")
247
  col2.metric("ข่าวเชิงบวก", f"{pos_pct:.1f}%")
248
  col3.metric("ข่าวเชิงลบ", f"{neg_pct:.1f}%")
249
 
 
253
  # ---------------------------------------------------------
254
  st.subheader("📈 แนวโน้มอารมณ์ของข่าว & ราคาหุ้น")
255
 
256
+ # สร้างคอลัมน์วันที่
257
  news_df["date_day"] = pd.to_datetime(news_df["date"].dt.date)
258
 
259
+ # ฟังก์ชันจำแนก Sentiment
260
  def sentiment_type(score):
261
  if score > 0.1:
262
  return "positive"
 
266
 
267
  news_df["sentiment_type"] = news_df["sentiment"].apply(sentiment_type)
268
 
269
+ # คำนวณค่าเฉลี่ย Sentiment ต่อวัน
270
  daily_avg = news_df.groupby("date_day")["sentiment"].mean().reset_index(name="avg_sentiment")
271
+
272
+ # นับจำนวนข่าวรายวัน
273
  daily_counts = news_df.groupby(["date_day", "sentiment_type"]).size().unstack(fill_value=0).reset_index()
274
 
275
+ # รวม DataFrame ของค่าเฉลี่ย และ จำนวนข่าว
276
  df_sorted = pd.merge(daily_avg, daily_counts, on="date_day").sort_values("date_day")
277
 
278
  if len(df_sorted) < 2:
 
287
  st.info(f"กำลังดึงราคาหุ้น {symbol} ...")
288
  stock_df = fetch_stock_price(symbol, min_date, max_date)
289
 
290
+ # รวม DataFrame ของข่าว (ค่าเฉลี่ยและจำนวนข่าวที่รวมกันแล้ว) และ ราคาหุ้น
291
+ # ผลลัพธ์จะมี 4 คอลัมน์ ได้แก่ วันที่, ค่าเฉลี่ย, จำนวนข่าว, ราคาหุ้น เพื่อให้พร้อมสำหรับทำกราฟ
292
  plot_data = pd.merge(df_sorted, stock_df, left_on="date_day", right_on="date", how="left")
293
 
294
+
295
  # ---------------------------------------------------------
296
  # Correlation
297
  # ---------------------------------------------------------
298
+
299
+ # คำนวณ correlation ระหว่างราคาหุ้นกับค่า sentiment
300
  correlation = plot_data['price'].corr(plot_data['avg_sentiment'])
301
 
302
+ # แปลงค่า correlation เป็นข้อความอธิบาย
303
  if correlation > 0.7:
304
  corr_label = "มีความสัมพันธ์กันอย่างมากในทิศทางเดียวกัน"
305
  elif correlation > 0.4:
 
323
  corr_value_text # ตัวล่าง (สีเขียว/แดง)
324
  )
325
 
326
+
327
  # ---------------------------------------------------------
328
  # Forecast Sentiment
329
  # ---------------------------------------------------------
330
+
331
+ # แปลง date เป็นตัวเลข โดยนับวันแรกเป็นวันที่ 0 และวันถัดมาเป็น 1, 2, ... เพื่อทำ Linear Regression
332
  plot_data["timestamp"] = (plot_data["date_day"] - plot_data["date_day"].min()).dt.days
333
  train_data = plot_data.dropna(subset=['avg_sentiment'])
334
 
335
+ # ตรวจสอบว่ามีข้อมูลเพียงพอ
336
  if len(train_data) >= 2:
337
+
338
+ # สร้างโมเดล Linear Regression โดยให้ x = จำนวนวัน, y = ค่าเฉลี่ย sentiment ต่อวัน
339
  model_lr = LinearRegression()
340
  model_lr.fit(train_data[["timestamp"]], train_data["avg_sentiment"])
341
 
342
+ # สร้างช่วงวันที่สำหรับทำนายอนาคต
343
  future_days = 7
344
  future_timestamps = np.arange(
345
  plot_data["timestamp"].max() + 1,
346
  plot_data["timestamp"].max() + future_days + 1
347
  )
348
+
349
+ # ทำนายค่า sentiment ในอนาคต
350
  future_dates = [plot_data["date_day"].max() + timedelta(days=i) for i in range(1, future_days + 1)]
351
  future_preds = model_lr.predict(future_timestamps.reshape(-1, 1))
352
 
353
+
354
  # ---------------------------------------------------------
355
  # Plot
356
  # ---------------------------------------------------------
 
387
  )
388
 
389
  # ---------------------------------------------------------
390
+ # สร้างเส้นเชื่อม Actual -> Predicted Sentiment เพื่อความสวยงาม
391
  # ---------------------------------------------------------
392
  last_actual_date = plot_data["date_day"].max()
393
  last_actual_value = plot_data["avg_sentiment"].iloc[-1]
 
406
  )
407
 
408
 
409
+ # กราฟแท่งแสดงจำนวนข่าว
410
  for col in ["neutral", "negative", "positive"]:
411
  if col not in plot_data.columns:
412
  plot_data[col] = 0
 
429
 
430
  st.plotly_chart(fig, use_container_width=True)
431
 
432
+ # ตารางแสดงรายการข่าว
433
  st.subheader("📰 รายการข่าวทั้งหมด")
434
  st.dataframe(news_df[["date", "source", "text", "sentiment", "url"]], use_container_width=True)
435