KYTHY commited on
Commit
3439737
·
verified ·
1 Parent(s): 6a35887

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +167 -106
app.py CHANGED
@@ -1,26 +1,40 @@
1
  import streamlit as st
2
- import requests
3
  import pandas as pd
4
- import numpy as np
5
  import yfinance as yf
6
- from datetime import datetime, timedelta
 
7
  from textblob import TextBlob
8
- from sklearn.linear_model import LinearRegression
9
  import plotly.graph_objects as go
 
 
 
 
 
 
10
 
11
  # -------------------------------
12
- # 🔧 ตั้งค่า API Key
13
  # -------------------------------
14
- NEWS_API_KEY = "88bc396d4eab4be494a4b86ec842db47"
 
15
 
16
- st.set_page_config(page_title="📊 วิเคราะห์อารมณ์ข่าวหุ้น", layout="wide")
 
 
 
 
 
 
 
17
 
18
- st.title("📈 วิเคราะห์แนวโน้มอารมณ์ข่าวหุ้นด้วย AI")
19
- st.caption("ใช้ NewsAPI + yfinance เพื่อค้นหาข่าวหุ้น และพยากรณ์อารมณ์ข่าว 7 วันข้างหน้า")
20
 
21
  # -------------------------------
22
- # 🧠 ฟังก์ชัน: แปลงชื่อบริษัท ↔️ Symbol โดยอัตโนมัติ
23
  # -------------------------------
 
24
  def resolve_company_symbol(keyword: str):
25
  keyword = keyword.strip()
26
  ticker = None
@@ -28,132 +42,179 @@ def resolve_company_symbol(keyword: str):
28
  try:
29
  data = yf.Ticker(keyword)
30
  info = data.info
31
- if "symbol" in info and info["symbol"]: # ถ้าเป็น ticker
32
  ticker = info["symbol"]
33
  name = info.get("longName", info.get("shortName", keyword))
34
  else:
35
- # ถ้าไม่ใช่ ticker → ค้นจากชื่อบริษัท
36
  url = f"https://query2.finance.yahoo.com/v1/finance/search?q={keyword}"
37
  res = requests.get(url).json()
38
  if "quotes" in res and len(res["quotes"]) > 0:
39
  q = res["quotes"][0]
40
- ticker = q.get("symbol", keyword.upper())
41
- name = q.get("longname", q.get("shortname", keyword.capitalize()))
42
- except Exception:
 
 
 
43
  ticker = keyword.upper()
 
44
  name = keyword.capitalize()
45
  return name, ticker
46
 
47
  # -------------------------------
48
- # 📰 ฟังก์ชัน: ดึงข่าวจาก NewsAPI
49
  # -------------------------------
50
- def fetch_financial_news(keyword: str):
51
- company, symbol = resolve_company_symbol(keyword)
52
- query = f"({company} OR {symbol})"
53
-
54
- to_date = datetime.utcnow().isoformat() # ดึงถึงเวลาปัจจุบัน (UTC)
55
- from_date = (datetime.utcnow() - timedelta(days=7)).strftime('%Y-%m-%d')
56
-
57
- all_articles = []
58
- for page in range(1, 6): # ดึงได้สูงสุด 500 ข่าว (100 x 5 หน้า)
59
- url = (
60
- f"https://newsapi.org/v2/everything?"
61
- f"q={query}+finance+stock&"
62
- f"from={from_date}&to={to_date}&"
63
- f"language=en&sortBy=publishedAt&"
64
- f"pageSize=100&page={page}&apiKey={NEWS_API_KEY}"
65
- )
66
- response = requests.get(url).json()
67
- if response.get("status") != "ok" or not response.get("articles"):
68
- break
69
- all_articles.extend(response["articles"])
70
-
71
- if not all_articles:
72
  return pd.DataFrame()
73
 
74
- df = pd.DataFrame(all_articles)
75
- df["publishedAt"] = pd.to_datetime(df["publishedAt"])
76
- df["date"] = df["publishedAt"].dt.date
77
- df["title"] = df["title"].fillna("")
78
- df["description"] = df["description"].fillna("")
79
- df["content"] = df["content"].fillna("")
80
- df["url"] = df["url"].fillna("")
81
- df["source"] = df["source"].apply(lambda x: x.get("name") if isinstance(x, dict) else x)
 
 
82
  df["company"] = company
83
  df["symbol"] = symbol
84
  return df
85
 
86
  # -------------------------------
87
- # 😊 ฟังก์ชัน: วิเคราะห์อารมณ์ข่าว
88
  # -------------------------------
89
- def analyze_sentiment(text):
90
- if not text:
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  return 0
92
- analysis = TextBlob(text)
93
- return analysis.sentiment.polarity # ค่า -1 (ลบ) → +1 (บวก)
94
 
