Market / app.py
nick5363's picture
Rename app- old 1.py to app.py
2965d15 verified
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.")