Reality8081 commited on
Commit
3eb5b88
·
1 Parent(s): d09e22a

Update src

Browse files
Files changed (5) hide show
  1. app.py +100 -0
  2. requirements.txt +9 -0
  3. src/data_processing.py +135 -0
  4. src/inference.py +61 -0
  5. src/train.py +113 -0
app.py CHANGED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import plotly.graph_objects as go
3
+ from inference import predict_next_day
4
+
5
+ def generate_dashboard(ticker, model_name):
6
+ try:
7
+ preds, last_close, last_date, hist_30 = predict_next_day(ticker, model_name)
8
+ except Exception as e:
9
+ return f"<h3 style='color:red;'>Lỗi: {str(e)}</h3>", None
10
+
11
+ # Bóc tách kết quả cho việc hiển thị HTML/Markdown
12
+ display_model = "SVR" if model_name == "SVR" else "Linear Regression"
13
+ main_pred = preds[display_model]
14
+
15
+ pct_change = main_pred['pred_return'] * 100
16
+ recommendation = "TĂNG 📈" if pct_change > 0 else "GIẢM 📉"
17
+ color = "green" if pct_change > 0 else "red"
18
+
19
+ html_stats = f"""
20
+ <div style="display: flex; gap: 20px; justify-content: space-around; text-align: center; padding: 20px; background-color: #f8f9fa; border-radius: 10px;">
21
+ <div>
22
+ <p style="margin:0; font-size: 16px; color: gray;">Last Close ({last_date})</p>
23
+ <h2 style="margin:0; color: black;">${last_close:.2f}</h2>
24
+ </div>
25
+ <div>
26
+ <p style="margin:0; font-size: 16px; color: gray;">Dự đoán ngày mai ({display_model})</p>
27
+ <h2 style="margin:0; color: {color};">${main_pred['pred_price']:.2f}</h2>
28
+ </div>
29
+ <div>
30
+ <p style="margin:0; font-size: 16px; color: gray;">Tỷ lệ biến động</p>
31
+ <h2 style="margin:0; color: {color};">{pct_change:+.2f}%</h2>
32
+ </div>
33
+ <div>
34
+ <p style="margin:0; font-size: 16px; color: gray;">Khuyến nghị</p>
35
+ <h2 style="margin:0; color: {color}; font-weight: bold;">{recommendation}</h2>
36
+ </div>
37
+ </div>
38
+ """
39
+
40
+ # --- Vẽ biểu đồ Plotly ---
41
+ fig = go.Figure()
42
+
43
+ # 1. Đường giá thực tế
44
+ fig.add_trace(go.Scatter(
45
+ x=hist_30['Date'], y=hist_30['Close'],
46
+ mode='lines+markers', name='Thực tế', line=dict(color='blue')
47
+ ))
48
+
49
+ # Xử lý điểm dự đoán cho ngày mai (tạm gọi là last_date + 1 Business day)
50
+ import pandas as pd
51
+ next_day = pd.to_datetime(last_date) + pd.offsets.BDay(1)
52
+
53
+ # 2. Vẽ marker nổi bật từ điểm cuối
54
+ if model_name in ["Linear Regression", "Cả Hai"]:
55
+ fig.add_trace(go.Scatter(
56
+ x=[hist_30['Date'].iloc[-1], next_day],
57
+ y=[last_close, preds["Linear Regression"]['pred_price']],
58
+ mode='lines+markers', name='Dự đoán LR',
59
+ line=dict(color='orange', dash='dash'),
60
+ marker=dict(size=12, symbol='star')
61
+ ))
62
+
63
+ if model_name in ["SVR", "Cả Hai"]:
64
+ fig.add_trace(go.Scatter(
65
+ x=[hist_30['Date'].iloc[-1], next_day],
66
+ y=[last_close, preds["SVR"]['pred_price']],
67
+ mode='lines+markers', name='Dự đoán SVR',
68
+ line=dict(color='purple', dash='dash'),
69
+ marker=dict(size=12, symbol='star')
70
+ ))
71
+
72
+ fig.update_layout(title=f"Lịch sử 30 phiên và Dự báo {ticker}",
73
+ xaxis_title="Thời gian",
74
+ yaxis_title="Giá (USD)",
75
+ template="plotly_white")
76
+
77
+ return html_stats, fig
78
+
79
+ # --- Giao diện Gradio ---
80
+ with gr.Blocks(title="Stock Prediction Machine Learning") as demo:
81
+ gr.Markdown("# 📈 Hệ thống Dự báo Giá Cổ phiếu với Machine Learning")
82
+
83
+ with gr.Row():
84
+ with gr.Column(scale=1):
85
+ ticker_dd = gr.Dropdown(choices=["AAPL", "MSFT", "GOOGL", "AMZN"], value="AAPL", label="Chọn Mã cổ phiếu (Ticker)")
86
+ model_dd = gr.Dropdown(choices=["SVR", "Linear Regression", "Cả Hai"], value="Cả Hai", label="Chọn Mô hình")
87
+ btn_predict = gr.Button("Dự đoán phiên tiếp theo", variant="primary")
88
+
89
+ with gr.Column(scale=3):
90
+ stats_html = gr.HTML()
91
+ plot_chart = gr.Plot()
92
+
93
+ btn_predict.click(
94
+ fn=generate_dashboard,
95
+ inputs=[ticker_dd, model_dd],
96
+ outputs=[stats_html, plot_chart]
97
+ )
98
+
99
+ if __name__ == "__main__":
100
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ pandas
2
+ numpy
3
+ yfinance
4
+ scikit-learn
5
+ optuna
6
+ joblib
7
+ huggingface-hub
8
+ gradio
9
+ plotly
src/data_processing.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ import yfinance as yf
4
+ import warnings
5
+
6
+ warnings.filterwarnings('ignore')
7
+
8
+ SMA_WINDOWS = [5, 10, 20, 50, 100]
9
+ EMA_WINDOWS = [5, 10, 20, 50]
10
+ RSI_WINDOWS = [7, 14, 21]
11
+ BB_WINDOWS = [10, 20, 50]
12
+ ATR_WINDOWS = [14, 20]
13
+ VOL_WINDOWS = [20, 50]
14
+ LAGS = 3
15
+
16
+ def load_data(symbols, market_symbol, start_date, end_date):
17
+ print(f"Downloading data from {start_date} to {end_date}...")
18
+ df_market = yf.download(market_symbol, start=start_date, end=end_date, auto_adjust=True, progress=False)
19
+ if isinstance(df_market.columns, pd.MultiIndex):
20
+ df_market.columns = df_market.columns.droplevel(1)
21
+
22
+ df_market = df_market.reset_index()[['Date', 'Close']].rename(columns={'Close': 'Market_Close'})
23
+ dfs = []
24
+
25
+ for symbol in symbols:
26
+ try:
27
+ df = yf.download(symbol, start=start_date, end=end_date, auto_adjust=True, progress=False)
28
+ if isinstance(df.columns, pd.MultiIndex):
29
+ df.columns = df.columns.droplevel(1)
30
+ df = df.reset_index()[['Date', 'Open', 'High', 'Low', 'Close', 'Volume']]
31
+ df['Ticker'] = symbol
32
+ df = pd.merge(df, df_market, on='Date', how='left')
33
+ dfs.append(df)
34
+ except Exception as e:
35
+ print(f"Lỗi khi tải dữ liệu {symbol}: {e}")
36
+
37
+ df_concat = pd.concat(dfs, ignore_index=True)
38
+ df_concat = df_concat.sort_values(['Ticker', 'Date']).reset_index(drop=True)
39
+ return df_concat
40
+
41
+ def clean_data(df):
42
+ cleaned_dfs = []
43
+ for ticker, group in df.groupby('Ticker'):
44
+ group = group.set_index('Date').sort_index()
45
+ start_dt, end_dt = group.index.min(), group.index.max()
46
+ all_business_days = pd.date_range(start=start_dt, end=end_dt, freq="B")
47
+ group = group.reindex(all_business_days).ffill().reset_index().rename(columns={'index': 'Date'})
48
+ group['Ticker'] = ticker
49
+ cleaned_dfs.append(group)
50
+
51
+ df_cleaned = pd.concat(cleaned_dfs, ignore_index=True).sort_values(['Ticker', 'Date']).reset_index(drop=True)
52
+ return df_cleaned
53
+
54
+ def validate_data(df, stage="pre_feature"):
55
+ num_cols = df.select_dtypes(include=[np.number]).columns
56
+ if df[num_cols].isna().sum().sum() > 0:
57
+ print(f"WARNING: Có NaN values tại {stage}")
58
+ return df
59
+
60
+ def generate_technical_features(df, is_inference=False):
61
+ data = df.copy()
62
+
63
+ def add_features(group):
64
+ g = group.copy()
65
+ g['Daily_Return'] = g['Close'].pct_change()
66
+ g['Log_Return'] = np.log(1 + g['Daily_Return'])
67
+ g['Market_Return'] = g['Market_Close'].pct_change()
68
+ g['Market_Log_Return'] = np.log(1 + g['Market_Return'])
69
+
70
+ for i in range(1, LAGS + 1):
71
+ g[f'Return_Lag_{i}'] = g['Daily_Return'].shift(i)
72
+ g[f'Market_Return_Lag_{i}'] = g['Market_Return'].shift(i)
73
+
74
+ for w in SMA_WINDOWS:
75
+ sma = g['Close'].rolling(window=w).mean()
76
+ g[f'SMA_{w}_Ratio'] = g['Close'] / sma
77
+ g[f'SMA_{w}_Distance_pct'] = (g['Close'] - sma) / sma * 100
78
+
79
+ for w in EMA_WINDOWS:
80
+ ema = g['Close'].ewm(span=w, adjust=False).mean()
81
+ g[f'EMA_{w}_Ratio'] = g['Close'] / ema
82
+ g[f'EMA_{w}_Distance_pct'] = (g['Close'] - ema) / ema * 100
83
+
84
+ for w in RSI_WINDOWS:
85
+ delta = g['Close'].diff()
86
+ gain = delta.where(delta > 0, 0).rolling(w).mean()
87
+ loss = -delta.where(delta < 0, 0).rolling(w).mean()
88
+ rs = gain / loss
89
+ g[f'RSI_{w}'] = 100 - (100 / (1 + rs))
90
+
91
+ ema_fast = g['Close'].ewm(span=12, adjust=False).mean()
92
+ ema_slow = g['Close'].ewm(span=26, adjust=False).mean()
93
+ g['MACD_Line'] = ema_fast - ema_slow
94
+ g['MACD_Signal'] = g['MACD_Line'].ewm(span=9, adjust=False).mean()
95
+ g['MACD_Hist'] = (g['MACD_Line'] - g['MACD_Signal'])
96
+ g['MACD_Hist_Normalized'] = g['MACD_Hist'] / g['Close'] * 100
97
+
98
+ for w in BB_WINDOWS:
99
+ middle = g['Close'].rolling(w).mean()
100
+ std_dev = g['Close'].rolling(w).std()
101
+ bb_range = (middle + 2 * std_dev) - (middle - 2 * std_dev)
102
+ g[f'BB_Width_{w}_pct'] = (bb_range / middle * 100)
103
+ g[f'BB_Position_{w}'] = (g['Close'] - (middle - 2 * std_dev)) / bb_range.where(bb_range > 0, 1)
104
+
105
+ tr = pd.concat([g['High'] - g['Low'],
106
+ abs(g['High'] - g['Close'].shift(1)),
107
+ abs(g['Low'] - g['Close'].shift(1))], axis=1).max(axis=1)
108
+
109
+ for w in ATR_WINDOWS:
110
+ atr = tr.rolling(w).mean()
111
+ g[f'ATR_Normalized_{w}'] = atr / g['Close']
112
+ g[f'ATR_{w}'] = atr
113
+
114
+ for w in VOL_WINDOWS:
115
+ g[f'Market_Rolling_Vol_{w}'] = g['Market_Return'].rolling(w).std()
116
+ g[f'AAPL_Rolling_Vol_{w}'] = g['Daily_Return'].rolling(w).std()
117
+
118
+ g['Rel_Volume_20'] = g['Volume'] / g['Volume'].rolling(20).mean()
119
+ return g
120
+
121
+ data_list = [add_features(group) for _, group in data.groupby('Ticker')]
122
+ data = pd.concat(data_list, ignore_index=True)
123
+
124
+ if not is_inference:
125
+ data['Target_Return'] = data.groupby('Ticker')['Daily_Return'].shift(-1)
126
+ data = data.dropna().reset_index(drop=True)
127
+ data = validate_data(data, stage="post_feature")
128
+ X = data.drop(columns=['Date', 'Ticker', 'Market_Close', 'Target_Return'], errors='ignore')
129
+ y = data['Target_Return']
130
+ return data, X, y
131
+ else:
132
+ # Nếu là predict, dòng cuối cùng của mỗi ticker sẽ chứa feature đầy đủ và không bị loại bỏ do thiếu target
133
+ data = data.dropna().reset_index(drop=True)
134
+ X = data.drop(columns=['Date', 'Ticker', 'Market_Close'], errors='ignore')
135
+ return data, X, None
src/inference.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import joblib
3
+ import pandas as pd
4
+ from datetime import datetime, timedelta
5
+ from huggingface_hub import hf_hub_download
6
+ from data_processing import load_data, clean_data, generate_technical_features
7
+
8
+ REPO_ID = "Reality8081/Predict_Stock_SVR_Linear" # << THAY ĐỔI DÒNG NÀY TƯƠNG TỰ
9
+ MARKET_SYMBOL = "^GSPC"
10
+
11
+ # Tự động tải models từ Hugging Face nếu chưa có tại local
12
+ def download_model_if_not_exists(filename):
13
+ local_path = os.path.join("models", filename)
14
+ if not os.path.exists(local_path):
15
+ os.makedirs("models", exist_ok=True)
16
+ path = hf_hub_download(repo_id=REPO_ID, filename=filename, local_dir="models")
17
+ return path
18
+ return local_path
19
+
20
+ def predict_next_day(ticker, model_name):
21
+ # Lấy data 150 ngày gần nhất để tính đủ các window (SMA 100 cần ít nhất 100 nến)
22
+ end_date = datetime.now()
23
+ start_date = end_date - timedelta(days=150)
24
+
25
+ df_raw = load_data([ticker], MARKET_SYMBOL, start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d'))
26
+ df_clean = clean_data(df_raw)
27
+ df_features, X, _ = generate_technical_features(df_clean, is_inference=True)
28
+
29
+ if len(X) == 0:
30
+ raise ValueError(f"Không đủ dữ liệu cho {ticker} để tạo đặc trưng.")
31
+
32
+ # Lấy dòng cuối cùng (ngày giao dịch gần nhất)
33
+ latest_X = X.iloc[[-1]]
34
+ latest_data = df_features.iloc[-1]
35
+ last_close = latest_data['Close']
36
+ last_date = latest_data['Date'].strftime('%Y-%m-%d')
37
+
38
+ predictions = {}
39
+
40
+ if model_name in ["Linear Regression", "Cả Hai"]:
41
+ scaler_lr = joblib.load(download_model_if_not_exists('scaler_lr.pkl'))
42
+ model_lr = joblib.load(download_model_if_not_exists('model_lr.pkl'))
43
+ pred_return_lr = model_lr.predict(scaler_lr.transform(latest_X))[0]
44
+ predictions["Linear Regression"] = {
45
+ "pred_return": pred_return_lr,
46
+ "pred_price": last_close * (1 + pred_return_lr)
47
+ }
48
+
49
+ if model_name in ["SVR", "Cả Hai"]:
50
+ scaler_svr = joblib.load(download_model_if_not_exists('scaler_svr.pkl'))
51
+ model_svr = joblib.load(download_model_if_not_exists('model_svr.pkl'))
52
+ pred_return_svr = model_svr.predict(scaler_svr.transform(latest_X))[0]
53
+ predictions["SVR"] = {
54
+ "pred_return": pred_return_svr,
55
+ "pred_price": last_close * (1 + pred_return_svr)
56
+ }
57
+
58
+ # Lịch sử giá 30 phiên để vẽ biểu đồ
59
+ historical_30 = df_features[['Date', 'Close']].tail(30)
60
+
61
+ return predictions, last_close, last_date, historical_30
src/train.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import joblib
3
+ import optuna
4
+ import numpy as np
5
+ from datetime import datetime
6
+ from sklearn.svm import SVR
7
+ from sklearn.linear_model import Ridge
8
+ from sklearn.preprocessing import StandardScaler
9
+ from sklearn.metrics import mean_squared_error
10
+ from sklearn.model_selection import TimeSeriesSplit
11
+ from huggingface_hub import HfApi
12
+
13
+ from data_processing import load_data, clean_data, generate_technical_features
14
+
15
+ SYMBOLS = ["AAPL", 'MSFT', 'GOOGL', 'AMZN']
16
+ MARKET_SYMBOL = "^GSPC"
17
+ START_DATE = "2010-01-01"
18
+ END_DATE = datetime.now().strftime('%Y-%m-%d')
19
+ REPO_ID = "Reality8081/Predict_Stock_SVR_Linear" # << THAY ĐỔI DÒNG NÀY
20
+
21
+ def main():
22
+ print("1. Đang tải và làm sạch dữ liệu...")
23
+ df_raw = load_data(SYMBOLS, MARKET_SYMBOL, START_DATE, END_DATE)
24
+ df_clean = clean_data(df_raw)
25
+
26
+ print("2. Tạo đặc trưng (Features)...")
27
+ _, X, y = generate_technical_features(df_clean, is_inference=False)
28
+
29
+ tscv = TimeSeriesSplit(n_splits=5)
30
+
31
+ # === TỐI ƯU LINEAR REGRESSION (RIDGE) ===
32
+ print("3. Tối ưu siêu tham số Ridge Regression...")
33
+ def objective_lr(trial):
34
+ alpha = trial.suggest_float('alpha', 1e-4, 1e4, log=True)
35
+ fold_scores = []
36
+ for train_idx, val_idx in tscv.split(X):
37
+ X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
38
+ y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]
39
+
40
+ scaler = StandardScaler()
41
+ X_train_scaled = scaler.fit_transform(X_train)
42
+ X_val_scaled = scaler.transform(X_val)
43
+
44
+ model = Ridge(alpha=alpha, random_state=42)
45
+ model.fit(X_train_scaled, y_train)
46
+ preds = model.predict(X_val_scaled)
47
+ fold_scores.append(np.sqrt(mean_squared_error(y_val, preds)))
48
+ return np.mean(fold_scores)
49
+
50
+ study_lr = optuna.create_study(direction='minimize')
51
+ study_lr.optimize(objective_lr, n_trials=20)
52
+ best_alpha = study_lr.best_params['alpha']
53
+
54
+ # === TỐI ƯU SVR ===
55
+ print("4. Tối ưu siêu tham số SVR...")
56
+ def objective_svr(trial):
57
+ C = trial.suggest_float('C', 1e-3, 10.0, log=True)
58
+ epsilon = trial.suggest_float('epsilon', 1e-3, 1.0, log=True)
59
+ fold_scores = []
60
+ for train_idx, val_idx in tscv.split(X):
61
+ X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
62
+ y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]
63
+
64
+ scaler = StandardScaler()
65
+ X_train_scaled = scaler.fit_transform(X_train)
66
+ X_val_scaled = scaler.transform(X_val)
67
+
68
+ # Khống chế max_iter để SVR chạy nhanh hơn trong quá trình tìm kiếm
69
+ model = SVR(kernel='rbf', C=C, epsilon=epsilon, gamma='scale', max_iter=2000)
70
+ model.fit(X_train_scaled, y_train)
71
+ preds = model.predict(X_val_scaled)
72
+ fold_scores.append(np.sqrt(mean_squared_error(y_val, preds)))
73
+ return np.mean(fold_scores)
74
+
75
+ study_svr = optuna.create_study(direction='minimize')
76
+ study_svr.optimize(objective_svr, n_trials=10) # Set số trial vừa phải
77
+
78
+ # === HUẤN LUYỆN MODEL CUỐI CÙNG & LƯU LẠI ===
79
+ print("5. Huấn luyện mô hình cuối và lưu trữ...")
80
+ os.makedirs("models", exist_ok=True)
81
+
82
+ # Ridge
83
+ scaler_lr = StandardScaler()
84
+ X_scaled_lr = scaler_lr.fit_transform(X)
85
+ model_lr = Ridge(alpha=best_alpha, random_state=42)
86
+ model_lr.fit(X_scaled_lr, y)
87
+ joblib.dump(scaler_lr, 'models/scaler_lr.pkl')
88
+ joblib.dump(model_lr, 'models/model_lr.pkl')
89
+
90
+ # SVR
91
+ scaler_svr = StandardScaler()
92
+ X_scaled_svr = scaler_svr.fit_transform(X)
93
+ model_svr = SVR(kernel='rbf', C=study_svr.best_params['C'], epsilon=study_svr.best_params['epsilon'], gamma='scale')
94
+ model_svr.fit(X_scaled_svr, y)
95
+ joblib.dump(scaler_svr, 'models/scaler_svr.pkl')
96
+ joblib.dump(model_svr, 'models/model_svr.pkl')
97
+
98
+ print("6. Tải mô hình lên Hugging Face Hub...")
99
+ hf_token = os.environ.get("HF_TOKEN")
100
+ if hf_token:
101
+ api = HfApi()
102
+ api.upload_folder(
103
+ folder_path="models",
104
+ repo_id=REPO_ID,
105
+ repo_type="model",
106
+ token=hf_token
107
+ )
108
+ print("Upload thành công!")
109
+ else:
110
+ print("Thiếu HF_TOKEN, bỏ qua bước upload.")
111
+
112
+ if __name__ == "__main__":
113
+ main()