Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -11,13 +11,14 @@ from io import BytesIO
|
|
| 11 |
import numpy as np
|
| 12 |
from sklearn.linear_model import LinearRegression
|
| 13 |
import plotly.graph_objects as go
|
|
|
|
| 14 |
import yfinance as yf
|
| 15 |
|
| 16 |
# --------------------------
|
| 17 |
# CONFIG
|
| 18 |
# --------------------------
|
| 19 |
st.set_page_config(page_title="📰 SentimentSync NewsAI", layout="wide")
|
| 20 |
-
API_KEY = "88bc396d4eab4be494a4b86ec842db47"
|
| 21 |
|
| 22 |
# --------------------------
|
| 23 |
# UTILITIES
|
|
@@ -118,29 +119,28 @@ def fetch_financial_news(keyword):
|
|
| 118 |
|
| 119 |
|
| 120 |
# --------------------------
|
| 121 |
-
# (***
|
| 122 |
-
#
|
| 123 |
# --------------------------
|
| 124 |
@st.cache_data(ttl=3600)
|
| 125 |
-
def fetch_stock_price(symbol):
|
|
|
|
|
|
|
|
|
|
| 126 |
try:
|
| 127 |
-
|
| 128 |
-
end_date =
|
| 129 |
-
start_date = end_date - timedelta(days=8) # ดึง 8 วัน เผื่อวันหยุด
|
| 130 |
|
| 131 |
-
df = yf.download(symbol,
|
| 132 |
-
start=start_date.strftime('%Y-%m-%d'),
|
| 133 |
-
end=end_date.strftime('%Y-%m-%d'),
|
| 134 |
-
interval="1d")
|
| 135 |
|
| 136 |
if df.empty:
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
df = df.reset_index()[["Date", "Close"]]
|
| 141 |
df.rename(columns={"Date": "date", "Close": "price"}, inplace=True)
|
| 142 |
-
#
|
| 143 |
-
df["date"] = pd.to_datetime(df["date"].dt.date)
|
| 144 |
return df
|
| 145 |
except Exception as e:
|
| 146 |
st.warning(f"ไม่สามารถดึงราคาหุ้นได้: {e}")
|
|
@@ -148,7 +148,7 @@ def fetch_stock_price(symbol):
|
|
| 148 |
|
| 149 |
|
| 150 |
# --------------------------
|
| 151 |
-
# MAIN APP (***
|
| 152 |
# --------------------------
|
| 153 |
def main():
|
| 154 |
st.title("📰 SentimentSync NewsAI")
|
|
@@ -195,73 +195,113 @@ def main():
|
|
| 195 |
st.image(f"data:image/png;base64,{img}", use_column_width=True)
|
| 196 |
|
| 197 |
# -----------------------------------------------------------------
|
| 198 |
-
# (ส่วนที่แก้ไข)
|
| 199 |
# -----------------------------------------------------------------
|
| 200 |
st.subheader("📈 แนวโน้มอารมณ์ของข่าว & ราคาหุ้น")
|
| 201 |
|
| 202 |
-
# 1. (
|
| 203 |
-
# **สำคัญมาก** เราต้อง normalize date ให้เป็น date (00:00)
|
| 204 |
news_df["date_day"] = pd.to_datetime(news_df["date"].dt.date)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
|
| 206 |
-
|
|
|
|
|
|
|
| 207 |
avg_sentiment=('sentiment', 'mean')
|
| 208 |
).reset_index()
|
| 209 |
|
| 210 |
-
|
| 211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
df_sorted["timestamp"] = (df_sorted["date_day"] - df_sorted["date_day"].min()).dt.days
|
| 213 |
|
| 214 |
-
# Train sentiment model
|
| 215 |
model = LinearRegression()
|
| 216 |
model.fit(df_sorted[["timestamp"]], df_sorted["avg_sentiment"])
|
| 217 |
|
| 218 |
-
# Forecast next 7 days
|
| 219 |
future_days = 7
|
| 220 |
future_timestamps = np.arange(df_sorted["timestamp"].max() + 1, df_sorted["timestamp"].max() + future_days + 1)
|
| 221 |
future_dates = [df_sorted["date_day"].max() + timedelta(days=i) for i in range(1, future_days + 1)]
|
| 222 |
future_preds = model.predict(future_timestamps.reshape(-1, 1))
|
| 223 |
|
| 224 |
-
# 3. ดึงราคาหุ้น (
|
| 225 |
_, symbol = resolve_company_symbol(keyword)
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
# 4. Plot (ตอนนี้ X-axis ทั้ง 3 เส้นจะตรงกันแล้ว)
|
| 229 |
-
fig = go.Figure()
|
| 230 |
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
|
| 238 |
-
# Predicted sentiment (
|
| 239 |
fig.add_trace(go.Scatter(
|
| 240 |
x=future_dates, y=future_preds,
|
| 241 |
mode="lines+markers", name="Predicted Sentiment (7-day Forecast)",
|
| 242 |
-
line=dict(color="orange", dash="dash")
|
|
|
|
| 243 |
))
|
| 244 |
-
|
| 245 |
-
# Stock price (จาก yfinance ที่เป็นรายวัน)
|
| 246 |
-
if not stock_df.empty:
|
| 247 |
-
fig.add_trace(go.Scatter(
|
| 248 |
-
x=stock_df["date"], y=stock_df["price"],
|
| 249 |
-
mode="lines+markers", name=f"{symbol} Stock Price",
|
| 250 |
-
line=dict(color="green"), yaxis="y2"
|
| 251 |
-
))
|
| 252 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
fig.update_layout(
|
| 254 |
-
title=f"
|
| 255 |
-
|
| 256 |
-
yaxis=dict(title="Sentiment", side="left", range=[-1, 1]),
|
| 257 |
-
yaxis2=dict(title="Stock Price", overlaying="y", side="right", showgrid=False),
|
| 258 |
hovermode="x unified",
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
| 260 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
st.plotly_chart(fig, use_container_width=True)
|
| 263 |
|
| 264 |
-
# แสดงข่าว (
|
| 265 |
st.subheader("📰 รายการข่าว")
|
| 266 |
st.dataframe(news_df[["date", "source", "text", "sentiment", "url"]], use_container_width=True)
|
| 267 |
|
|
|
|
| 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 # Import สิ่งนี้
|
| 15 |
import yfinance as yf
|
| 16 |
|
| 17 |
# --------------------------
|
| 18 |
# CONFIG
|
| 19 |
# --------------------------
|
| 20 |
st.set_page_config(page_title="📰 SentimentSync NewsAI", layout="wide")
|
| 21 |
+
API_KEY = "88bc396d4eab4be494a4b86ec842db47"
|
| 22 |
|
| 23 |
# --------------------------
|
| 24 |
# UTILITIES
|
|
|
|
| 119 |
|
| 120 |
|
| 121 |
# --------------------------
|
| 122 |
+
# (*** ส่วนที่แก้ไข ***)
|
| 123 |
+
# ดึงราคาหุ้นตามช่วงเวลาที่กำหนด
|
| 124 |
# --------------------------
|
| 125 |
@st.cache_data(ttl=3600)
|
| 126 |
+
def fetch_stock_price(symbol, start_date, end_date):
|
| 127 |
+
"""
|
| 128 |
+
แก้ไข: รับ start_date และ end_date เพื่อดึงข้อมูลให้ตรงกัน
|
| 129 |
+
"""
|
| 130 |
try:
|
| 131 |
+
start_str = (start_date - timedelta(days=2)).strftime('%Y-%m-%d')
|
| 132 |
+
end_str = (end_date + timedelta(days=1)).strftime('%Y-%m-%d')
|
|
|
|
| 133 |
|
| 134 |
+
df = yf.download(symbol, start=start_str, end=end_str, interval="1d")
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
if df.empty:
|
| 137 |
+
st.warning("ไม่พบข้อมูลราคาหุ้นในช่วงเวลานี้")
|
| 138 |
+
return pd.DataFrame()
|
| 139 |
+
|
| 140 |
df = df.reset_index()[["Date", "Close"]]
|
| 141 |
df.rename(columns={"Date": "date", "Close": "price"}, inplace=True)
|
| 142 |
+
# Normalize วันที่ให้เป็น .dt.date
|
| 143 |
+
df["date"] = pd.to_datetime(df["date"].dt.date)
|
| 144 |
return df
|
| 145 |
except Exception as e:
|
| 146 |
st.warning(f"ไม่สามารถดึงราคาหุ้นได้: {e}")
|
|
|
|
| 148 |
|
| 149 |
|
| 150 |
# --------------------------
|
| 151 |
+
# MAIN APP (*** ส่วนที่แก้ไขทั้งหมด ***)
|
| 152 |
# --------------------------
|
| 153 |
def main():
|
| 154 |
st.title("📰 SentimentSync NewsAI")
|
|
|
|
| 195 |
st.image(f"data:image/png;base64,{img}", use_column_width=True)
|
| 196 |
|
| 197 |
# -----------------------------------------------------------------
|
| 198 |
+
# (ส่วนที่แก้ไข) กราฟไฮบริด (Ref1 + Prediction)
|
| 199 |
# -----------------------------------------------------------------
|
| 200 |
st.subheader("📈 แนวโน้มอารมณ์ของข่าว & ราคาหุ้น")
|
| 201 |
|
| 202 |
+
# 1. รวบรวมข้อมูลข่าวเป็นรายวัน (Daily Aggregation)
|
|
|
|
| 203 |
news_df["date_day"] = pd.to_datetime(news_df["date"].dt.date)
|
| 204 |
+
|
| 205 |
+
def sentiment_type(score):
|
| 206 |
+
if score > 0.1: return "positive"
|
| 207 |
+
if score < -0.1: return "negative"
|
| 208 |
+
return "neutral"
|
| 209 |
|
| 210 |
+
news_df["sentiment_type"] = news_df["sentiment"].apply(sentiment_type)
|
| 211 |
+
|
| 212 |
+
daily_avg_sentiment = news_df.groupby("date_day").agg(
|
| 213 |
avg_sentiment=('sentiment', 'mean')
|
| 214 |
).reset_index()
|
| 215 |
|
| 216 |
+
daily_counts = news_df.groupby(["date_day", "sentiment_type"]).size().unstack(fill_value=0).reset_index()
|
| 217 |
+
|
| 218 |
+
daily_data = pd.merge(daily_avg_sentiment, daily_counts, on="date_day", how="left").fillna(0)
|
| 219 |
+
|
| 220 |
+
for col in ['positive', 'negative', 'neutral']:
|
| 221 |
+
if col not in daily_data.columns:
|
| 222 |
+
daily_data[col] = 0
|
| 223 |
+
|
| 224 |
+
# 2. (ใหม่) เทรนโมเดล Prediction โดยใช้ข้อมูล "รายวัน"
|
| 225 |
+
df_sorted = daily_data.sort_values("date_day").copy()
|
| 226 |
df_sorted["timestamp"] = (df_sorted["date_day"] - df_sorted["date_day"].min()).dt.days
|
| 227 |
|
|
|
|
| 228 |
model = LinearRegression()
|
| 229 |
model.fit(df_sorted[["timestamp"]], df_sorted["avg_sentiment"])
|
| 230 |
|
|
|
|
| 231 |
future_days = 7
|
| 232 |
future_timestamps = np.arange(df_sorted["timestamp"].max() + 1, df_sorted["timestamp"].max() + future_days + 1)
|
| 233 |
future_dates = [df_sorted["date_day"].max() + timedelta(days=i) for i in range(1, future_days + 1)]
|
| 234 |
future_preds = model.predict(future_timestamps.reshape(-1, 1))
|
| 235 |
|
| 236 |
+
# 3. ดึงราคาหุ้น (ในช่วงเวลาเดียวกับข่าว)
|
| 237 |
_, symbol = resolve_company_symbol(keyword)
|
| 238 |
+
min_date = df_sorted["date_day"].min()
|
| 239 |
+
max_date = df_sorted["date_day"].max()
|
|
|
|
|
|
|
| 240 |
|
| 241 |
+
st.info(f"กำลังดึงราคาหุ้น {symbol} ระหว่างวันที่ {min_date.strftime('%Y-%m-%d')} ถึง {max_date.strftime('%Y-%m-%d')}...")
|
| 242 |
+
stock_df = fetch_stock_price(symbol, min_date, max_date)
|
| 243 |
+
|
| 244 |
+
# 4. สร้างกราฟ (Plot) ด้วย Subplots
|
| 245 |
+
fig = make_subplots(rows=2, cols=1, specs=[[{"secondary_y": True}], [{}]],
|
| 246 |
+
row_heights=[0.7, 0.3], vertical_spacing=0.1,
|
| 247 |
+
shared_xaxes=True)
|
| 248 |
+
|
| 249 |
+
# --- กราฟส่วนบน (ราคา, Sentiment, Prediction) ---
|
| 250 |
+
|
| 251 |
+
# Add stock price (Y-axis 1, สีเขียว)
|
| 252 |
+
if not stock_df.empty:
|
| 253 |
+
fig.add_trace(
|
| 254 |
+
go.Scatter(
|
| 255 |
+
x=stock_df["date"], y=stock_df["price"],
|
| 256 |
+
name=f"{symbol} Stock Price",
|
| 257 |
+
line=dict(color="green", width=2) # ใช้สีเขียวตามโค้ดเดิม
|
| 258 |
+
),
|
| 259 |
+
row=1, col=1, secondary_y=False
|
| 260 |
+
)
|
| 261 |
+
|
| 262 |
+
# Add daily sentiment score (Y-axis 2, สีน้ำเงิน)
|
| 263 |
+
fig.add_trace(
|
| 264 |
+
go.Scatter(
|
| 265 |
+
x=df_sorted["date_day"], y=df_sorted["avg_sentiment"],
|
| 266 |
+
name="Actual Sentiment (Daily Avg)",
|
| 267 |
+
mode="lines+markers",
|
| 268 |
+
line=dict(color="blue", width=2) # ใช้สีน้ำเงินตามโค้ดเดิม
|
| 269 |
+
),
|
| 270 |
+
row=1, col=1, secondary_y=True
|
| 271 |
+
)
|
| 272 |
|
| 273 |
+
# (ใหม่) Add Predicted sentiment (Y-axis 2, สีส้ม)
|
| 274 |
fig.add_trace(go.Scatter(
|
| 275 |
x=future_dates, y=future_preds,
|
| 276 |
mode="lines+markers", name="Predicted Sentiment (7-day Forecast)",
|
| 277 |
+
line=dict(color="orange", dash="dash"),
|
| 278 |
+
secondary_y=True # <-- สำคัญ: ต้องอยู่แกน Y เดียวกับ Actual
|
| 279 |
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
|
| 281 |
+
# --- กราฟส่วนล่าง (จำนวนข่าว) ---
|
| 282 |
+
fig.add_trace(go.Bar(x=df_sorted["date_day"], y=df_sorted["neutral"], name="Neutral", marker_color='rgba(128, 128, 128, 0.7)'), row=2, col=1)
|
| 283 |
+
fig.add_trace(go.Bar(x=df_sorted["date_day"], y=df_sorted["negative"], name="Negative", marker_color='rgba(255, 0, 0, 0.7)'), row=2, col=1)
|
| 284 |
+
fig.add_trace(go.Bar(x=df_sorted["date_day"], y=df_sorted["positive"], name="Positive", marker_color='rgba(0, 128, 0, 0.7)'), row=2, col=1)
|
| 285 |
+
|
| 286 |
+
# 5. ตกแต่ง Layout
|
| 287 |
fig.update_layout(
|
| 288 |
+
title=f"แนวโน้มอารมณ์ข่าว & ราคาหุ้น '{keyword}'",
|
| 289 |
+
template="plotly_white",
|
|
|
|
|
|
|
| 290 |
hovermode="x unified",
|
| 291 |
+
barmode='stack',
|
| 292 |
+
legend=dict(orientation='h', yanchor='bottom', y=1.02, xanchor='right', x=1),
|
| 293 |
+
height=600,
|
| 294 |
+
margin=dict(l=20, r=20, t=80, b=20)
|
| 295 |
)
|
| 296 |
+
|
| 297 |
+
fig.update_yaxes(title_text="Stock Price", row=1, col=1, secondary_y=False)
|
| 298 |
+
fig.update_yaxes(title_text="Sentiment Score", range=[-1, 1], row=1, col=1, secondary_y=True)
|
| 299 |
+
fig.update_yaxes(title_text="Article Count", row=2, col=1)
|
| 300 |
+
fig.update_xaxes(title_text="วันที่", row=2, col=1)
|
| 301 |
|
| 302 |
st.plotly_chart(fig, use_container_width=True)
|
| 303 |
|
| 304 |
+
# แสดงข่าว (ยังอยู่เหมือนเดิม)
|
| 305 |
st.subheader("📰 รายการข่าว")
|
| 306 |
st.dataframe(news_df[["date", "source", "text", "sentiment", "url"]], use_container_width=True)
|
| 307 |
|