LocCoPhieu / app.py
nick5363's picture
Update app.py
1c468ae verified
import pytz
import requests
import pandas as pd
import yfinance as yf
import numpy as np
from bs4 import BeautifulSoup
import gradio as gr
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
# 🔗 URL Yahoo Finance Most Active Stocks
YAHOO_FINANCE_URL = "https://finance.yahoo.com/markets/stocks/most-active/?start=25&count=100"
# 📌 Lấy danh sách 100 cổ phiếu hoạt động mạnh nhất từ Yahoo
def fetch_most_active_stocks():
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(YAHOO_FINANCE_URL, headers=headers)
if response.status_code != 200:
print(f"❌ Lỗi HTTP {response.status_code}")
return []
soup = BeautifulSoup(response.text, 'html.parser')
table = soup.find('table')
if not table:
print("❌ Không tìm thấy bảng dữ liệu!")
return []
rows = table.find_all('tr')[1:]
stocks = []
for row in rows:
cols = row.find_all('td')
if cols:
ticker = cols[0].text.strip()
stocks.append(ticker)
return stocks[:100] # 📈 Giới hạn 100 cổ phiếu
# 📌 Tính toán Williams %R (14 kỳ), RSI (14), MACD, Bollinger Bands
def calculate_technical_indicators(ticker):
try:
stock = yf.Ticker(ticker)
hist = stock.history(period="3mo") # Lấy dữ liệu 3 tháng để tính toán
if hist.empty:
return "N/A", "N/A", "N/A", "N/A"
# Williams %R (14 kỳ)
high14 = hist['High'].rolling(window=14).max()
low14 = hist['Low'].rolling(window=14).min()
close = hist['Close']
williams_r = -100 * ((high14 - close) / (high14 - low14))
williams_r = round(williams_r.iloc[-1], 2) if not np.isnan(williams_r.iloc[-1]) else "N/A"
# RSI (14 kỳ)
delta = close.diff(1)
gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
rs = gain / loss
rsi = 100 - (100 / (1 + rs))
rsi = round(rsi.iloc[-1], 2) if not np.isnan(rsi.iloc[-1]) else "N/A"
# MACD (12,26,9)
ema12 = close.ewm(span=12, adjust=False).mean()
ema26 = close.ewm(span=26, adjust=False).mean()
macd = ema12 - ema26
signal = macd.ewm(span=9, adjust=False).mean()
macd_signal = round(macd.iloc[-1] - signal.iloc[-1], 2) if not np.isnan(macd.iloc[-1]) else "N/A"
# Bollinger Bands (20,2)
sma20 = close.rolling(window=20).mean()
stddev = close.rolling(window=20).std()
upper_band = sma20 + (stddev * 2)
lower_band = sma20 - (stddev * 2)
bollinger_signal = "Buy" if close.iloc[-1] <= lower_band.iloc[-1] else "Sell" if close.iloc[-1] >= upper_band.iloc[-1] else "Neutral"
return williams_r, rsi, macd_signal, bollinger_signal
except:
return "N/A", "N/A", "N/A", "N/A"
# 📌 Phân tích quyền chọn và xu hướng
def analyze_stock_options(ticker):
try:
stock = yf.Ticker(ticker)
current_price = stock.history(period="1d")['Close'].iloc[-1]
williams_r, rsi, macd_signal, bollinger_signal = calculate_technical_indicators(ticker)
try:
options = stock.option_chain()
calls = options.calls
puts = options.puts
except:
return None
total_call_volume = calls['volume'].sum()
total_put_volume = puts['volume'].sum()
pcr = total_put_volume / total_call_volume if total_call_volume > 0 else np.nan
try:
iv_call = calls['impliedVolatility'].mean()
iv_put = puts['impliedVolatility'].mean()
iv_skew = round(iv_call - iv_put, 2) if isinstance(iv_call, float) and isinstance(iv_put, float) else "N/A"
except:
iv_skew = "N/A"
trend = "📈 TĂNG" if pcr < 0.7 else "📉 GIẢM" if pcr > 1.0 else "➖ NEUTRAL"
return {
'Ticker': ticker,
'Current Price': round(current_price, 2),
'Williams %R': williams_r,
'RSI': rsi,
'MACD': macd_signal,
'Signal': bollinger_signal,
'PCR': round(pcr, 2) if not np.isnan(pcr) else "N/A",
'IV Skew': iv_skew,
'Trend': trend
}
except:
return None
# 📌 Chạy ứng dụng trên Gradio
def display_results(stock_symbol):
stock_list = [s.strip().upper() for s in stock_symbol.split(",")] if stock_symbol else fetch_most_active_stocks()
valid_stocks = [s for s in stock_list if yf.Ticker(s).info.get("symbol")]
if not valid_stocks:
return "❌ Không có dữ liệu hợp lệ!"
data = list(map(analyze_stock_options, valid_stocks))
df = pd.DataFrame([d for d in data if d is not None]) # Loại bỏ None nếu có
if df.empty:
return "❌ Không có dữ liệu!"
df.insert(0, "STT", range(1, len(df) + 1))
# Định nghĩa thứ tự cột mong muốn
table_columns = ["STT", "Trend", "Ticker", "Current Price", "Signal", "RSI", "MACD", "Williams %R", "PCR", "IV Skew"]
# Kiểm tra nếu cột tồn tại trước khi sắp xếp
if not df.empty:
df = df[[col for col in table_columns if col in df.columns]]
return df
title = f"📊 Phân tích xu hướng cổ phiếu "
iface = gr.Interface(
fn=display_results,
inputs=gr.Textbox(placeholder="Nhập mã cổ phiếu cách nhau bằng dấu phẩy (VD: AAPL, TSLA, NVDA)", label="📌 Nhập mã cổ phiếu (Tùy chọn)"),
outputs="dataframe",
title=title,
description="""🔍 **Dự đoán xu hướng 📈 TĂNG / 📉 GIẢM**
🚀 Hướng dẫn sử dụng
1. **Nhập mã cổ phiếu** (VD: `AAPL, TSLA, NVDA`) hoặc **để trống** (hệ thống sẽ tự lấy mã cổ phiếu).
2. **Nhấn nút "Submit"** để phân tích xu hướng. (nếu **để trống** thời gian quét sẽ mất tầm 50 giây)
3. **Ứng dụng sẽ tự động phân tích** và dự đoán
📈 TĂNG hoặc 📉 GIẢM. """)
iface.launch()