Spaces:
Sleeping
Sleeping
Upload app.py with huggingface_hub
Browse files
app.py
CHANGED
|
@@ -149,6 +149,22 @@ def refresh_market_data(tickers_str: str, period: str = "1y"):
|
|
| 149 |
return tickers
|
| 150 |
|
| 151 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
# -----------------------------------------------------------------------------
|
| 153 |
# Visual Component Builders (Continuous Marquee & Cyber HUDs)
|
| 154 |
# -----------------------------------------------------------------------------
|
|
@@ -410,7 +426,22 @@ def plot_stock_candle(
|
|
| 410 |
STATE["market_data"][cache_key] = df
|
| 411 |
df = STATE["market_data"][cache_key]
|
| 412 |
|
| 413 |
-
dates =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
|
| 415 |
# 4 rows: Price (52%), Volume (16%), RSI (16%), MACD (16%)
|
| 416 |
fig = make_subplots(
|
|
@@ -421,67 +452,67 @@ def plot_stock_candle(
|
|
| 421 |
|
| 422 |
# 1. Candlestick
|
| 423 |
fig.add_trace(go.Candlestick(
|
| 424 |
-
x=dates, open=
|
| 425 |
name="OHLC", increasing_line_color='#00ff87', decreasing_line_color='#ff3366', showlegend=False
|
| 426 |
), row=1, col=1)
|
| 427 |
|
| 428 |
# Overlays: SMA 20 & SMA 50
|
| 429 |
if "SMA 20 & SMA 50" in overlays:
|
| 430 |
-
if
|
| 431 |
fig.add_trace(go.Scatter(
|
| 432 |
-
x=dates, y=
|
| 433 |
), row=1, col=1)
|
| 434 |
-
if
|
| 435 |
fig.add_trace(go.Scatter(
|
| 436 |
-
x=dates, y=
|
| 437 |
), row=1, col=1)
|
| 438 |
|
| 439 |
# Overlays: Bollinger Bands
|
| 440 |
-
if "Bollinger Bands (20, 2σ)" in overlays and
|
| 441 |
fig.add_trace(go.Scatter(
|
| 442 |
-
x=dates, y=
|
| 443 |
), row=1, col=1)
|
| 444 |
fig.add_trace(go.Scatter(
|
| 445 |
-
x=dates, y=
|
| 446 |
fill='tonexty', fillcolor='rgba(0, 242, 254, 0.05)', showlegend=False
|
| 447 |
), row=1, col=1)
|
| 448 |
|
| 449 |
# Overlays: VWAP
|
| 450 |
-
if "VWAP" in overlays and
|
| 451 |
fig.add_trace(go.Scatter(
|
| 452 |
-
x=dates, y=
|
| 453 |
), row=1, col=1)
|
| 454 |
|
| 455 |
# 2. Volume
|
| 456 |
-
vol_colors = ['#00ff87' if c >= o else '#ff3366' for c, o in zip(
|
| 457 |
fig.add_trace(go.Bar(
|
| 458 |
-
x=dates, y=
|
| 459 |
), row=2, col=1)
|
| 460 |
if "Volume + 20D MA" in overlays:
|
| 461 |
-
vol_ma = df['volume'].rolling(20, min_periods=1).mean()
|
| 462 |
fig.add_trace(go.Scatter(
|
| 463 |
x=dates, y=vol_ma, line=dict(color='#00f2fe', width=1.2), name="Vol MA20", showlegend=False
|
| 464 |
), row=2, col=1)
|
| 465 |
|
| 466 |
# 3. RSI (14)
|
| 467 |
-
if
|
| 468 |
fig.add_trace(go.Scatter(
|
| 469 |
-
x=dates, y=
|
| 470 |
), row=3, col=1)
|
| 471 |
fig.add_hline(y=70, line_dash='dot', line_color='#ff3366', line_width=1, row=3, col=1)
|
| 472 |
fig.add_hline(y=30, line_dash='dot', line_color='#00ff87', line_width=1, row=3, col=1)
|
| 473 |
|
| 474 |
# 4. MACD
|
| 475 |
-
if
|
| 476 |
fig.add_trace(go.Scatter(
|
| 477 |
-
x=dates, y=
|
| 478 |
), row=4, col=1)
|
| 479 |
fig.add_trace(go.Scatter(
|
| 480 |
-
x=dates, y=
|
| 481 |
), row=4, col=1)
|
| 482 |
-
macd_colors = ['#00ff87' if h >= 0 else '#ff3366' for h in
|
| 483 |
fig.add_trace(go.Bar(
|
| 484 |
-
x=dates, y=
|
| 485 |
), row=4, col=1)
|
| 486 |
|
| 487 |
# Watermark Badges (Zero-collision top-left annotations)
|
|
@@ -524,7 +555,7 @@ def plot_stock_candle(
|
|
| 524 |
)
|
| 525 |
|
| 526 |
# X-Axes: Robust Category Axis with evenly spaced horizontal tick labels (No weekend gaps, zero collapse)
|
| 527 |
-
tick_indices = np.linspace(0, len(dates)-1, min(8, len(dates)), dtype=int)
|
| 528 |
tickvals = [dates[i] for i in tick_indices]
|
| 529 |
|
| 530 |
fig.update_xaxes(
|
|
@@ -550,6 +581,7 @@ def plot_stock_candle(
|
|
| 550 |
tickfont=dict(family='JetBrains Mono', size=10, color='#94a3b8'),
|
| 551 |
row=r, col=1
|
| 552 |
)
|
|
|
|
| 553 |
|
| 554 |
kpi_card = generate_kpi_card_html(ticker)
|
| 555 |
return fig, kpi_card
|
|
@@ -563,6 +595,7 @@ def plot_backtest_cumulative(record: dict) -> go.Figure:
|
|
| 563 |
)
|
| 564 |
days = 252
|
| 565 |
dates = pd.date_range(end=pd.Timestamp.now(), periods=days, freq='B')
|
|
|
|
| 566 |
ann_ret = record.get("annualized_return_pct", 15.0) / 100.0
|
| 567 |
sharpe = record.get("sharpe", 1.5)
|
| 568 |
vol = abs(ann_ret / sharpe) if sharpe != 0 else 0.15
|
|
@@ -573,12 +606,15 @@ def plot_backtest_cumulative(record: dict) -> go.Figure:
|
|
| 573 |
rolling_max = np.maximum.accumulate(cum_ret)
|
| 574 |
dd = (cum_ret - rolling_max) / rolling_max
|
| 575 |
|
|
|
|
|
|
|
|
|
|
| 576 |
fig.add_trace(go.Scatter(
|
| 577 |
-
x=
|
| 578 |
), row=1, col=1)
|
| 579 |
|
| 580 |
fig.add_trace(go.Scatter(
|
| 581 |
-
x=
|
| 582 |
), row=2, col=1)
|
| 583 |
|
| 584 |
fig.add_annotation(
|
|
@@ -646,7 +682,7 @@ def plot_factor_quantiles(record: dict) -> go.Figure:
|
|
| 646 |
def plot_portfolio_allocation(weights: dict) -> go.Figure:
|
| 647 |
"""Plots interactive donut chart of optimized asset allocation."""
|
| 648 |
labels = list(weights.keys())
|
| 649 |
-
values = [w * 100 for w in weights.values()]
|
| 650 |
|
| 651 |
fig = go.Figure(data=[go.Pie(
|
| 652 |
labels=labels, values=values, hole=0.52,
|
|
@@ -734,8 +770,8 @@ def handle_train_xgboost():
|
|
| 734 |
"""
|
| 735 |
importances = res.get("feature_importances", {})
|
| 736 |
fig = go.Figure(data=[go.Bar(
|
| 737 |
-
x=
|
| 738 |
-
y=
|
| 739 |
marker_color='#00f2fe'
|
| 740 |
)])
|
| 741 |
fig.update_layout(
|
|
|
|
| 149 |
return tickers
|
| 150 |
|
| 151 |
|
| 152 |
+
# -----------------------------------------------------------------------------
|
| 153 |
+
# Clean Data Normalizer for Gradio / Plotly Web Serialization
|
| 154 |
+
# (Prevents numpy arrays from serializing as bdata binary buffers in Gradio)
|
| 155 |
+
# -----------------------------------------------------------------------------
|
| 156 |
+
def to_clean_list(series, default_val=None):
|
| 157 |
+
if series is None:
|
| 158 |
+
return []
|
| 159 |
+
out = []
|
| 160 |
+
for val in series:
|
| 161 |
+
if val is None or pd.isna(val) or (isinstance(val, (int, float)) and np.isnan(val)):
|
| 162 |
+
out.append(default_val)
|
| 163 |
+
else:
|
| 164 |
+
out.append(round(float(val), 4))
|
| 165 |
+
return out
|
| 166 |
+
|
| 167 |
+
|
| 168 |
# -----------------------------------------------------------------------------
|
| 169 |
# Visual Component Builders (Continuous Marquee & Cyber HUDs)
|
| 170 |
# -----------------------------------------------------------------------------
|
|
|
|
| 426 |
STATE["market_data"][cache_key] = df
|
| 427 |
df = STATE["market_data"][cache_key]
|
| 428 |
|
| 429 |
+
dates = [str(d)[:10] for d in df.index]
|
| 430 |
+
|
| 431 |
+
open_vals = to_clean_list(df['open'])
|
| 432 |
+
high_vals = to_clean_list(df['high'])
|
| 433 |
+
low_vals = to_clean_list(df['low'])
|
| 434 |
+
close_vals = to_clean_list(df['close'])
|
| 435 |
+
vol_vals = to_clean_list(df['volume'], 0.0)
|
| 436 |
+
sma20_vals = to_clean_list(df['sma20']) if 'sma20' in df else []
|
| 437 |
+
sma50_vals = to_clean_list(df['sma50']) if 'sma50' in df else []
|
| 438 |
+
bb_u = to_clean_list(df['bb_upper']) if 'bb_upper' in df else []
|
| 439 |
+
bb_l = to_clean_list(df['bb_lower']) if 'bb_lower' in df else []
|
| 440 |
+
vwap_vals = to_clean_list(df['vwap']) if 'vwap' in df else []
|
| 441 |
+
rsi_vals = to_clean_list(df['rsi'], 50.0) if 'rsi' in df else []
|
| 442 |
+
macd_vals = to_clean_list(df['macd_line'], 0.0) if 'macd_line' in df else []
|
| 443 |
+
sig_vals = to_clean_list(df['macd_signal'], 0.0) if 'macd_signal' in df else []
|
| 444 |
+
hist_vals = to_clean_list(df['macd_hist'], 0.0) if 'macd_hist' in df else []
|
| 445 |
|
| 446 |
# 4 rows: Price (52%), Volume (16%), RSI (16%), MACD (16%)
|
| 447 |
fig = make_subplots(
|
|
|
|
| 452 |
|
| 453 |
# 1. Candlestick
|
| 454 |
fig.add_trace(go.Candlestick(
|
| 455 |
+
x=dates, open=open_vals, high=high_vals, low=low_vals, close=close_vals,
|
| 456 |
name="OHLC", increasing_line_color='#00ff87', decreasing_line_color='#ff3366', showlegend=False
|
| 457 |
), row=1, col=1)
|
| 458 |
|
| 459 |
# Overlays: SMA 20 & SMA 50
|
| 460 |
if "SMA 20 & SMA 50" in overlays:
|
| 461 |
+
if sma20_vals:
|
| 462 |
fig.add_trace(go.Scatter(
|
| 463 |
+
x=dates, y=sma20_vals, line=dict(color='#ffb703', width=1.5), name="SMA 20"
|
| 464 |
), row=1, col=1)
|
| 465 |
+
if sma50_vals:
|
| 466 |
fig.add_trace(go.Scatter(
|
| 467 |
+
x=dates, y=sma50_vals, line=dict(color='#00f2fe', width=1.5), name="SMA 50"
|
| 468 |
), row=1, col=1)
|
| 469 |
|
| 470 |
# Overlays: Bollinger Bands
|
| 471 |
+
if "Bollinger Bands (20, 2σ)" in overlays and bb_u and bb_l:
|
| 472 |
fig.add_trace(go.Scatter(
|
| 473 |
+
x=dates, y=bb_u, line=dict(color='rgba(0, 242, 254, 0.35)', width=1), showlegend=False
|
| 474 |
), row=1, col=1)
|
| 475 |
fig.add_trace(go.Scatter(
|
| 476 |
+
x=dates, y=bb_l, line=dict(color='rgba(0, 242, 254, 0.35)', width=1),
|
| 477 |
fill='tonexty', fillcolor='rgba(0, 242, 254, 0.05)', showlegend=False
|
| 478 |
), row=1, col=1)
|
| 479 |
|
| 480 |
# Overlays: VWAP
|
| 481 |
+
if "VWAP" in overlays and vwap_vals:
|
| 482 |
fig.add_trace(go.Scatter(
|
| 483 |
+
x=dates, y=vwap_vals, line=dict(color='#d500f9', width=1.2, dash='dash'), name="VWAP"
|
| 484 |
), row=1, col=1)
|
| 485 |
|
| 486 |
# 2. Volume
|
| 487 |
+
vol_colors = ['#00ff87' if (c or 0) >= (o or 0) else '#ff3366' for c, o in zip(close_vals, open_vals)]
|
| 488 |
fig.add_trace(go.Bar(
|
| 489 |
+
x=dates, y=vol_vals, marker_color=vol_colors, name="Volume", showlegend=False
|
| 490 |
), row=2, col=1)
|
| 491 |
if "Volume + 20D MA" in overlays:
|
| 492 |
+
vol_ma = to_clean_list(df['volume'].rolling(20, min_periods=1).mean(), 0.0)
|
| 493 |
fig.add_trace(go.Scatter(
|
| 494 |
x=dates, y=vol_ma, line=dict(color='#00f2fe', width=1.2), name="Vol MA20", showlegend=False
|
| 495 |
), row=2, col=1)
|
| 496 |
|
| 497 |
# 3. RSI (14)
|
| 498 |
+
if rsi_vals:
|
| 499 |
fig.add_trace(go.Scatter(
|
| 500 |
+
x=dates, y=rsi_vals, line=dict(color='#00f2fe', width=1.5), name="RSI(14)", showlegend=False
|
| 501 |
), row=3, col=1)
|
| 502 |
fig.add_hline(y=70, line_dash='dot', line_color='#ff3366', line_width=1, row=3, col=1)
|
| 503 |
fig.add_hline(y=30, line_dash='dot', line_color='#00ff87', line_width=1, row=3, col=1)
|
| 504 |
|
| 505 |
# 4. MACD
|
| 506 |
+
if macd_vals and sig_vals and hist_vals:
|
| 507 |
fig.add_trace(go.Scatter(
|
| 508 |
+
x=dates, y=macd_vals, line=dict(color='#00f2fe', width=1.2), name="MACD", showlegend=False
|
| 509 |
), row=4, col=1)
|
| 510 |
fig.add_trace(go.Scatter(
|
| 511 |
+
x=dates, y=sig_vals, line=dict(color='#ffb703', width=1.2), name="Signal", showlegend=False
|
| 512 |
), row=4, col=1)
|
| 513 |
+
macd_colors = ['#00ff87' if (h or 0) >= 0 else '#ff3366' for h in hist_vals]
|
| 514 |
fig.add_trace(go.Bar(
|
| 515 |
+
x=dates, y=hist_vals, marker_color=macd_colors, name="Histogram", showlegend=False
|
| 516 |
), row=4, col=1)
|
| 517 |
|
| 518 |
# Watermark Badges (Zero-collision top-left annotations)
|
|
|
|
| 555 |
)
|
| 556 |
|
| 557 |
# X-Axes: Robust Category Axis with evenly spaced horizontal tick labels (No weekend gaps, zero collapse)
|
| 558 |
+
tick_indices = [int(i) for i in np.linspace(0, len(dates)-1, min(8, len(dates)), dtype=int)]
|
| 559 |
tickvals = [dates[i] for i in tick_indices]
|
| 560 |
|
| 561 |
fig.update_xaxes(
|
|
|
|
| 581 |
tickfont=dict(family='JetBrains Mono', size=10, color='#94a3b8'),
|
| 582 |
row=r, col=1
|
| 583 |
)
|
| 584 |
+
fig.update_yaxes(range=[0, 100], row=3, col=1)
|
| 585 |
|
| 586 |
kpi_card = generate_kpi_card_html(ticker)
|
| 587 |
return fig, kpi_card
|
|
|
|
| 595 |
)
|
| 596 |
days = 252
|
| 597 |
dates = pd.date_range(end=pd.Timestamp.now(), periods=days, freq='B')
|
| 598 |
+
date_strs = [d.strftime('%Y-%m-%d') for d in dates]
|
| 599 |
ann_ret = record.get("annualized_return_pct", 15.0) / 100.0
|
| 600 |
sharpe = record.get("sharpe", 1.5)
|
| 601 |
vol = abs(ann_ret / sharpe) if sharpe != 0 else 0.15
|
|
|
|
| 606 |
rolling_max = np.maximum.accumulate(cum_ret)
|
| 607 |
dd = (cum_ret - rolling_max) / rolling_max
|
| 608 |
|
| 609 |
+
cum_ret_clean = [round(float(v), 4) for v in cum_ret]
|
| 610 |
+
dd_clean = [round(float(v * 100), 2) for v in dd]
|
| 611 |
+
|
| 612 |
fig.add_trace(go.Scatter(
|
| 613 |
+
x=date_strs, y=cum_ret_clean, line=dict(color='#00ff87', width=2), name="Strategy Equity"
|
| 614 |
), row=1, col=1)
|
| 615 |
|
| 616 |
fig.add_trace(go.Scatter(
|
| 617 |
+
x=date_strs, y=dd_clean, fill='tozeroy', line=dict(color='#ff3366', width=1), name="Drawdown %"
|
| 618 |
), row=2, col=1)
|
| 619 |
|
| 620 |
fig.add_annotation(
|
|
|
|
| 682 |
def plot_portfolio_allocation(weights: dict) -> go.Figure:
|
| 683 |
"""Plots interactive donut chart of optimized asset allocation."""
|
| 684 |
labels = list(weights.keys())
|
| 685 |
+
values = [round(float(w * 100), 2) for w in weights.values()]
|
| 686 |
|
| 687 |
fig = go.Figure(data=[go.Pie(
|
| 688 |
labels=labels, values=values, hole=0.52,
|
|
|
|
| 770 |
"""
|
| 771 |
importances = res.get("feature_importances", {})
|
| 772 |
fig = go.Figure(data=[go.Bar(
|
| 773 |
+
x=[str(k) for k in importances.keys()],
|
| 774 |
+
y=[round(float(v), 4) for v in importances.values()],
|
| 775 |
marker_color='#00f2fe'
|
| 776 |
)])
|
| 777 |
fig.update_layout(
|