import yfinance as yf import pandas as pd import streamlit as st import requests import plotly.express as px import plotly.graph_objects as go from datetime import datetime, timedelta from bs4 import BeautifulSoup import time from playwright.sync_api import sync_playwright # ============================== # 📌 LẤY DỮ LIỆU TỪ YFINANCE # ============================== def get_stock_data(ticker, period="11d"): try: stock = yf.Ticker(ticker) hist = stock.history(period=period) if len(hist) < 11: return None, None, None, None avg_volume = hist["Volume"][:-1].mean() today_volume = hist["Volume"][-1] price = hist["Close"][-1] change = ((hist["Close"][-1] - hist["Close"][-2]) / hist["Close"][-2]) * 100 vol_ratio = today_volume / avg_volume if avg_volume else None return price, change, vol_ratio, hist except: return None, None, None, None def get_pcr(ticker, use_oi=False): try: opt = yf.Ticker(ticker).option_chain() calls = opt.calls puts = opt.puts if use_oi: call_oi = calls["openInterest"].sum() put_oi = puts["openInterest"].sum() if call_oi > 0: return round(put_oi / call_oi, 2) else: call_volume = calls["volume"].sum() put_volume = puts["volume"].sum() if call_volume > 0: return round(put_volume / call_volume, 2) except: return None def get_pcr_trend(ticker, days=5): try: pcr_values = [] for i in range(days): date = (datetime.now() - timedelta(days=i)).strftime('%Y-%m-%d') opt = yf.Ticker(ticker).option_chain(date=date) calls = opt.calls puts = opt.puts call_volume = calls["volume"].sum() put_volume = puts["volume"].sum() if call_volume > 0: pcr_values.append(put_volume / call_volume) return round(sum(pcr_values) / len(pcr_values), 2) if pcr_values else None except: return None # ============================== # 📌 LẤY DỮ LIỆU VIX # ============================== def get_vix_index(): try: vix = yf.Ticker("^VIX") df = vix.history(period="1d") if df.empty: return None return df["Close"].iloc[-1] except Exception as e: st.error(f"Lỗi khi lấy dữ liệu VIX Index: {e}") return None def get_vix_history(period="11d"): try: vix = yf.Ticker("^VIX") hist = vix.history(period=period) if hist.empty: return None return hist except Exception as e: st.error(f"Lỗi khi lấy lịch sử VIX: {e}") return None # ============================== # 📌 LẤY LỢI SUẤT TRÁI PHIẾU 10 NĂM TỪ FRED # ============================== def get_bond_yield(): FRED_API_KEY = "f312589066a1d21d3c09fc5cec6d9421" url = f"https://api.stlouisfed.org/fred/series/observations?series_id=GS10&api_key={FRED_API_KEY}&file_type=json" response = requests.get(url).json() if "observations" in response: return float(response["observations"][-1]["value"]) return None # ============================== # 📌 LẤY DỮ LIỆU VÀNG (GOLD) # ============================== def get_gold_price_from_goldapi(api_key): url = "https://www.goldapi.io/api/XAU/USD" headers = { "x-access-token": api_key, "Content-Type": "application/json" } try: response = requests.get(url, headers=headers, timeout=10) response.raise_for_status() data = response.json() price = data.get("price") prev_close = data.get("prev_close_price") if price is not None and prev_close is not None: change_percent = ((price - prev_close) / prev_close) * 100 return price, change_percent else: return None, None except Exception as e: print("Lỗi khi lấy dữ liệu từ GoldAPI:", e) return None, None # ============================== # 📌 WEB SCRAPING DỮ LIỆU TỪ FEARGREEDMETER.COM (NGUỒN CŨ) # ============================== def get_fear_and_greed_index_from_feargreedmeter(): try: with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() url = "https://feargreedmeter.com/fear-and-greed-index" page.goto(url) page.wait_for_load_state("networkidle") # Đợi trang tải xong # Tìm giá trị chỉ số và trạng thái (cần kiểm tra selector thực tế) index_element = page.query_selector("div.fng-value") # Thay bằng selector thực tế status_element = page.query_selector("div.fng-status") # Thay bằng selector thực tế if index_element: index_value = int(index_element.inner_text().strip().replace(",", "")) status = status_element.inner_text().strip() if status_element else None if not status: # Suy ra status dựa trên giá trị nếu không tìm thấy if index_value < 25: status = "Extreme Fear" elif index_value < 45: status = "Fear" elif index_value < 55: status = "Neutral" elif index_value < 75: status = "Greed" else: status = "Extreme Greed" browser.close() return index_value, status browser.close() return None, None except Exception as e: st.error(f"Lỗi khi lấy Fear & Greed Index từ feargreedmeter.com: {e}") return None, None # ============================== # 📌 WEB SCRAPING DỮ LIỆU TỪ MACROMICRO (NGUỒN MỚI) # ============================== def get_fear_and_greed_index_from_macromicro(): try: with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() url = "https://en.macromicro.me/series/22748/cnn-fear-and-greed" page.goto(url) page.wait_for_load_state("networkidle") # Đợi trang tải xong # Tìm giá trị Fear & Greed Index # Dựa trên cấu trúc HTML của trang, ta cần tìm selector phù hợp # Sau khi kiểm tra, giá trị nằm trong thẻ có class "mm-data-value" index_element = page.query_selector("div.mm-data-value") if index_element: index_value = float(index_element.inner_text().strip()) else: browser.close() return None, None # Xác định trạng thái dựa trên giá trị index if index_value < 25: status = "Extreme Fear" elif index_value < 45: status = "Fear" elif index_value < 55: status = "Neutral" elif index_value < 75: status = "Greed" else: status = "Extreme Greed" browser.close() return index_value, status except Exception as e: st.error(f"Lỗi khi lấy Fear & Greed Index từ MacroMicro: {e}") return None, None def get_futures_data_from_businessinsider(): try: url = "https://markets.businessinsider.com/premarket" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" } response = requests.get(url, headers=headers) time.sleep(2) # Thêm delay để tránh bị chặn soup = BeautifulSoup(response.text, 'html.parser') futures_data = {} futures_table = soup.find("table") # Giả định bảng đầu tiên, cần kiểm tra class thực tế if futures_table: rows = futures_table.find_all("tr")[1:] # Bỏ qua hàng tiêu đề for row in rows: cols = row.find_all("td") if len(cols) >= 4: # Đảm bảo có đủ cột: Name, Price, +/-, % name = cols[0].text.strip() price = float(cols[1].text.replace(",", "").replace("$", "")) change = float(cols[2].text.replace("+", "").replace("-", "")) percent_change = float(cols[3].text.replace("%", "")) futures_data[name] = { "price": price, "change": change, "percent_change": percent_change } return futures_data except Exception as e: st.error(f"Lỗi khi lấy dữ liệu Futures từ Business Insider: {e}") return {} # ============================== # 📌 PHÂN TÍCH THỊ TRƯỜNG # ============================== st.title("📉 Bộ Cảnh Báo Rủi Ro Thị Trường & Gợi Ý ETF Nghịch Đảo") tickers = { "SPY": "S&P 500", "QQQ": "NASDAQ 100", "IWM": "Russell 2000", "SOXX": "Semiconductors", "XLF": "Financials" } market_data = {} for ticker, name in tickers.items(): price, change, vol_ratio, hist = get_stock_data(ticker) pcr = get_pcr(ticker) pcr_oi = get_pcr(ticker, use_oi=True) pcr_trend = get_pcr_trend(ticker) market_data[ticker] = { "name": name, "change": f"{change:.2f}%" if change is not None else "N/A", "price": f"${price:.2f}" if price is not None else "N/A", "volume_ratio": f"{vol_ratio:.2f}x" if vol_ratio is not None else "N/A", "pcr": f"{pcr}" if pcr is not None else "N/A", "pcr_oi": f"{pcr_oi}" if pcr_oi is not None else "N/A", "pcr_trend": f"{pcr_trend}" if pcr_trend is not None else "N/A", "history": hist } st.subheader("📊 Thị Trường Chung") for ticker, data in market_data.items(): st.write(f"**{ticker} ({data['name']})**: {data['change']} | Giá: {data['price']} | Volume: {data['volume_ratio']} | PCR: {data['pcr']} | PCR OI: {data['pcr_oi']} | PCR Trend (5d): {data['pcr_trend']}") if data["history"] is not None: fig = px.line(data["history"], x=data["history"].index, y="Close", title=f"Giá {ticker} (10 ngày gần nhất)") st.plotly_chart(fig) if 'pcr' in data and data['pcr'] != "N/A": pcr_value = float(data['pcr']) if pcr_value > 1.2: st.warning(f"⚠️ PCR cao ({pcr_value}) → Nhiều người mua PUT → Thị trường có thể suy yếu.") elif pcr_value < 0.7: st.success(f"✅ PCR thấp ({pcr_value}) → Nhiều người mua CALL → Thị trường có thể tích cực.") else: st.info(f"ℹ️ PCR trung bình ({pcr_value}) → Tâm lý thị trường trung lập.") vix_history = get_vix_history() if vix_history is not None: fig = px.line(vix_history, x=vix_history.index, y="Close", title="Biểu đồ VIX Index (10 ngày gần nhất)") st.plotly_chart(fig) st.subheader("📈 Dữ liệu Futures (Pre-market từ Business Insider)") futures_data = get_futures_data_from_businessinsider() for name, data in futures_data.items(): st.write(f"**{name}**: Giá: ${data['price']:.2f} | Biến động: {data['change']:.2f} ({data['percent_change']:.2f}%)") if data["percent_change"] < -2: st.error(f"⚠️ {name} giảm mạnh ({data['percent_change']:.2f}%) → Dấu hiệu rủi ro cao!") # Hiển thị Fear & Greed Index từ cả hai nguồn st.subheader("📊 Fear & Greed Index") # Nguồn cũ: feargreedmeter.com st.write("**Nguồn cũ (feargreedmeter.com):**") fear_greed_index_old, fear_greed_status_old = get_fear_and_greed_index_from_feargreedmeter() if fear_greed_index_old is not None: st.write(f"📉 **Fear & Greed Index**: {fear_greed_index_old} ({fear_greed_status_old})") if fear_greed_index_old < 25: st.error("⚠️ Extreme Fear → Thị trường rất tiêu cực!") elif fear_greed_index_old > 75: st.warning("⚠️ Extreme Greed → Thị trường có thể quá nóng!") else: st.info("ℹ️ Tâm lý thị trường trung lập.") else: # Sử dụng dữ liệu cũ nếu không lấy được dữ liệu mới fear_greed_index_old = 22 # Dữ liệu từ tài liệu trước đó fear_greed_status_old = "Extreme Fear" st.write(f"📉 **Fear & Greed Index (dữ liệu cũ)**: {fear_greed_index_old} ({fear_greed_status_old})") st.error("⚠️ Extreme Fear → Thị trường rất tiêu cực!") # Nguồn mới: MacroMicro st.write("**Nguồn mới (MacroMicro):**") fear_greed_index_new, fear_greed_status_new = get_fear_and_greed_index_from_macromicro() if fear_greed_index_new is not None: st.write(f"📉 **Fear & Greed Index**: {fear_greed_index_new} ({fear_greed_status_new})") if fear_greed_index_new < 25: st.error("⚠️ Extreme Fear → Thị trường rất tiêu cực!") elif fear_greed_index_new > 75: st.warning("⚠️ Extreme Greed → Thị trường có thể quá nóng!") else: st.info("ℹ️ Tâm lý thị trường trung lập.") else: # Sử dụng dữ liệu từ hình ảnh nếu không lấy được dữ liệu mới fear_greed_index_new = 21.86 # Dữ liệu từ hình ảnh fear_greed_status_new = "Extreme Fear" st.write(f"📉 **Fear & Greed Index (dữ liệu từ hình ảnh)**: {fear_greed_index_new} ({fear_greed_status_new})") st.error("⚠️ Extreme Fear → Thị trường rất tiêu cực!") vix_index = get_vix_index() st.write(f"🛑 **VIX Index**: {vix_index:.1f}" if vix_index else "Không có dữ liệu VIX") bond_yield = get_bond_yield() st.subheader("📊 Trái Phiếu & Lãi Suất") st.write(f"📉 **Lợi suất 10 năm (UST10Y)**: {bond_yield:.2f}%" if bond_yield else "Không có dữ liệu lợi suất") GOLD_API_KEY = "goldapi-7jusm8lqoyp4-io" gold_price, gold_change = get_gold_price_from_goldapi(GOLD_API_KEY) st.subheader("🏆 Vàng (Gold)") if gold_price is not None: st.write(f"Giá vàng: ${gold_price:.2f} | Biến động: {gold_change:.2f}%") if gold_change < 0: st.info("Vàng giảm → Nhà đầu tư đang lạc quan.") else: st.warning("Vàng tăng → Nhà đầu tư đang lo ngại rủi ro.") else: st.write("Không lấy được dữ liệu vàng.") st.subheader("🔄 Gợi Ý ETF Nghịch Đảo") risk_score = 0 if vix_index and vix_index > 20: risk_score += 1 if market_data["SPY"]["change"].startswith("-"): risk_score += 1 if fear_greed_index_new and fear_greed_index_new < 25: # Sử dụng nguồn mới từ MacroMicro risk_score += 1 if "SOXX" in market_data and market_data["SOXX"]["change"].startswith("-"): risk_score += 1 for name, data in futures_data.items(): if data["percent_change"] < -2: risk_score += 1 if risk_score >= 2: st.error(f"⚠️ Thị trường có dấu hiệu suy yếu (Risk Score: {risk_score}/5), có thể xem xét ETF nghịch đảo như SQQQ, SPXS, UVXY.") else: st.success(f"✅ Thị trường chưa có dấu hiệu suy yếu lớn (Risk Score: {risk_score}/5), chưa cần sử dụng ETF nghịch đảo.")