stockvnpro / app.py
NguyNhu's picture
Update app.py
2f37028 verified
# -*- coding: utf-8 -*-
import streamlit as st
from vnstock3 import Vnstock, Listing, Quote, Company, Finance, Trading
from vnstock3.explorer.fmarket.fund import Fund
from vnstock3.explorer.misc import vcb_exchange_rate, sjc_gold_price
import pandas as pd
import plotly.graph_objs as go
import plotly.express as px
from datetime import datetime, timedelta
st.set_page_config(page_title="Vietnam Stock Market Insight", layout="wide")
st.title("📈 Vietnam Stock Market Insight")
st.sidebar.header("Cài đặt")
user_selected_source = st.sidebar.selectbox(
"Chọn nguồn dữ liệu Mặc định (cho PTKT, Bảng giá, Biểu đồ NC):",
["TCBS", "SSI", "VCI", "VPS", "DNSE"],
index=0,
help="Nguồn này áp dụng cho Phân tích Kỹ thuật, Bảng giá và Biểu đồ nâng cao. Các mục khác sẽ ưu tiên nguồn TCBS."
)
symbol = st.sidebar.text_input("Nhập mã cổ phiếu (VD: FPT, VNM):", value="FPT").upper()
st.sidebar.subheader("Khoảng thời gian")
default_start_date = pd.to_datetime("2024-01-01")
default_end_date = pd.Timestamp.now().date()
col1, col2 = st.sidebar.columns(2)
with col1:
start_date = st.date_input("Ngày bắt đầu", default_start_date, key="start_date", max_value=default_end_date)
with col2:
end_date = st.date_input("Ngày kết thúc", default_end_date, key="end_date", max_value=default_end_date)
if start_date > end_date:
st.sidebar.error("Lỗi: Ngày bắt đầu không được sau ngày kết thúc.")
st.stop()
tab_titles = [
"📉 PT Kỹ thuật",
"📊 PT Cơ bản",
"🏢 TT Doanh nghiệp",
"💹 Bảng giá",
"📈 Chỉ số TT",
"📊 Biểu đồ NC",
"🥇 Kim loại quý",
"📝 HĐ Tương lai",
"💼 Quỹ đầu tư",
"💱 Forex & Crypto"
]
tabs = st.tabs(tab_titles)
# TAB 1 - Phân tích kỹ thuật
with tabs[0]:
st.header(f"Phân tích kỹ thuật cho {symbol}")
st.info(f"Sử dụng nguồn dữ liệu: **{user_selected_source}** (do bạn chọn ở sidebar). Nếu lỗi, hãy thử đổi nguồn.")
if st.button("Tải dữ liệu kỹ thuật", key="tech_analysis_btn"):
with st.spinner(f"Đang tải dữ liệu từ {user_selected_source}..."):
try:
stock = Vnstock().stock(symbol=symbol, source=user_selected_source)
df = stock.quote.history(start=start_date.strftime("%Y-%m-%d"), end=end_date.strftime("%Y-%m-%d"), interval='1D')
if df.empty:
st.error(f"❌ Không có dữ liệu cho mã {symbol} từ nguồn {user_selected_source} trong khoảng thời gian đã chọn.")
else:
df['time'] = pd.to_datetime(df['time'])
st.success(f"Đã tải {len(df)} bản ghi giá cho {symbol} từ {user_selected_source}.")
st.subheader(f"Biểu đồ nến {symbol}")
fig_candle = go.Figure(data=[go.Candlestick(x=df['time'], open=df['open'], high=df['high'], low=df['low'], close=df['close'], name=symbol)])
fig_candle.update_layout(xaxis_title='Ngày', yaxis_title='Giá', xaxis_rangeslider_visible=False)
st.plotly_chart(fig_candle, use_container_width=True)
df.set_index('time', inplace=True); df.sort_index(inplace=True)
df['EMA12'] = df['close'].ewm(span=12, adjust=False).mean()
df['EMA26'] = df['close'].ewm(span=26, adjust=False).mean()
df['MACD'] = df['EMA12'] - df['EMA26']
df['MACD_Signal'] = df['MACD'].ewm(span=9, adjust=False).mean()
df['MACD_Hist'] = df['MACD'] - df['MACD_Signal']
df['BB_Middle'] = df['close'].rolling(window=20).mean()
df['BB_Std'] = df['close'].rolling(window=20).std().fillna(0)
df['BB_Upper'] = df['BB_Middle'] + (2 * df['BB_Std'])
df['BB_Lower'] = df['BB_Middle'] - (2 * df['BB_Std'])
delta = df['close'].diff()
gain = delta.where(delta > 0, 0).fillna(0)
loss = -delta.where(delta < 0, 0).fillna(0)
avg_gain = gain.ewm(com=14 - 1, min_periods=14).mean()
avg_loss = loss.ewm(com=14 - 1, min_periods=14).mean()
rs = avg_gain / avg_loss.replace(0, 0.000001)
df['RSI'] = 100 - (100 / (1 + rs))
display_cols_tech = ['open', 'high', 'low', 'close', 'volume', 'RSI', 'MACD', 'BB_Upper', 'BB_Lower']
st.dataframe(df[display_cols_tech].dropna(subset=['close']).tail(10).round(2))
st.subheader("Chỉ báo kỹ thuật")
fig_bb = go.Figure()
fig_bb.add_trace(go.Scatter(x=df.index, y=df['close'], name='Close', line=dict(color='blue')))
fig_bb.add_trace(go.Scatter(x=df.index, y=df['BB_Upper'], name='Upper', line=dict(color='rgba(255,0,0,0.5)', dash='dash')))
fig_bb.add_trace(go.Scatter(x=df.index, y=df['BB_Middle'], name='Middle', line=dict(color='rgba(255,165,0,0.5)', dash='dash')))
fig_bb.add_trace(go.Scatter(x=df.index, y=df['BB_Lower'], name='Lower', line=dict(color='rgba(0,128,0,0.5)', dash='dash')))
fig_bb.update_layout(title='Bollinger Bands', yaxis_title='Giá')
st.plotly_chart(fig_bb, use_container_width=True)
fig_rsi = go.Figure()
fig_rsi.add_trace(go.Scatter(x=df.index, y=df['RSI'], name='RSI', line=dict(color='purple')))
fig_rsi.add_hline(y=70, line=dict(color='red', dash='dash'), name='Quá mua')
fig_rsi.add_hline(y=30, line=dict(color='green', dash='dash'), name='Quá bán')
fig_rsi.update_layout(title='RSI', yaxis_title='RSI', yaxis=dict(range=[0, 100]), showlegend=True)
st.plotly_chart(fig_rsi, use_container_width=True)
fig_macd = go.Figure()
fig_macd.add_trace(go.Scatter(x=df.index, y=df['MACD'], name='MACD', line=dict(color='blue')))
fig_macd.add_trace(go.Scatter(x=df.index, y=df['MACD_Signal'], name='Signal', line=dict(color='orange')))
colors_macd = ['green' if val >= 0 else 'red' for val in df['MACD_Hist']]
fig_macd.add_trace(go.Bar(x=df.index, y=df['MACD_Hist'], name='Histogram', marker_color=colors_macd, opacity=0.6))
fig_macd.update_layout(title='MACD', yaxis_title='Giá trị', bargap=0.01)
st.plotly_chart(fig_macd, use_container_width=True)
except Exception as e:
st.error(f"❌ Lỗi tải PTKT từ {user_selected_source}: {e}")
st.exception(e)
st.divider()
if st.button("Thống kê giá lịch sử", key="price_history_btn"):
with st.spinner(f"Đang tải lịch sử từ {user_selected_source}..."):
try:
df_history_data = None
if 'df' in locals() and not df.empty and isinstance(df.index, pd.DatetimeIndex):
if df.index.min().date() <= start_date and df.index.max().date() >= end_date:
df_history_data = df.copy()
st.info(f"Dùng dữ liệu đã tải từ {user_selected_source}.")
if df_history_data is None:
stock_hist = Vnstock().stock(symbol=symbol, source=user_selected_source)
df_history_raw = stock_hist.quote.history(start=start_date.strftime("%Y-%m-%d"), end=end_date.strftime("%Y-%m-%d"), interval='1D')
if df_history_raw.empty:
st.error(f"❌ Không có lịch sử cho {symbol} từ {user_selected_source}.")
st.stop()
df_history_raw['time'] = pd.to_datetime(df_history_raw['time'])
df_history_data = df_history_raw.set_index('time')
st.subheader("Dữ liệu giá lịch sử (10 ngày cuối)")
st.dataframe(df_history_data[['open', 'high', 'low', 'close', 'volume']].tail(10).round(2))
min_date_hist = df_history_data.index.min().strftime('%Y-%m-%d')
max_date_hist = df_history_data.index.max().strftime('%Y-%m-%d')
st.write(f"Khoảng thời gian: {min_date_hist} đến {max_date_hist}")
low_min, high_max = df_history_data['low'].min(), df_history_data['high'].max()
vol_mean, vol_max = df_history_data['volume'].mean(), df_history_data['volume'].max()
stats = pd.DataFrame({
'Chỉ số': ['Giá cao nhất', 'Giá thấp nhất', 'Giá đóng cửa TB', 'Khối lượng TB', 'Khối lượng LN', 'Biến động giá (%)'],
'Giá trị': [f"{high_max:,.0f}", f"{low_min:,.0f}", f"{df_history_data['close'].mean():,.0f}", f"{vol_mean:,.0f}", f"{vol_max:,.0f}", f"{(high_max - low_min) / low_min * 100:.2f}%" if low_min != 0 else "N/A"]
}).set_index('Chỉ số')
st.dataframe(stats)
st.subheader("Biểu đồ khối lượng giao dịch")
fig_vol = go.Figure(go.Bar(x=df_history_data.index, y=df_history_data['volume'], name='Khối lượng', marker_color='lightblue'))
fig_vol.update_layout(yaxis_title='Khối lượng')
st.plotly_chart(fig_vol, use_container_width=True)
except Exception as e:
st.error(f"❌ Lỗi lấy lịch sử từ {user_selected_source}: {e}")
st.exception(e)
# ... (Các tab còn lại giữ nguyên theo diff đã fix, đảm bảo mỗi khối try đều có except)
# Do giới hạn ký tự, bạn hãy dùng mẫu này cho các tab còn lại:
# - Tất cả các khối try đều phải có except.
# - Nếu có nhiều try-lồng-nhau, mỗi try đều phải có except.
# - Nếu có try chỉ để kiểm tra một dòng, cũng phải có except.
# Cuối file, không có lệnh bị thừa hoặc thiếu except.
# --- Footer ---
st.markdown("---")