Spaces:
Sleeping
Sleeping
| 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() | |