|
|
import streamlit as st |
|
|
from streamlit_autorefresh import st_autorefresh |
|
|
import pandas as pd |
|
|
import requests |
|
|
import yfinance as yf |
|
|
from datetime import datetime, timedelta |
|
|
|
|
|
|
|
|
def get_active_stocks(): |
|
|
url = "https://finance.yahoo.com/markets/stocks/most-active/?start=0&count=100" |
|
|
headers = { |
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)", |
|
|
"Accept-Language": "en-US,en;q=0.9" |
|
|
} |
|
|
|
|
|
response = requests.get(url, headers=headers, timeout=10) |
|
|
|
|
|
tables = pd.read_html(response.text) |
|
|
stocks_df = tables[0] |
|
|
|
|
|
stocks_df['Volume'] = stocks_df['Volume'].replace({'M': '*1e6', 'k': '*1e3'}, regex=True).map(pd.eval).astype(int) |
|
|
stocks_df['Avg Vol (3M)'] = stocks_df['Avg Vol (3M)'].replace({'M': '*1e6', 'k': '*1e3'}, regex=True).map(pd.eval).astype(int) |
|
|
|
|
|
result = stocks_df[stocks_df['Volume'] > stocks_df['Avg Vol (3M)']] |
|
|
|
|
|
result_df = result[['Symbol', 'Price', 'Change', 'Change %', 'Volume', 'Avg Vol (3M)']] |
|
|
result_df.columns = ['Symbol', 'Price', 'Change', 'Change %', 'Volume', 'Avg Vol (3M)'] |
|
|
|
|
|
|
|
|
result_df['Price'] = result_df['Price'].str.split(' ').str[0] |
|
|
result_df['Volume'] = result_df['Volume'].apply(lambda x: f"{x:,}") |
|
|
result_df['Avg Vol (3M)'] = result_df['Avg Vol (3M)'].apply(lambda x: f"{x:,}") |
|
|
result_df['Price'] = result_df['Price'].astype(float).apply(lambda x: f"${x:.2f}") |
|
|
result_df['Change'] = result_df['Change'].astype(float).apply(lambda x: f"+{x:.2f}" if x > 0 else f"{x:.2f}") |
|
|
|
|
|
return result_df |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_stock_data(symbols): |
|
|
stock_data = [] |
|
|
for symbol in symbols: |
|
|
try: |
|
|
stock = yf.Ticker(symbol) |
|
|
hist = stock.history(period="1d") |
|
|
live_price = stock.info.get("regularMarketPrice", None) |
|
|
if not hist.empty: |
|
|
open_price = hist['Open'].iloc[-1] |
|
|
high_price = hist['High'].iloc[-1] |
|
|
close_price = hist['Close'].iloc[-1] |
|
|
low_price = hist['Low'].iloc[-1] |
|
|
stock_data.append({ |
|
|
"Symbol": symbol, |
|
|
"Open": round(open_price, 2), |
|
|
"High": round(high_price, 2), |
|
|
"Close": round(close_price, 2), |
|
|
"Low": round(low_price, 2), |
|
|
"Live Price": round(live_price, 2) if live_price else "N/A" |
|
|
}) |
|
|
except Exception as e: |
|
|
print(f"Không lấy được dữ liệu của {symbol}: {e}") |
|
|
|
|
|
return pd.DataFrame(stock_data) |
|
|
|
|
|
|
|
|
def compute_atr(ticker): |
|
|
end_date = datetime.today() |
|
|
start_date = end_date - timedelta(days=30) |
|
|
df = yf.download(ticker, start=start_date, end=end_date, progress=False) |
|
|
if df.empty or len(df) < 15: |
|
|
return None |
|
|
df['H-L'] = df['High'] - df['Low'] |
|
|
df['H-PC'] = abs(df['High'] - df['Close'].shift(1)) |
|
|
df['L-PC'] = abs(df['Low'] - df['Close'].shift(1)) |
|
|
df['TR'] = df[['H-L', 'H-PC', 'L-PC']].max(axis=1) |
|
|
df['ATR'] = df['TR'].rolling(window=14).mean() |
|
|
return round(df['ATR'].iloc[-1], 2) |
|
|
|
|
|
|
|
|
with st.spinner("Đang tải dữ liệu..."): |
|
|
df = get_active_stocks() |
|
|
df['ATR (14)'] = df['Symbol'].apply(compute_atr) |
|
|
st.dataframe(df.dropna(subset=["ATR (14)"]).reset_index(drop=True)) |
|
|
|
|
|
|
|
|
|
|
|
st.title("Stocks with Volume > Avg Vol (3M) and Price Data") |
|
|
|
|
|
|
|
|
st_autorefresh(interval=60000, key="price_refresh") |
|
|
|
|
|
|
|
|
st.write("🔄 **Lần cập nhật gần nhất:**", pd.Timestamp.now()) |
|
|
|
|
|
if st.button("Get Stocks Data"): |
|
|
with st.spinner('Fetching data...'): |
|
|
df = get_active_stocks() |
|
|
symbols = df["Symbol"].tolist() |
|
|
stock_prices = get_stock_data(symbols) |
|
|
|
|
|
|
|
|
merged_df = pd.merge(df, stock_prices, on="Symbol", how="left") |
|
|
|
|
|
st.dataframe(merged_df) |
|
|
|