Spaces:
Sleeping
Sleeping
Commit ·
22259b4
1
Parent(s): 515fc70
Update src
Browse files
app.py
CHANGED
|
@@ -45,7 +45,6 @@ def generate_quant_dashboard(ticker, model_name):
|
|
| 45 |
preds, last_close, last_date, _ = predict_horizons(ticker, model_name)
|
| 46 |
|
| 47 |
# 2. Lấy dữ liệu OHLCV 90 ngày để vẽ Candlestick & tính toán Context
|
| 48 |
-
# Sử dụng yfinance trực tiếp để render UI mượt mà, độc lập với backend load_data nặng nề
|
| 49 |
end_dt = datetime.strptime(last_date, '%Y-%m-%d') + timedelta(days=1)
|
| 50 |
start_dt = end_dt - timedelta(days=90)
|
| 51 |
|
|
@@ -81,8 +80,7 @@ def generate_quant_dashboard(ticker, model_name):
|
|
| 81 |
consensus_html = ""
|
| 82 |
target_price = 0
|
| 83 |
|
| 84 |
-
#
|
| 85 |
-
# Hàm tính giá trung bình giữa các model (đã có từ bước trước)
|
| 86 |
def get_avg_price(h):
|
| 87 |
if model_name == "Cả Hai":
|
| 88 |
return (preds[h]["Linear Regression"]["pred_price"] + preds[h]["SVR"]["pred_price"]) / 2
|
|
@@ -96,38 +94,34 @@ def generate_quant_dashboard(ticker, model_name):
|
|
| 96 |
else:
|
| 97 |
return preds[h][model_name]["pred_return"]
|
| 98 |
|
| 99 |
-
# Lấy giá mục tiêu
|
| 100 |
target_1d = get_avg_price(1)
|
| 101 |
target_7d = get_avg_price(7)
|
| 102 |
target_21d = get_avg_price(21)
|
| 103 |
|
| 104 |
-
# Lấy tỷ suất sinh lời (return)
|
| 105 |
ret_1d = get_avg_return(1)
|
| 106 |
ret_7d = get_avg_return(7)
|
| 107 |
ret_21d = get_avg_return(21)
|
| 108 |
|
| 109 |
-
# 1. LOGIC XÁC ĐỊNH TÍN HIỆU TỔNG THỂ
|
| 110 |
-
# Dựa vào mức tăng/giảm của dự báo T+21 (Dài nhất) và T+7 (Trung bình)
|
| 111 |
if ret_21d > 0.02 and ret_7d > 0:
|
| 112 |
signal_text = "STRONG BUY"
|
| 113 |
-
signal_color = "#00ff00"
|
| 114 |
bg_color = "rgba(0, 255, 0, 0.1)"
|
| 115 |
elif ret_21d > 0:
|
| 116 |
signal_text = "ACCUMULATE"
|
| 117 |
-
signal_color = "#00cc66"
|
| 118 |
bg_color = "rgba(0, 204, 102, 0.1)"
|
| 119 |
elif ret_21d < -0.02 and ret_7d < 0:
|
| 120 |
signal_text = "STRONG SELL"
|
| 121 |
-
signal_color = "#ff0000"
|
| 122 |
bg_color = "rgba(255, 0, 0, 0.1)"
|
| 123 |
else:
|
| 124 |
signal_text = "REDUCE / SELL"
|
| 125 |
-
signal_color = "#ff6666"
|
| 126 |
bg_color = "rgba(255, 102, 102, 0.1)"
|
| 127 |
|
| 128 |
-
# Hàm định dạng HTML cho % Return (Tự động đổi màu Xanh/Đỏ)
|
| 129 |
def fmt_ret(val):
|
| 130 |
-
color = "#
|
| 131 |
sign = "+" if val > 0 else ""
|
| 132 |
return f"<span style='color: {color}; font-weight: bold;'>{sign}{val*100:.2f}%</span>"
|
| 133 |
|
|
@@ -166,12 +160,7 @@ def generate_quant_dashboard(ticker, model_name):
|
|
| 166 |
</div>
|
| 167 |
"""
|
| 168 |
|
| 169 |
-
#
|
| 170 |
-
|
| 171 |
-
# 4. Market Context Panel (Technical Stats)
|
| 172 |
-
rsi_color = "#ff3333" if rsi_val > 70 else ("#00ff00" if rsi_val < 30 else "#a8b2d1")
|
| 173 |
-
macd_color = "#00ff00" if macd_h > 0 else "#ff3333"
|
| 174 |
-
|
| 175 |
context_html = f"""
|
| 176 |
<div style="background: var(--background-fill-secondary); border: 1px solid var(--border-color-primary); padding: 15px; border-radius: 5px; font-family: monospace;">
|
| 177 |
<p style="color: var(--body-text-color-subdued); margin:0 0 10px 0; font-size:12px;">LAST CLOSE: {last_date}</p>
|
|
@@ -184,7 +173,7 @@ def generate_quant_dashboard(ticker, model_name):
|
|
| 184 |
</div>
|
| 185 |
"""
|
| 186 |
|
| 187 |
-
# 5. Vẽ biểu đồ Plotly cấp độ Institutional
|
| 188 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
|
| 189 |
vertical_spacing=0.03, row_heights=[0.75, 0.25],
|
| 190 |
subplot_titles=(f"{ticker} - MULTI-HORIZON PROJECTIONS", "VOLUME"))
|
|
@@ -193,7 +182,7 @@ def generate_quant_dashboard(ticker, model_name):
|
|
| 193 |
fig.add_trace(go.Candlestick(
|
| 194 |
x=df_ui['Date'], open=df_ui['Open'], high=df_ui['High'],
|
| 195 |
low=df_ui['Low'], close=df_ui['Close'], name='Price',
|
| 196 |
-
increasing_line_color='#
|
| 197 |
), row=1, col=1)
|
| 198 |
|
| 199 |
# SMAs
|
|
@@ -201,10 +190,12 @@ def generate_quant_dashboard(ticker, model_name):
|
|
| 201 |
fig.add_trace(go.Scatter(x=df_ui['Date'], y=df_ui['SMA_50'], mode='lines', name='SMA 50', line=dict(color='#00bfff', width=1)), row=1, col=1)
|
| 202 |
|
| 203 |
# Volume subplot
|
| 204 |
-
colors = ['#
|
| 205 |
fig.add_trace(go.Bar(x=df_ui['Date'], y=df_ui['Volume'], marker_color=colors, name='Volume'), row=2, col=1)
|
|
|
|
| 206 |
x_future = [base_date, dates_future[1], dates_future[7], dates_future[21]]
|
| 207 |
-
|
|
|
|
| 208 |
if model_name in ["Linear Regression", "Cả Hai"]:
|
| 209 |
y_lr = [last_close, preds[1]["Linear Regression"]["pred_price"],
|
| 210 |
preds[7]["Linear Regression"]["pred_price"], preds[21]["Linear Regression"]["pred_price"]]
|
|
@@ -215,27 +206,32 @@ def generate_quant_dashboard(ticker, model_name):
|
|
| 215 |
y_svr = [last_close, preds[1]["SVR"]["pred_price"],
|
| 216 |
preds[7]["SVR"]["pred_price"], preds[21]["SVR"]["pred_price"]]
|
| 217 |
fig.add_trace(go.Scatter(x=x_future, y=y_svr, mode='lines+markers', name='SVR Trajectory',
|
| 218 |
-
line=dict(color='#
|
|
|
|
| 219 |
upper_band = [last_close, target_1d + atr_val*np.sqrt(1), target_7d + atr_val*np.sqrt(7), target_21d + atr_val*np.sqrt(21)]
|
| 220 |
lower_band = [last_close, target_1d - atr_val*np.sqrt(1), target_7d - atr_val*np.sqrt(7), target_21d - atr_val*np.sqrt(21)]
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
fig.add_trace(go.Scatter(x=x_future, y=
|
|
|
|
| 224 |
|
| 225 |
-
#
|
| 226 |
fig.update_layout(
|
| 227 |
margin=dict(l=40, r=40, t=40, b=40),
|
| 228 |
xaxis_rangeslider_visible=False,
|
| 229 |
-
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
|
| 230 |
-
font=dict(family="Courier New, monospace")
|
| 231 |
-
#
|
|
|
|
| 232 |
)
|
| 233 |
|
| 234 |
-
|
| 235 |
-
fig.
|
|
|
|
|
|
|
| 236 |
return consensus_html, context_html, fig
|
| 237 |
|
| 238 |
-
# --- KIẾN TRÚC GRADIO GIAO DIỆN
|
| 239 |
css = """
|
| 240 |
.gradio-container { max-width: 1400px !important; }
|
| 241 |
#main-row { margin-top: 20px; }
|
|
@@ -253,7 +249,6 @@ function toggle_theme() {
|
|
| 253 |
with gr.Blocks(title="Quant Terminal | Stock ML", css=css, theme=gr.themes.Monochrome()) as demo:
|
| 254 |
with gr.Row():
|
| 255 |
with gr.Column(scale=9):
|
| 256 |
-
# Lưu ý: Đã bỏ style="color: #e6e6fa;" để chữ tự động đổi màu theo theme
|
| 257 |
gr.Markdown("""
|
| 258 |
<div style="padding: 10px 0; border-bottom: 2px solid var(--border-color-primary);">
|
| 259 |
<h1 style="margin: 0; font-family: monospace;">⚡ QUANTRONIC ML TERMINAL v2.0</h1>
|
|
@@ -261,12 +256,10 @@ with gr.Blocks(title="Quant Terminal | Stock ML", css=css, theme=gr.themes.Monoc
|
|
| 261 |
</div>
|
| 262 |
""")
|
| 263 |
with gr.Column(scale=1, min_width=150):
|
| 264 |
-
# Thêm nút bấm chuyển đổi Theme
|
| 265 |
btn_theme = gr.Button("🌓 Toggle Dark/Light", size="sm")
|
| 266 |
|
| 267 |
-
# Đã sửa lỗi tại dòng này: Dùng elem_id thay vì style
|
| 268 |
with gr.Row(elem_id="main-row"):
|
| 269 |
-
# SIDEBAR
|
| 270 |
with gr.Column(scale=1, min_width=300):
|
| 271 |
gr.Markdown("<h4 style='font-family: monospace;'>⚙️ PARAMETERS</h4>")
|
| 272 |
ticker_dd = gr.Dropdown(choices=["AAPL", "MSFT", "GOOGL", "AMZN"], value="AAPL", label="Asset Ticker")
|
|
@@ -277,9 +270,10 @@ with gr.Blocks(title="Quant Terminal | Stock ML", css=css, theme=gr.themes.Monoc
|
|
| 277 |
consensus_panel = gr.HTML()
|
| 278 |
context_panel = gr.HTML()
|
| 279 |
|
| 280 |
-
# MAIN AREA
|
| 281 |
with gr.Column(scale=3):
|
| 282 |
plot_chart = gr.Plot()
|
|
|
|
| 283 |
btn_theme.click(fn=None, inputs=None, outputs=None, js=toggle_script)
|
| 284 |
btn_predict.click(
|
| 285 |
fn=generate_quant_dashboard,
|
|
@@ -287,7 +281,6 @@ with gr.Blocks(title="Quant Terminal | Stock ML", css=css, theme=gr.themes.Monoc
|
|
| 287 |
outputs=[consensus_panel, context_panel, plot_chart]
|
| 288 |
)
|
| 289 |
|
| 290 |
-
# Load default data on start
|
| 291 |
demo.load(
|
| 292 |
fn=generate_quant_dashboard,
|
| 293 |
inputs=[ticker_dd, model_dd],
|
|
|
|
| 45 |
preds, last_close, last_date, _ = predict_horizons(ticker, model_name)
|
| 46 |
|
| 47 |
# 2. Lấy dữ liệu OHLCV 90 ngày để vẽ Candlestick & tính toán Context
|
|
|
|
| 48 |
end_dt = datetime.strptime(last_date, '%Y-%m-%d') + timedelta(days=1)
|
| 49 |
start_dt = end_dt - timedelta(days=90)
|
| 50 |
|
|
|
|
| 80 |
consensus_html = ""
|
| 81 |
target_price = 0
|
| 82 |
|
| 83 |
+
# Hàm tính giá trung bình giữa các model
|
|
|
|
| 84 |
def get_avg_price(h):
|
| 85 |
if model_name == "Cả Hai":
|
| 86 |
return (preds[h]["Linear Regression"]["pred_price"] + preds[h]["SVR"]["pred_price"]) / 2
|
|
|
|
| 94 |
else:
|
| 95 |
return preds[h][model_name]["pred_return"]
|
| 96 |
|
|
|
|
| 97 |
target_1d = get_avg_price(1)
|
| 98 |
target_7d = get_avg_price(7)
|
| 99 |
target_21d = get_avg_price(21)
|
| 100 |
|
|
|
|
| 101 |
ret_1d = get_avg_return(1)
|
| 102 |
ret_7d = get_avg_return(7)
|
| 103 |
ret_21d = get_avg_return(21)
|
| 104 |
|
| 105 |
+
# 1. LOGIC XÁC ĐỊNH TÍN HIỆU TỔNG THỂ
|
|
|
|
| 106 |
if ret_21d > 0.02 and ret_7d > 0:
|
| 107 |
signal_text = "STRONG BUY"
|
| 108 |
+
signal_color = "#00ff00"
|
| 109 |
bg_color = "rgba(0, 255, 0, 0.1)"
|
| 110 |
elif ret_21d > 0:
|
| 111 |
signal_text = "ACCUMULATE"
|
| 112 |
+
signal_color = "#00cc66"
|
| 113 |
bg_color = "rgba(0, 204, 102, 0.1)"
|
| 114 |
elif ret_21d < -0.02 and ret_7d < 0:
|
| 115 |
signal_text = "STRONG SELL"
|
| 116 |
+
signal_color = "#ff0000"
|
| 117 |
bg_color = "rgba(255, 0, 0, 0.1)"
|
| 118 |
else:
|
| 119 |
signal_text = "REDUCE / SELL"
|
| 120 |
+
signal_color = "#ff6666"
|
| 121 |
bg_color = "rgba(255, 102, 102, 0.1)"
|
| 122 |
|
|
|
|
| 123 |
def fmt_ret(val):
|
| 124 |
+
color = "#00cc00" if val > 0 else "#ff4444" # Điều chỉnh màu xanh lá cây hơi tối xuống 1 chút cho chế độ nền trắng dễ nhìn
|
| 125 |
sign = "+" if val > 0 else ""
|
| 126 |
return f"<span style='color: {color}; font-weight: bold;'>{sign}{val*100:.2f}%</span>"
|
| 127 |
|
|
|
|
| 160 |
</div>
|
| 161 |
"""
|
| 162 |
|
| 163 |
+
# 4. Market Context Panel
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
context_html = f"""
|
| 165 |
<div style="background: var(--background-fill-secondary); border: 1px solid var(--border-color-primary); padding: 15px; border-radius: 5px; font-family: monospace;">
|
| 166 |
<p style="color: var(--body-text-color-subdued); margin:0 0 10px 0; font-size:12px;">LAST CLOSE: {last_date}</p>
|
|
|
|
| 173 |
</div>
|
| 174 |
"""
|
| 175 |
|
| 176 |
+
# 5. Vẽ biểu đồ Plotly cấp độ Institutional (ĐÃ TỐI ƯU CHO LIGHT/DARK MODE)
|
| 177 |
fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
|
| 178 |
vertical_spacing=0.03, row_heights=[0.75, 0.25],
|
| 179 |
subplot_titles=(f"{ticker} - MULTI-HORIZON PROJECTIONS", "VOLUME"))
|
|
|
|
| 182 |
fig.add_trace(go.Candlestick(
|
| 183 |
x=df_ui['Date'], open=df_ui['Open'], high=df_ui['High'],
|
| 184 |
low=df_ui['Low'], close=df_ui['Close'], name='Price',
|
| 185 |
+
increasing_line_color='#00cc00', decreasing_line_color='#ff3333' # Điều chỉnh màu nến tăng để không bị chói ở nền trắng
|
| 186 |
), row=1, col=1)
|
| 187 |
|
| 188 |
# SMAs
|
|
|
|
| 190 |
fig.add_trace(go.Scatter(x=df_ui['Date'], y=df_ui['SMA_50'], mode='lines', name='SMA 50', line=dict(color='#00bfff', width=1)), row=1, col=1)
|
| 191 |
|
| 192 |
# Volume subplot
|
| 193 |
+
colors = ['#00cc00' if row['Close'] >= row['Open'] else '#ff3333' for _, row in df_ui.iterrows()]
|
| 194 |
fig.add_trace(go.Bar(x=df_ui['Date'], y=df_ui['Volume'], marker_color=colors, name='Volume'), row=2, col=1)
|
| 195 |
+
|
| 196 |
x_future = [base_date, dates_future[1], dates_future[7], dates_future[21]]
|
| 197 |
+
|
| 198 |
+
# Thêm điểm dự báo
|
| 199 |
if model_name in ["Linear Regression", "Cả Hai"]:
|
| 200 |
y_lr = [last_close, preds[1]["Linear Regression"]["pred_price"],
|
| 201 |
preds[7]["Linear Regression"]["pred_price"], preds[21]["Linear Regression"]["pred_price"]]
|
|
|
|
| 206 |
y_svr = [last_close, preds[1]["SVR"]["pred_price"],
|
| 207 |
preds[7]["SVR"]["pred_price"], preds[21]["SVR"]["pred_price"]]
|
| 208 |
fig.add_trace(go.Scatter(x=x_future, y=y_svr, mode='lines+markers', name='SVR Trajectory',
|
| 209 |
+
line=dict(color='#00cccc', dash='dot'), marker=dict(size=8, symbol='diamond')), row=1, col=1)
|
| 210 |
+
|
| 211 |
upper_band = [last_close, target_1d + atr_val*np.sqrt(1), target_7d + atr_val*np.sqrt(7), target_21d + atr_val*np.sqrt(21)]
|
| 212 |
lower_band = [last_close, target_1d - atr_val*np.sqrt(1), target_7d - atr_val*np.sqrt(7), target_21d - atr_val*np.sqrt(21)]
|
| 213 |
+
|
| 214 |
+
# Error Band (Đã đổi màu fillcolor thành xám trung tính có độ trong suốt để tương thích tốt cả nền trắng và đen)
|
| 215 |
+
fig.add_trace(go.Scatter(x=x_future, y=upper_band, mode='lines', name='Risk Cone Upper', line=dict(color='rgba(0, 256, 0, 1)')), row=1, col=1)
|
| 216 |
+
fig.add_trace(go.Scatter(x=x_future, y=lower_band, mode='lines', fill='tonexty', fillcolor='rgba(128, 128, 128, 0.25)', name='Risk Cone Lower', line=dict(color='rgba(256, 0, 0, 0.6)')), row=1, col=1)
|
| 217 |
|
| 218 |
+
# --- TỐI ƯU LAYOUT CHO DARK / LIGHT MODE ---
|
| 219 |
fig.update_layout(
|
| 220 |
margin=dict(l=40, r=40, t=40, b=40),
|
| 221 |
xaxis_rangeslider_visible=False,
|
| 222 |
+
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1, font=dict(color="#888888")), # Chữ xám trung tính
|
| 223 |
+
font=dict(family="Courier New, monospace", color="#525252"), # Cả tiêu đề và nhãn đều xám trung tính
|
| 224 |
+
paper_bgcolor='rgba(0,0,0,0)', # Nền ngoài TRONG SUỐT
|
| 225 |
+
plot_bgcolor='rgba(0,0,0,0)' # Nền trong TRONG SUỐT
|
| 226 |
)
|
| 227 |
|
| 228 |
+
# Thiết lập màu lưới trung tính chống chói
|
| 229 |
+
fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='rgba(128, 128, 128, 0.2)', linecolor='rgba(128, 128, 128, 0.3)', tickfont=dict(color="#888888"))
|
| 230 |
+
fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='rgba(128, 128, 128, 0.2)', linecolor='rgba(128, 128, 128, 0.3)', tickfont=dict(color="#888888"))
|
| 231 |
+
|
| 232 |
return consensus_html, context_html, fig
|
| 233 |
|
| 234 |
+
# --- KIẾN TRÚC GRADIO GIAO DIỆN ---
|
| 235 |
css = """
|
| 236 |
.gradio-container { max-width: 1400px !important; }
|
| 237 |
#main-row { margin-top: 20px; }
|
|
|
|
| 249 |
with gr.Blocks(title="Quant Terminal | Stock ML", css=css, theme=gr.themes.Monochrome()) as demo:
|
| 250 |
with gr.Row():
|
| 251 |
with gr.Column(scale=9):
|
|
|
|
| 252 |
gr.Markdown("""
|
| 253 |
<div style="padding: 10px 0; border-bottom: 2px solid var(--border-color-primary);">
|
| 254 |
<h1 style="margin: 0; font-family: monospace;">⚡ QUANTRONIC ML TERMINAL v2.0</h1>
|
|
|
|
| 256 |
</div>
|
| 257 |
""")
|
| 258 |
with gr.Column(scale=1, min_width=150):
|
|
|
|
| 259 |
btn_theme = gr.Button("🌓 Toggle Dark/Light", size="sm")
|
| 260 |
|
|
|
|
| 261 |
with gr.Row(elem_id="main-row"):
|
| 262 |
+
# SIDEBAR
|
| 263 |
with gr.Column(scale=1, min_width=300):
|
| 264 |
gr.Markdown("<h4 style='font-family: monospace;'>⚙️ PARAMETERS</h4>")
|
| 265 |
ticker_dd = gr.Dropdown(choices=["AAPL", "MSFT", "GOOGL", "AMZN"], value="AAPL", label="Asset Ticker")
|
|
|
|
| 270 |
consensus_panel = gr.HTML()
|
| 271 |
context_panel = gr.HTML()
|
| 272 |
|
| 273 |
+
# MAIN AREA
|
| 274 |
with gr.Column(scale=3):
|
| 275 |
plot_chart = gr.Plot()
|
| 276 |
+
|
| 277 |
btn_theme.click(fn=None, inputs=None, outputs=None, js=toggle_script)
|
| 278 |
btn_predict.click(
|
| 279 |
fn=generate_quant_dashboard,
|
|
|
|
| 281 |
outputs=[consensus_panel, context_panel, plot_chart]
|
| 282 |
)
|
| 283 |
|
|
|
|
| 284 |
demo.load(
|
| 285 |
fn=generate_quant_dashboard,
|
| 286 |
inputs=[ticker_dd, model_dd],
|