95
  # -------------------------------
96
- # 🧮 ฟังก์ชัน: พยากรณ์แนวโน้มด้วย Linear Regression
97
  # -------------------------------
98
  def forecast_sentiment_trend(df):
99
- df_sorted = df.groupby("date")["sentiment"].mean().reset_index()
100
- df_sorted["timestamp"] = (df_sorted["date"] - df_sorted["date"].min()).dt.days
101
-
102
- model = LinearRegression()
103
- model.fit(df_sorted[["timestamp"]], df_sorted["sentiment"])
104
-
105
- future_days = 7
106
- future_timestamps = np.arange(df_sorted["timestamp"].max() + 1,
107
- df_sorted["timestamp"].max() + future_days + 1)
108
- future_dates = [df_sorted["date"].max() + timedelta(days=i) for i in range(1, future_days + 1)]
109
- future_preds = model.predict(future_timestamps.reshape(-1, 1))
110
-
 
 
 
 
111
  forecast_df = pd.DataFrame({"date": future_dates, "predicted_sentiment": future_preds})
112
- return df_sorted, forecast_df
 
113
 
114
  # -------------------------------
115
- # 🎯 ส่วนหลักของแอป
116
  # -------------------------------
117
- keyword = st.text_input("🔍 พิมพ์ชื่อบริษัทหรืออักษรย่อหุ้น (เช่น Apple หรือ AAPL):", "AAPL")
118
 
119
- if st.button("ค้นหาข่าว"):
120
- with st.spinner("กำลังดึงข่าวและวิเคราะห์อารมณ์..."):
121
- news_df = fetch_financial_news(keyword)
 
122
 
 
123
  if news_df.empty:
