Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import requests | |
| import pandas as pd | |
| from datetime import datetime, timedelta | |
| from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer | |
| from textblob import TextBlob | |
| import nltk | |
| from wordcloud import WordCloud | |
| import base64 | |
| from io import BytesIO | |
| import numpy as np | |
| from sklearn.linear_model import LinearRegression | |
| import plotly.graph_objects as go | |
| import yfinance as yf | |
| # -------------------------- | |
| # CONFIG | |
| # -------------------------- | |
| st.set_page_config(page_title="📰 SentimentSync NewsAI", layout="wide") | |
| API_KEY = "88bc396d4eab4be494a4b86ec842db47" | |
| # -------------------------- | |
| # UTILITIES | |
| # -------------------------- | |
| def analyze_text(text, vader): | |
| if not text.strip(): | |
| return 0 | |
| vader_score = vader.polarity_scores(text)["compound"] | |
| textblob_score = TextBlob(text).sentiment.polarity | |
| return np.mean([vader_score, textblob_score]) | |
| def generate_wordcloud(text): | |
| stopwords = nltk.corpus.stopwords.words('english') | |
| wordcloud = WordCloud(width=800, height=400, background_color="white", stopwords=stopwords).generate(text) | |
| buf = BytesIO() | |
| wordcloud.to_image().save(buf, format="PNG") | |
| return base64.b64encode(buf.getvalue()).decode() | |
| # -------------------------- | |
| # แปลงชื่อ/ตัวย่อ → (Company Name, Symbol) | |
| # -------------------------- | |
| def resolve_company_symbol(keyword: str): | |
| keyword = keyword.strip() | |
| ticker = None | |
| name = None | |
| try: | |
| data = yf.Ticker(keyword) | |
| info = data.info | |
| if "symbol" in info and info["symbol"]: | |
| ticker = info["symbol"] | |
| name = info.get("longName", info.get("shortName", keyword)) | |
| else: | |
| url = f"https://query2.finance.yahoo.com/v1/finance/search?q={keyword}" | |
| res = requests.get(url).json() | |
| if "quotes" in res and len(res["quotes"]) > 0: | |
| q = res["quotes"][0] | |
| ticker = q.get("symbol") | |
| name = q.get("longname", q.get("shortname", keyword)) | |
| except Exception as e: | |
| print("Lookup failed:", e) | |
| if not ticker: | |
| ticker = keyword.upper() | |
| if not name: | |
| name = keyword.capitalize() | |
| return name, ticker | |
| # -------------------------- | |
| # ดึงข่าว 7 วัน สำหรับ Company + Symbol | |
| # -------------------------- | |
| def fetch_financial_news(keyword): | |
| company, symbol = resolve_company_symbol(keyword) | |
| to_date = datetime.now().strftime('%Y-%m-%d') | |
| from_date = (datetime.now() - timedelta(days=7)).strftime('%Y-%m-%d') | |
| query_keyword = f"({company} OR {symbol}) finance stock" | |
| all_articles = [] | |
| page = 1 | |
| while True: | |
| url = ( | |
| f"https://newsapi.org/v2/everything?" | |
| f"q={query_keyword}&" | |
| f"from={from_date}&to={to_date}&" | |
| f"language=en&sortBy=publishedAt&" | |
| f"pageSize=100&page={page}&apiKey={API_KEY}" | |
| ) | |
| r = requests.get(url) | |
| data = r.json() | |
| if data.get("status") != "ok": | |
| st.error(f"API Error: {data}") | |
| break | |
| articles = data.get("articles", []) | |
| if not articles: | |
| break | |
| for a in articles: | |
| if a["description"]: | |
| all_articles.append({ | |
| "date": pd.to_datetime(a["publishedAt"]), | |
| "text": f"{a['title']} {a['description']}", | |
| "source": a["source"]["name"], | |
| "url": a["url"] | |
| }) | |
| if len(articles) < 100: | |
| break | |
| page += 1 | |
| return pd.DataFrame(all_articles) | |
| # -------------------------- | |
| # (*** ส่วนที่แก้ไข ***) | |
| # ดึงราคาหุ้นย้อนหลัง (แก้ไขให้ตรงกับช่วงข่าว) | |
| # -------------------------- | |
| def fetch_stock_price(symbol): | |
| try: | |
| # --- (เริ่มการแก้ไข) --- | |
| # ดึงข้อมูลย้อนหลัง 8 วัน (เพื่อให้ครอบคลุมช่วงข่าว 7 วัน) | |
| # เราใช้ start/end ที่ชัดเจน แทน period="14d" | |
| # เพื่อให้แน่ใจว่าช่วงเวลาตรงกับข่าวที่เพิ่งดึงมา | |
| end_date = datetime.now() | |
| start_date = end_date - timedelta(days=8) | |
| df = yf.download(symbol, | |
| start=start_date.strftime('%Y-%m-%d'), | |
| end=end_date.strftime('%Y-%m-%d'), | |
| interval="1d") | |
| # --- (สิ้นสุดการแก้ไข) --- | |
| if df.empty: | |
| st.warning("ไม่พบข้อมูลราคาหุ้นในช่วง 8 วันที่ผ่านมา") | |
| return pd.DataFrame() | |
| df = df.reset_index()[["Date", "Close"]] | |
| df.rename(columns={"Date": "date", "Close": "price"}, inplace=True) | |
| df["date"] = pd.to_datetime(df["date"]) | |
| return df | |
| except Exception as e: | |
| st.warning(f"ไม่สามารถดึงราคาหุ้นได้: {e}") | |
| return pd.DataFrame() | |
| # -------------------------- | |
| # MAIN APP (เหมือนเดิมทุกประการ) | |
| # -------------------------- | |
| def main(): | |
| st.title("📰 SentimentSync NewsAI") | |
| st.markdown("วิเคราะห์แนวโน้มอารมณ์ของข่าวย้อนหลัง 7 วัน พร้อมราคาหุ้น") | |
| # Sidebar | |
| with st.sidebar: | |
| keyword = st.text_input("ค้นหาคำ / ตัวย่อหุ้น (เช่น Tesla หรือ TSLA):", "") | |
| analyze_btn = st.button("วิเคราะห์เลย") | |
| if not analyze_btn: | |
| st.info("กรอกคำค้นแล้วกด 'วิเคราะห์เลย' เพื่อเริ่มต้น") | |
| return | |
| vader = SentimentIntensityAnalyzer() | |
| # ดึงข่าว | |
| st.info(f"กำลังดึงข่าวย้อนหลัง 7 วันสำหรับ '{keyword}' ...") | |
| news_df = fetch_financial_news(keyword) | |
| if news_df.empty: | |
| st.warning("ไม่พบบทความข่าวในช่วง 7 วันที่ผ่านมา") | |
| return | |
| # วิเคราะห์ sentiment | |
| st.info("กำลังวิเคราะห์อารมณ์ของข่าว...") | |
| news_df["sentiment"] = news_df["text"].apply(lambda x: analyze_text(x, vader)) | |
| news_df["date"] = pd.to_datetime(news_df["date"]) | |
| avg_sentiment = news_df["sentiment"].mean() | |
| pos_pct = (news_df["sentiment"] > 0.1).mean() * 100 | |
| neg_pct = (news_df["sentiment"] < -0.1).mean() * 100 | |
| col1, col2, col3 = st.columns(3) | |
| col1.metric("ค่าเฉลี่ยอารมณ์ข่าว", f"{avg_sentiment:.2f}", | |
| "Positive" if avg_sentiment > 0 else "Negative" if avg_sentiment < 0 else "Neutral") | |
| col2.metric("ข่าวเชิงบวก", f"{pos_pct:.1f}%") | |
| col3.metric("ข่าวเชิงลบ", f"{neg_pct:.1f}%") | |
| # Wordcloud | |
| st.subheader("☁️ Word Cloud ของข่าว") | |
| all_text = " ".join(news_df["text"].tolist()) | |
| img = generate_wordcloud(all_text) | |
| st.image(f"data:image/png;base64,{img}", use_column_width=True) | |
| # แนวโน้ม + พยากรณ์ + ราคาหุ้น | |
| st.subheader("📈 แนวโน้มอารมณ์ของข่าว & ราคาหุ้น") | |
| df_sorted = news_df.sort_values("date").copy() | |
| df_sorted["timestamp"] = (df_sorted["date"] - df_sorted["date"].min()).dt.days | |
| # Train sentiment model | |
| model = LinearRegression() | |
| model.fit(df_sorted[["timestamp"]], df_sorted["sentiment"]) | |
| # Forecast next 7 days | |
| future_days = 7 | |
| future_timestamps = np.arange(df_sorted["timestamp"].max() + 1, df_sorted["timestamp"].max() + future_days + 1) | |
| future_dates = [df_sorted["date"].max() + timedelta(days=i) for i in range(1, future_days + 1)] | |
| future_preds = model.predict(future_timestamps.reshape(-1, 1)) | |
| # ดึงราคาหุ้น (นี่จะเรียกใช้ฟังก์ชันที่แก้ไขแล้ว) | |
| _, symbol = resolve_company_symbol(keyword) | |
| stock_df = fetch_stock_price(symbol) | |
| # Plot | |
| fig = go.Figure() | |
| # Actual sentiment | |
| fig.add_trace(go.Scatter( | |
| x=df_sorted["date"], y=df_sorted["sentiment"], | |
| mode="lines+markers", name="Actual Sentiment", | |
| line=dict(color="blue") | |
| )) | |
| # Predicted sentiment | |
| fig.add_trace(go.Scatter( | |
| x=future_dates, y=future_preds, | |
| mode="lines+markers", name="Predicted Sentiment (7-day Forecast)", | |
| line=dict(color="orange", dash="dash") | |
| )) | |
| # Stock price (ตอนนี้จะแสดงผลตรงช่วงเวลาแล้ว) | |
| if not stock_df.empty: | |
| fig.add_trac(go.Scatter( | |
| x=stock_df["date"], y=stock_df["price"], | |
| mode="lines+markers", name=f"{symbol} Stock Price", | |
| line=dict(color="green"), yaxis="y2" | |
| )) | |
| fig.update_layout( | |
| title=f"แนวโน้มและพยากรณ์อารมณ์ข่าว & ราคาหุ้น '{keyword}'", | |
| xaxis_title="วันที่", | |
| yaxis=dict(title="Sentiment", side="left", range=[-1, 1]), | |
| yaxis2=dict(title="Stock Price", overlaying="y", side="right", showgrid=False), | |
| hovermode="x unified", | |
| template="plotly_white" | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # แสดงข่าว | |
| st.subheader("📰 รายการข่าว") | |
| st.dataframe(news_df[["date", "source", "text", "sentiment", "url"]], use_container_width=True) | |
| if __name__ == "__main__": | |
| nltk.download("stopwords", quiet=True) | |
| main() |