btmnngoc/dataexplorers2025dabaverse
0
1import streamlit as st2import pandas as pd3import numpy as np4import plotly.graph_objects as go5from sklearn.preprocessing import MinMaxScaler6from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score7from tensorflow.keras.models import load_model8from tensorflow.keras.losses import MeanSquaredError9import xgboost as xgb10import joblib11import os12from datetime import datetime, timedelta13 14# Thiết lập trang Streamlit15st.set_page_config(page_title="Dự Báo Giá Cổ Phiếu FPT & CMG", page_icon="📈", layout="wide")16 17# Tiêu đề ứng dụng18st.title("📈 Hệ Thống Dự Báo Giá Cổ Phiếu FPT & CMG")19st.markdown("**Dự báo giá cổ phiếu bằng mô hình hybrid LSTM, XGBoost, và Meta-model**")20 21# Định nghĩa danh sách đặc trưng toàn cục22FEATURES_XGB = [23 'Return%', 'MA5', 'MA10', 'Volume_ratio', 'Dividend_Event', 'Meeting_Event', 'Volatility', 'Price_Momentum',24 'Tỷ suất lợi nhuận trên Vốn chủ sở hữu bình quân (ROEA)%',25 'Tỷ lệ lãi EBIT%',26 'Chỉ số giá thị trường trên giá trị sổ sách (P/B)Lần',27 'Chỉ số giá thị trường trên thu nhập (P/E)Lần',28 'P/SLần',29 'Tỷ suất sinh lợi trên vốn dài hạn bình quân (ROCE)%',30 'Thu nhập trên mỗi cổ phần (EPS)VNĐ'31]32 33# Sidebar cho lựa chọn thông số34with st.sidebar:35 st.header("Cấu hình Dự Báo")36 stock_choice = st.selectbox("Chọn cổ phiếu", ["FPT", "CMG"])37 forecast_days = st.slider("Số ngày dự báo", 1, 30, 7)38 st.markdown("---")39 st.info("Hệ thống sử dụng mô hình hybrid: LSTM, XGBoost, và Meta-model.")40 41# Hàm tải và xử lý dữ liệu42@st.cache_data43def load_stock_data(stock_id):44 try:45 # Load main transaction data46 df = pd.read_csv(f"4.2.3 (TARGET) (live & his) {stock_id}_detail_transactions_processed.csv")47 df = df[df['StockID'] == stock_id].copy()48 df['Date'] = pd.to_datetime(df['Date'], format='%d/%m/%Y')49 df = df.sort_values('Date')50 df['Closing Price'] = df['Closing Price'].str.replace(',', '').astype(float)51 df['Total Volume'] = df['Total Volume'].str.replace(',', '').astype(float)52 53 # Create features54 df['Return%'] = df['Closing Price'].pct_change() * 10055 df['MA5'] = df['Closing Price'].rolling(window=5).mean()56 df['MA10'] = df['Closing Price'].rolling(window=10).mean()57 df['Volume_ratio'] = df['Total Volume'] / df['Total Volume'].rolling(5).mean()58 df['Volatility'] = df['Closing Price'].pct_change().rolling(window=5).std() * 10059 df['Price_Momentum'] = df['Closing Price'].diff(5)60 df = df.fillna(0) # Điền giá trị thiếu bằng 061 62 # Load event data63 df_dividend = pd.read_csv("3.2 (live & his) news_dividend_issue (FPT_CMG)_processed.csv")64 df_meeting = pd.read_csv("3.3 (live & his) news_shareholder_meeting (FPT_CMG)_processed.csv")65 df_dividend = df_dividend[df_dividend['StockID'] == stock_id].copy()66 df_meeting = df_meeting[df_meeting['StockID'] == stock_id].copy()67 df_dividend['Execution Date'] = pd.to_datetime(df_dividend['Execution Date'], format='%d/%m/%Y', errors='coerce')68 df_meeting['Execution Date'] = pd.to_datetime(df_meeting['Execution Date'], format='%d/%m/%Y')69 df['Dividend_Event'] = df['Date'].isin(df_dividend['Execution Date']).astype(int)70 df['Meeting_Event'] = df['Date'].isin(df_meeting['Execution Date']).astype(int)71 72 # Load financial data73 df_financial = pd.read_csv("6.5 (his) financialreport_metrics_Nhóm ngành_Công nghệ thông tin (of FPT_CMG)_processed.csv")74 def clean_financial_data(df):75 df['Indicator'] = df['Indicator'].str.replace('\n', '', regex=False).str.replace(r'\s+', ' ', regex=True).str.strip()76 for col in df.columns[3:]:77 df[col] = df[col].str.replace(',', '').astype(float, errors='ignore')78 return df79 df_financial = clean_financial_data(df_financial)80 81 indicators = FEATURES_XGB[8:]82 df_financial = df_financial[(df_financial['Stocks'].str.contains(stock_id)) & (df_financial['Indicator'].isin(indicators))].copy()83 quarters = ['Q1_2023', 'Q2_2023', 'Q3_2023', 'Q4_2023', 'Q1_2024', 'Q2_2024', 'Q3_2024', 'Q4_2024']84 df_financial_melted = df_financial.melt(id_vars=['Indicator'], value_vars=quarters, var_name='Quarter', value_name='Value')85 quarter_dates = {86 'Q1_2023': '2023-01-01', 'Q2_2023': '2023-04-01', 'Q3_2023': '2023-07-01', 'Q4_2023': '2023-10-01',87 'Q1_2024': '2024-01-01', 'Q2_2024': '2024-04-01', 'Q3_2024': '2024-07-01', 'Q4_2024': '2024-10-01'88 }89 df_financial_melted['Date'] = df_financial_melted['Quarter'].map(quarter_dates)90 df_financial_melted['Date'] = pd.to_datetime(df_financial_melted['Date'])91 df_financial_pivot = df_financial_melted.pivot(index='Date', columns='Indicator', values='Value')92 df = df.merge(df_financial_pivot, left_on='Date', right_index=True, how='left')93 df[indicators] = df[indicators].ffill()94 df = df.dropna().reset_index(drop=True)95 return df96 except Exception as e:97 st.error(f"Lỗi khi tải dữ liệu cho {stock_id}: {str(e)}. Vui lòng kiểm tra các tệp CSV.")98 st.stop()99 100# Hàm tải mô hình101@st.cache_resource102def load_models(stock_id):103 try:104 # Load scaler if available, else create new one105 scaler_path = f"models/scaler_FPT.joblib" if stock_id == 'FPT' else f"models/scaler_CMG.joblib"106 scaler = joblib.load(scaler_path) if os.path.exists(scaler_path) else MinMaxScaler()107 if not os.path.exists(scaler_path):108 st.warning(f"Scaler cho {stock_id} không được tìm thấy. Sử dụng scaler mới, có thể ảnh hưởng đến độ chính xác.")109 110 # Load models111 model_lstm = load_model(f"models/lstm_model_{stock_id}.h5", custom_objects={'mse': MeanSquaredError(), 'MeanSquaredError': MeanSquaredError()})112 model_xgb = joblib.load(f"models/xgb_model_{stock_id}.joblib")113 meta_model = load_model(f"models/meta_model_{stock_id}.h5", custom_objects={'mse': MeanSquaredError(), 'MeanSquaredError': MeanSquaredError()})114 return {'lstm': model_lstm, 'xgb': model_xgb, 'meta': meta_model}, scaler115 except Exception as e:116 st.error(f"Lỗi khi tải mô hình hoặc scaler cho {stock_id}: {str(e)}. Vui lòng kiểm tra thư mục 'models/'.")117 st.stop()118 119# Tải dữ liệu và mô hình120with st.spinner("Đang tải dữ liệu và mô hình..."):121 df = load_stock_data(stock_choice)122 models, scaler = load_models(stock_choice)123 124# Hàm dự báo giá125def forecast_prices(df, _models, scaler, forecast_days, lookback=7):126 try:127 y_log = np.log1p(df['Closing Price'])128 scaler.fit(y_log.values.reshape(-1, 1))129 scaled_data = scaler.transform(y_log.values.reshape(-1, 1))130 131 # Kiểm tra nếu dữ liệu đủ lookback132 if len(scaled_data) < lookback:133 raise ValueError(f"Dữ liệu không đủ {lookback} ngày để dự báo.")134 135 last_data = scaled_data[-lookback:]136 X_lstm = last_data.reshape(1, lookback, 1)137 138 forecast_prices = []139 current_price = df['Closing Price'].iloc[-1]140 for _ in range(forecast_days):141 lstm_pred = _models['lstm'].predict(X_lstm, verbose=0)142 lstm_pred_price = np.expm1(scaler.inverse_transform(lstm_pred))[0, 0]143 144 last_features = df.iloc[-1][FEATURES_XGB].values.reshape(1, -1)145 xgb_pred = _models['xgb'].predict(last_features)[0]146 147 meta_input = np.array([[lstm_pred_price, xgb_pred]])148 final_pred = _models['meta'].predict(meta_input, verbose=0)[0, 0]149 final_pred = np.nan_to_num(final_pred, nan=current_price, neginf=0)150 forecast_prices.append(max(final_pred, 0))151 152 # Cập nhật new_data_point với lookback đầy đủ153 new_data_point = np.append(last_data[0][1:], lstm_pred[0, 0]) # Trích xuất giá trị duy nhất từ lstm_pred154 if len(new_data_point) != lookback:155 new_data_point = np.pad(new_data_point, (0, lookback - len(new_data_point)), 'edge')[:lookback]156 X_lstm = new_data_point.reshape(1, lookback, 1)157 158 forecast_dates = [df['Date'].iloc[-1] + timedelta(days=i) for i in range(1, forecast_days + 1)]159 return forecast_dates, forecast_prices160 except Exception as e:161 st.error(f"Lỗi khi dự báo giá cho {stock_choice}: {str(e)}")162 st.stop()163 164# Tab chính165tab1, tab2, tab3 = st.tabs(["📊 Dữ Liệu & Phân Tích", "🔮 Dự Báo Giá", "📌 Đề Xuất Giao Dịch"])166 167with tab1:168 st.header(f"Phân Tích Cổ Phiếu {stock_choice}")169 col1, col2 = st.columns(2)170 171 with col1:172 st.subheader("Biểu Đồ Giá")173 fig = go.Figure()174 fig.add_trace(go.Scatter(x=df['Date'], y=df['Closing Price'], name='Giá đóng cửa', line=dict(color='blue')))175 fig.add_trace(go.Scatter(x=df['Date'], y=df['MA5'], name='MA5', line=dict(color='orange', width=1)))176 fig.add_trace(go.Scatter(x=df['Date'], y=df['MA10'], name='MA10', line=dict(color='green', width=1)))177 fig.update_layout(height=400, xaxis_title='Ngày', yaxis_title='Giá (VNĐ)', xaxis_rangeslider_visible=True)178 st.plotly_chart(fig, use_container_width=True)179 180 with col2:181 st.subheader("Chỉ Số Kỹ Thuật")182 delta = df['Closing Price'].diff()183 gain = (delta.where(delta > 0, 0)).rolling(window=5).mean()184 loss = (-delta.where(delta < 0, 0)).rolling(window=5).mean()185 rs = gain / loss.replace(0, np.finfo(float).eps)186 rsi = 100 - (100 / (1 + rs))187 ema12 = df['Closing Price'].ewm(span=5, adjust=False).mean()188 ema26 = df['Closing Price'].ewm(span=10, adjust=False).mean()189 macd = ema12 - ema26190 signal = macd.ewm(span=3, adjust=False).mean()191 192 st.metric("RSI (5 ngày)", f"{rsi.iloc[-1]:.2f}",193 "Mua quá" if rsi.iloc[-1] > 70 else "Bán quá" if rsi.iloc[-1] < 30 else "Bình thường")194 st.metric("MACD", f"{macd.iloc[-1]:.2f}",195 "Tăng" if macd.iloc[-1] > signal.iloc[-1] else "Giảm")196 st.metric("Khối lượng giao dịch", f"{df['Total Volume'].iloc[-1]:,.0f}")197 trend = "Tăng" if df['Closing Price'].iloc[-1] > df['Closing Price'].iloc[-5] else "Giảm"198 st.metric("Xu hướng ngắn hạn", trend)199 200with tab2:201 st.header(f"Dự Báo Giá {stock_choice}")202 with st.spinner("Đang tạo dự báo giá..."):203 forecast_dates, forecast_prices = forecast_prices(df, models, scaler, forecast_days)204 205 col1, col2 = st.columns(2)206 207 with col1:208 st.subheader("Biểu Đồ Dự Báo")209 fig = go.Figure()210 fig.add_trace(go.Scatter(211 x=df['Date'],212 y=df['Closing Price'],213 name='Giá lịch sử',214 line=dict(color='blue')215 ))216 fig.add_trace(go.Scatter(217 x=forecast_dates,218 y=forecast_prices,219 name='Dự báo',220 line=dict(color='red', dash='dot')221 ))222 fig.update_layout(223 height=500,224 title=f"Dự báo giá {stock_choice} trong {forecast_days} ngày tới",225 xaxis_title="Ngày",226 yaxis_title="Giá (VNĐ)",227 showlegend=True228 )229 st.plotly_chart(fig, use_container_width=True)230 231 with col2:232 st.subheader("Chi Tiết Dự Báo")233 current_price = df['Closing Price'].iloc[-1]234 forecast_df = pd.DataFrame({235 'Ngày': forecast_dates,236 'Giá dự báo': forecast_prices,237 'Biến động (%)': [(p / current_price - 1) * 100 for p in forecast_prices]238 })239 forecast_df['Giá dự báo'] = forecast_df['Giá dự báo'].apply(lambda x: f"{x:,.0f}")240 forecast_df['Biến động (%)'] = forecast_df['Biến động (%)'].apply(lambda x: f"{x:+.2f}%")241 242 st.dataframe(243 forecast_df,244 hide_index=True,245 use_container_width=True246 )247 248 max_change = max([abs(float(x.strip('%'))) for x in forecast_df['Biến động (%)']])249 st.metric("Biến động tối đa dự kiến", f"{max_change:.2f}%")250 251with tab3:252 st.header(f"Đề Xuất Giao Dịch {stock_choice}")253 254 current_price = df['Closing Price'].iloc[-1]255 ma5 = df['MA5'].iloc[-1]256 ma10 = df['MA10'].iloc[-1]257 rsi_value = rsi.iloc[-1]258 259 recommendation = "Giữ"260 reasoning = []261 262 forecast_trend = forecast_prices[-1] > current_price263 if forecast_trend:264 reasoning.append("Mô hình dự báo xu hướng tăng trong ngắn hạn")265 else:266 reasoning.append("Mô hình dự báo xu hướng giảm trong ngắn hạn")267 268 if current_price > ma5 > ma10:269 recommendation = "Mua"270 reasoning.append("Giá vượt trên cả MA5 và MA10 - xu hướng tăng ngắn hạn")271 elif current_price < ma5 < ma10:272 recommendation = "Bán"273 reasoning.append("Giá dưới cả MA5 và MA10 - xu hướng giảm ngắn hạn")274 275 if rsi_value < 30:276 recommendation = "Mua mạnh" if recommendation == "Mua" else "Mua"277 reasoning.append("RSI dưới 30 - cổ phiếu bị bán quá mức")278 elif rsi_value > 70:279 recommendation = "Bán mạnh" if recommendation == "Bán" else "Bán"280 reasoning.append("RSI trên 70 - cổ phiếu mua quá mức")281 282 if df['Dividend_Event'].iloc[-5:].sum() > 0:283 reasoning.append("Có sự kiện cổ tức gần đây - thường tạo biến động giá")284 if df['Meeting_Event'].iloc[-5:].sum() > 0:285 reasoning.append("Có sự kiện họp cổ đông gần đây - cần theo dõi thông tin")286 287 col1, col2 = st.columns([1, 3])288 289 with col1:290 st.subheader("Khuyến Nghị")291 if recommendation.startswith("Mua"):292 st.success(f"### {recommendation}")293 elif recommendation.startswith("Bán"):294 st.error(f"### {recommendation}")295 else:296 st.info(f"### {recommendation}")297 298 st.metric("Giá hiện tại", f"{current_price:,.0f}")299 300 atr = df['Closing Price'].diff().abs().rolling(window=5).mean().iloc[-1]301 if recommendation.startswith("Mua"):302 entry = current_price * 0.99303 stop_loss = current_price - 2 * atr304 take_profit = current_price + 3 * atr305 elif recommendation.startswith("Bán"):306 entry = current_price * 1.01307 stop_loss = current_price + 2 * atr308 take_profit = current_price - 3 * atr309 else:310 entry = current_price311 stop_loss = current_price - 1.5 * atr312 take_profit = current_price + 1.5 * atr313 314 st.metric("Điểm vào lệnh", f"{entry:,.0f}")315 st.metric("Cắt lỗ", f"{stop_loss:,.0f}", delta=f"{(stop_loss / current_price - 1) * 100:+.1f}%")316 st.metric("Chốt lời", f"{take_profit:,.0f}", delta=f"{(take_profit / current_price - 1) * 100:+.1f}%")317 318 with col2:319 st.subheader("Phân Tích Chi Tiết")320 st.write("**Cơ sở đề xuất:**")321 for reason in reasoning:322 st.write(f"- {reason}")323 324 st.write("**Chỉ số quan trọng:**")325 cols = st.columns(4)326 with cols[0]:327 st.metric("P/E", f"{df['Chỉ số giá thị trường trên thu nhập (P/E)Lần'].iloc[-1]:.1f}")328 with cols[1]:329 st.metric("ROE", f"{df['Tỷ suất lợi nhuận trên Vốn chủ sở hữu bình quân (ROEA)%'].iloc[-1]:.1f}%")330 with cols[2]:331 st.metric("EPS", f"{df['Thu nhập trên mỗi cổ phần (EPS)VNĐ'].iloc[-1]:,.0f}")332 with cols[3]:333 st.metric("Volume Ratio", f"{df['Volume_ratio'].iloc[-1]:.2f}")334 335 st.warning("**Cảnh báo rủi ro:**")336 st.write("""337 - Dự báo không đảm bảo chính xác 100%338 - Thị trường có thể biến động do yếu tố vĩ mô339 - Luôn sử dụng lệnh cắt lỗ để quản lý rủi ro340 - Cân nhắc đa dạng hóa danh mục đầu tư341 """)342 343# Hiển thị đánh giá mô hình344 345 346# Footer347st.markdown("---")348st.markdown("""349**Hướng dẫn sử dụng:**3501. Chọn cổ phiếu (FPT/CMG) và số ngày dự báo trong sidebar3512. Xem phân tích kỹ thuật và cơ bản ở tab đầu tiên3523. Kiểm tra dự báo giá ở tab thứ hai3534. Tham khảo đề xuất giao dịch ở tab cuối cùng3545. Kiểm tra hiệu suất mô hình ở phần đánh giá355""")