CoolFace
Apppublic

btmnngoc/dataexplorers2025dabaverse

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
prediction_optimization.py355 linesDownload Raw Back to pages
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""")