124
- st.warning("ไม่พบข่าวในช่วง 7 วันที่ผ่านมา 😢")
125
- else:
126
- st.success(f"✅ พบข่าวทั้งหมด {len(news_df)} รายการ")
127
-
128
- # วิเคราะห์อารมณ์
129
- news_df["sentiment"] = news_df["title"].apply(analyze_sentiment)
130
-
131
- # แสดงตารางข่าว
132
- st.subheader("📰 ข่าวล่าสุด")
133
- st.dataframe(news_df[["date", "title", "source", "sentiment", "url"]])
134
-
135
- # สร้างกราฟแนวโน้ม + พยากรณ์
136
- st.subheader("📊 แนวโน้มและพยากรณ์อารมณ์ข่าว 7 วันข้างหน้า")
137
- df_actual, df_forecast = forecast_sentiment_trend(news_df)
138
-
139
- fig = go.Figure()
140
- fig.add_trace(go.Scatter(
141
- x=df_actual["date"], y=df_actual["sentiment"],
142
- mode="lines+markers",
143
- name="Actual Sentiment",
144
- line=dict(color="blue")
145
- ))
146
- fig.add_trace(go.Scatter(
147
- x=df_forecast["date"], y=df_forecast["predicted_sentiment"],
148
- mode="lines+markers",
149
- name="Predicted Trend (Next 7 Days)",
150
- line=dict(color="orange", dash="dash")
151
- ))
152
- fig.update_layout(
153
- title=f"📈 แนวโน้มอารมณ์ข่าวของ {df_actual['date'].min()} ถึง {df_forecast['date'].max()}",
154
- xaxis_title="วันที่",
155
- yaxis_title="ค่าอารมณ์ (Sentiment)",
156
- hovermode="x unified",
157
- template="plotly_white"
158
- )
159
- st.plotly_chart(fig, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
 
2
  import pandas as pd
3
+ import requests
4
  import yfinance as yf
5
+ from transformers import pipeline
6
+ from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
7
  from textblob import TextBlob
8
+ from datetime import datetime, timedelta
9
  import plotly.graph_objects as go
10
+ import nltk
11
+ import numpy as np
12
+ from sklearn.linear_model import Ridge
13
+ from sklearn.preprocessing import PolynomialFeatures
14
+ from sklearn.pipeline import make_pipeline
15
+ import os
16
 
17
  # -------------------------------
18
+ # 🔧 CONFIG
19
  # -------------------------------
20
+ st.set_page_config(page_title="📈 News Sentiment & Stock Tracker", layout="wide")
21
+ API_KEY = "88bc396d4eab4be494a4b86ec842db47"
22
 
23
+ # -------------------------------
24
+ # 📦 โหลดโมเดล
25
+ # -------------------------------
26
+ @st.cache_resource
27
+ def load_models():
28
+ bert = pipeline("sentiment-analysis", model="nlptown/bert-base-multilingual-uncased-sentiment")
29
+ vader = SentimentIntensityAnalyzer()
30
+ return bert, vader
31
 
32
+ bert_model, vader_analyzer = load_models()
 
33
 
34
  # -------------------------------
35
+ # 🧠 ฟังก์ชันแปลงชื่อบริษัท <-> ตัวย่อหุ้น
36
  # -------------------------------
37
+ @st.cache_data(ttl=86400)
38
  def resolve_company_symbol(keyword: str):
39
  keyword = keyword.strip()
40
  ticker = None
 
42
  try:
43
  data = yf.Ticker(keyword)
44
  info = data.info
45
+ if "symbol" in info and info["symbol"]:
46
  ticker = info["symbol"]
47
  name = info.get("longName", info.get("shortName", keyword))
48
  else:
 
49
  url = f"https://query2.finance.yahoo.com/v1/finance/search?q={keyword}"
50
  res = requests.get(url).json()
51
  if "quotes" in res and len(res["quotes"]) > 0:
52
  q = res["quotes"][0]
53
+ ticker = q.get("symbol")
54
+ name = q.get("longname", q.get("shortname", keyword))
55
+ except Exception as e:
56
+ st.warning(f"⚠️ ไม่สามารถค้นหาข้อมูลบริษัทได้: {e}")
57
+
58
+ if not ticker:
59
  ticker = keyword.upper()
60
+ if not name:
61
  name = keyword.capitalize()
62
  return name, ticker
63
 
64
  # -------------------------------
65
+ # 📰 ดึงข่าวย้อนหลัง 7 วัน
66
  # -------------------------------
67
+ @st.cache_data(ttl=3600)
68
+ def fetch_news(company, symbol):
69
+ to_date = datetime.utcnow()
70
+ from_date = to_date - timedelta(days=7)
71
+ query = f"({company} OR {symbol}) finance stock"
72
+
73
+ url = (
74
+ f"https://newsapi.org/v2/everything?"
75
+ f"q={query}&from={from_date.date()}&to={to_date.isoformat()}&"
76
+ f"language=en&sortBy=publishedAt&pageSize=100&apiKey={API_KEY}"
77
+ )
78
+
79
+ res = requests.get(url)
80
+ data = res.json()
81
+ if data.get("status") != "ok":
82
+ st.error("❌ ดึงข้อมูลข่าวไม่สำเร็จ")
 
 
 
 
 
 
83
  return pd.DataFrame()
84
 
85
+ articles = data.get("articles", [])
86
+ df = pd.DataFrame([{
87
+ "date": datetime.fromisoformat(a["publishedAt"].replace("Z", "+00:00")),
88
+ "title": a["title"],
89
+ "description": a["description"],
90
+ "source": a["source"]["name"],
91
+ "url": a["url"],
92
+ } for a in articles])
93
+
94
+ df["text"] = df["title"].fillna('') + " " + df["description"].fillna('')
95
  df["company"] = company
96
  df["symbol"] = symbol
97
  return df
98
 
99
  # -------------------------------
100
+ # 💬 วิเคราะห์อารมณ์ข่าว
101
  # -------------------------------
102
+ def analyze_sentiment(text, models):
103
+ bert, vader = models
104
+ if not text.strip():
105
+ return 0
106
+ try:
107
+ vader_score = vader.polarity_scores(text)["compound"]
108
+ tb_score = TextBlob(text).sentiment.polarity
109
+ bert_res = bert(text[:512])[0]
110
+ label_map = {
111
+ "1 star": -1, "2 stars": -0.5, "3 stars": 0,
112
+ "4 stars": 0.5, "5 stars": 1
113
+ }
114
+ bert_score = label_map.get(bert_res["label"], 0)
115
+ return np.mean([vader_score, tb_score, bert_score])
116
+ except Exception:
117
  return 0
 
 
118
 
119
  # -------------------------------
120
+ # 📈 สร้างโมเดลพยากรณ์
121
  # -------------------------------
122
  def forecast_sentiment_trend(df):
123
+ # ensure datetime format
124
+ df["date"] = pd.to_datetime(df["date"], errors="coerce")
125
+ df = df.dropna(subset=["date"])
126
+ df_daily = df.groupby(df["date"].dt.date)["sentiment"].mean().reset_index()
127
+ df_daily["date"] = pd.to_datetime(df_daily["date"])
128
+ df_daily["days"] = (df_daily["date"] - df_daily["date"].min()).dt.days
129
+
130
+ X = df_daily["days"].values.reshape(-1, 1)
131
+ y = df_daily["sentiment"].values
132
+ model = make_pipeline(PolynomialFeatures(2), Ridge(alpha=1.0))
133
+ model.fit(X, y)
134
+
135
+ last_day = df_daily["days"].max()
136
+ future_days = np.arange(last_day + 1, last_day + 8).reshape(-1, 1)
137
+ future_preds = model.predict(future_days)
138
+ future_dates = [df_daily["date"].max() + timedelta(days=i) for i in range(1, 8)]
139
  forecast_df = pd.DataFrame({"date": future_dates, "predicted_sentiment": future_preds})
140
+
141
+ return df_daily, forecast_df
142
 
143
  # -------------------------------
144
+ # 📊 ส่วนแสดงผลหลัก
145
  # -------------------------------
146
+ st.title("📈 News Sentiment & Stock Tracker")
147
 
148
+ keyword = st.text_input("🔍 ค้นหาบริษัทหรือตัวย่อหุ้น (เช่น Apple หรือ AAPL):", "AAPL")
149
+ if st.button("Analyze"):
150
+ company, symbol = resolve_company_symbol(keyword)
151
+ st.info(f"📊 กำลังวิเคราะห์ข่าวของ **{company} ({symbol})**...")
152
 
153
+ news_df = fetch_news(company, symbol)
154
  if news_df.empty:
155
+ st.warning("ไม่พบข่าวในช่วง 7 วันที่ผ่านมา")
156
+ st.stop()
157
+
158
+ news_df["sentiment"] = news_df["text"].apply(lambda x: analyze_sentiment(x, (bert_model, vader_analyzer)))
159
+
160
+ avg_sent = news_df["sentiment"].mean()
161
+ st.metric("📈 ค่าเฉลี่ยอารมณ์ข่าว (7 วัน)", f"{avg_sent:.2f}",
162
+ "Positive" if avg_sent > 0 else "Negative" if avg_sent < 0 else "Neutral")
163
+
164
+ # -------------------------------
165
+ # 📈 แนวโน้มอารมณ์ + ราคาหุ้น
166
+ # -------------------------------
167
+ st.subheader("📊 แนวโน้มอารมณ์ข่าว & ราคาหุ้น")
168
+
169
+ df_actual, df_forecast = forecast_sentiment_trend(news_df)
170
+
171
+ # ดึงราคาหุ้นจาก yfinance
172
+ price_df = yf.download(symbol, period="14d", interval="1d")
173
+ price_df = price_df.reset_index()[["Date", "Close"]]
174
+ price_df.rename(columns={"Date": "date"}, inplace=True)
175
+ price_df["date"] = pd.to_datetime(price_df["date"]).dt.date
176
+ df_actual["date"] = pd.to_datetime(df_actual["date"]).dt.date
177
+ df_forecast["date"] = pd.to_datetime(df_forecast["date"]).dt.date
178
+
179
+ # สร้างกราฟรวม
180
+ fig = go.Figure()
181
+ fig.add_trace(go.Scatter(
182
+ x=df_actual["date"], y=df_actual["sentiment"],
183
+ mode="lines+markers", name="Actual Sentiment", line=dict(color="blue")
184
+ ))
185
+ fig.add_trace(go.Scatter(
186
+ x=df_forecast["date"], y=df_forecast["predicted_sentiment"],
187
+ mode="lines+markers", name="Predicted Sentiment (Next 7 Days)",
188
+ line=dict(color="orange", dash="dash")
189
+ ))
190
+ fig.add_trace(go.Scatter(
191
+ x=price_df["date"], y=price_df["Close"],
192
+ mode="lines+markers", name=f"{symbol} Stock Price",
193
+ line=dict(color="green"), yaxis="y2"
194
+ ))
195
+
196
+ fig.update_layout(
197
+ title=f"📈 แนวโน้มอารมณ์ข่าว & ราคาหุ้น ({symbol})",
198
+ xaxis=dict(title="วันที่"),
199
+ yaxis=dict(title="Sentiment", side="left", range=[-1, 1]),
200
+ yaxis2=dict(title="Stock Price (USD)", overlaying="y", side="right", showgrid=False),
201
+ legend=dict(x=0, y=1.1, orientation="h"),
202
+ hovermode="x unified",
203
+ template="plotly_white"
204
+ )
205
+ st.plotly_chart(fig, use_container_width=True)
206
+
207
+ # -------------------------------
208
+ # 📰 แสดงข่าวที่ใช้วิเคราะห์
209
+ # -------------------------------
210
+ st.subheader("📰 ข่าวที่ใช้วิเคราะห์")
211
+ st.dataframe(news_df[["date", "source", "title", "sentiment"]])
212
+
213
+ # -------------------------------
214
+ # 📚 โหลด NLTK
215
+ # -------------------------------
216
+ try:
217
+ nltk.download("punkt", quiet=True)
218
+ nltk.download("stopwords", quiet=True)
219
+ except:
220
+ pass