# -*- 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("---")