Spaces:
Running on Zero
Running on Zero
Upload app.py with huggingface_hub
Browse files
app.py
CHANGED
|
@@ -22,6 +22,20 @@ import numpy as np
|
|
| 22 |
import plotly.graph_objects as go
|
| 23 |
from plotly.subplots import make_subplots
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
# -----------------------------------------------------------------------------
|
| 26 |
# Hugging Face Hub / Gradio 4.42 Compatibility Shim
|
| 27 |
# -----------------------------------------------------------------------------
|
|
@@ -331,21 +345,46 @@ def generate_kpi_card_html(ticker: str) -> str:
|
|
| 331 |
def generate_macro_hud_html() -> str:
|
| 332 |
"""Generates high-contrast HUD cards for Macro & Quantitative Regime."""
|
| 333 |
snap = STATE["macro_snapshot"]
|
| 334 |
-
yield_slope = snap.get("yield_curve_slope",
|
| 335 |
-
is_inverted = snap.get("inverted_yield_curve",
|
| 336 |
slope_color = "#ff3366" if is_inverted else "#00ff87"
|
| 337 |
-
slope_text =
|
| 338 |
|
| 339 |
vix_val = snap.get("vix", 16.50)
|
| 340 |
vix_color = "#ff3366" if vix_val > 25 else ("#00ff87" if vix_val < 18 else "#ffb703")
|
| 341 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
return f"""
|
| 343 |
<div class="hud-container">
|
| 344 |
<!-- Card 1: Macro Radar -->
|
| 345 |
<div class="hud-card">
|
| 346 |
<div class="hud-title">
|
| 347 |
<span style="color:#00f2fe;">π MACROECONOMIC RADAR</span>
|
| 348 |
-
|
| 349 |
</div>
|
| 350 |
<div class="hud-grid-row">
|
| 351 |
<div class="hud-stat-box">
|
|
@@ -373,6 +412,7 @@ def generate_macro_hud_html() -> str:
|
|
| 373 |
<div class="hud-val">{snap.get('usd_index', 104.20):.2f}</div>
|
| 374 |
</div>
|
| 375 |
</div>
|
|
|
|
| 376 |
</div>
|
| 377 |
|
| 378 |
<!-- Card 2: Quantitative Regime -->
|
|
@@ -731,16 +771,25 @@ def handle_run_quantagent(trading_idea: str, max_rounds: int, threshold: float):
|
|
| 731 |
traj_md += f"> **Mentor Critique & Feedback:**\n> {critique}\n\n"
|
| 732 |
|
| 733 |
status_badge = "π’ ACCEPTED INTO KNOWLEDGE BASE" if rec.get("status") == "ACCEPTED" else "π‘ PENDING REFINEMENT"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 734 |
status_md = f"""
|
| 735 |
### Outer Feedback Loop Backtest Results: {status_badge}
|
| 736 |
-
- **Signal Name**: `{
|
| 737 |
-
- **Information Coefficient (IC Mean)**: `{
|
| 738 |
-
- **IC Information Ratio (IC_IR)**: `{
|
| 739 |
-
- **Annualized Sharpe Ratio**: **`{
|
| 740 |
-
- **Annualized Return**: **`{
|
| 741 |
-
- **Max Drawdown**: `{
|
| 742 |
-
- **Turnover Rate**: `{
|
| 743 |
-
- **Valid Data Ratio**: `{
|
| 744 |
"""
|
| 745 |
|
| 746 |
cum_fig = plot_backtest_cumulative(rec)
|
|
@@ -844,9 +893,10 @@ $$\\text{{ROE}} = \\text{{Net Profit Margin}} \\times \\text{{Asset Turnover}} \
|
|
| 844 |
|
| 845 |
|
| 846 |
def handle_news_lookup(ticker: str):
|
| 847 |
-
"""Fetches live news headlines and calculates sentiment polarity."""
|
| 848 |
ticker = ticker.strip().upper()
|
| 849 |
-
items = news_service.fetch_ticker_news(ticker, max_items=
|
|
|
|
| 850 |
if not items:
|
| 851 |
return "No recent news found.", pd.DataFrame()
|
| 852 |
|
|
@@ -856,10 +906,22 @@ def handle_news_lookup(ticker: str):
|
|
| 856 |
mean_score = df["Polarity Score"].mean()
|
| 857 |
sentiment_label = "π’ Bullish / Positive" if mean_score > 0.15 else ("π΄ Bearish / Negative" if mean_score < -0.15 else "π‘ Neutral")
|
| 858 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 859 |
summary_md = f"""
|
| 860 |
-
### π°
|
|
|
|
| 861 |
- **Overall Catalyst Bias**: **`{sentiment_label}`** (Average Polarity: `{mean_score:+.2f}`)
|
| 862 |
- **Analyzed Articles Count**: `{len(items)}`
|
|
|
|
| 863 |
- **Signal Impact**: Real-time sentiment feeds modulate alpha exposure weights in the Multi-Agent Synthesizer.
|
| 864 |
"""
|
| 865 |
return summary_md, df
|
|
@@ -1490,8 +1552,8 @@ Enter your Hugging Face write token and the target Space ID (`deveshc23/quantage
|
|
| 1490 |
gr.Markdown("### π Optional API Keys (Upgrades functionality)")
|
| 1491 |
with gr.Row():
|
| 1492 |
gemini_key_input = gr.Textbox(label="Google Gemini API Key", type="password", placeholder="AIza...")
|
| 1493 |
-
finnhub_key_input = gr.Textbox(label="Finnhub API Key", type="password", placeholder="Optional...")
|
| 1494 |
-
fred_key_input = gr.Textbox(label="FRED API Key", type="password", placeholder="Optional...")
|
| 1495 |
save_keys_btn = gr.Button("Save API Keys")
|
| 1496 |
|
| 1497 |
def save_keys_action(gemini_k, finnhub_k, fred_k):
|
|
@@ -1500,9 +1562,21 @@ Enter your Hugging Face write token and the target Space ID (`deveshc23/quantage
|
|
| 1500 |
quant_engine.llm = llm_client
|
| 1501 |
if finnhub_k:
|
| 1502 |
news_service.finnhub_key = finnhub_k
|
|
|
|
| 1503 |
if fred_k:
|
| 1504 |
macro_service.fred_key = fred_k
|
| 1505 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1506 |
|
| 1507 |
keys_status_md = gr.Markdown("")
|
| 1508 |
save_keys_btn.click(save_keys_action, [gemini_key_input, finnhub_key_input, fred_key_input], keys_status_md)
|
|
|
|
| 22 |
import plotly.graph_objects as go
|
| 23 |
from plotly.subplots import make_subplots
|
| 24 |
|
| 25 |
+
# Auto-load .env file if present
|
| 26 |
+
_env_path = os.path.join(os.path.dirname(__file__), ".env")
|
| 27 |
+
if os.path.exists(_env_path):
|
| 28 |
+
try:
|
| 29 |
+
with open(_env_path, "r", encoding="utf-8") as _ef:
|
| 30 |
+
for _line in _ef:
|
| 31 |
+
_line = _line.strip()
|
| 32 |
+
if _line and not _line.startswith("#") and "=" in _line:
|
| 33 |
+
_k, _v = _line.split("=", 1)
|
| 34 |
+
if _k.strip() not in os.environ:
|
| 35 |
+
os.environ[_k.strip()] = _v.strip()
|
| 36 |
+
except Exception:
|
| 37 |
+
pass
|
| 38 |
+
|
| 39 |
# -----------------------------------------------------------------------------
|
| 40 |
# Hugging Face Hub / Gradio 4.42 Compatibility Shim
|
| 41 |
# -----------------------------------------------------------------------------
|
|
|
|
| 345 |
def generate_macro_hud_html() -> str:
|
| 346 |
"""Generates high-contrast HUD cards for Macro & Quantitative Regime."""
|
| 347 |
snap = STATE["macro_snapshot"]
|
| 348 |
+
yield_slope = snap.get("yield_curve_slope", 0.31)
|
| 349 |
+
is_inverted = snap.get("inverted_yield_curve", False)
|
| 350 |
slope_color = "#ff3366" if is_inverted else "#00ff87"
|
| 351 |
+
slope_text = f"{yield_slope:+.2f}% ({'INVERTED' if is_inverted else 'NORMAL'})"
|
| 352 |
|
| 353 |
vix_val = snap.get("vix", 16.50)
|
| 354 |
vix_color = "#ff3366" if vix_val > 25 else ("#00ff87" if vix_val < 18 else "#ffb703")
|
| 355 |
|
| 356 |
+
fred_tag = '<span class="hud-tag" style="border-color:#00ff87; color:#00ff87;">ST. LOUIS FED (FRED) ACTIVE</span>' if snap.get("fred_active") else '<span class="hud-tag">REAL-TIME FEEDS</span>'
|
| 357 |
+
|
| 358 |
+
fedfunds_row = ""
|
| 359 |
+
if snap.get("fred_active"):
|
| 360 |
+
fedfunds_row = f"""
|
| 361 |
+
<div class="hud-grid-row" style="margin-top:6px; border-top:1px solid rgba(255,255,255,0.06); padding-top:6px;">
|
| 362 |
+
<div class="hud-stat-box">
|
| 363 |
+
<div class="hud-lbl">FED FUNDS RATE</div>
|
| 364 |
+
<div class="hud-val" style="color:#00f2fe;">{snap.get('fed_funds_rate', 3.75)}%</div>
|
| 365 |
+
</div>
|
| 366 |
+
<div class="hud-stat-box">
|
| 367 |
+
<div class="hud-lbl">UNEMPLOYMENT</div>
|
| 368 |
+
<div class="hud-val">{snap.get('unemployment_rate', 4.20)}%</div>
|
| 369 |
+
</div>
|
| 370 |
+
<div class="hud-stat-box">
|
| 371 |
+
<div class="hud-lbl">HY CREDIT SPREAD</div>
|
| 372 |
+
<div class="hud-val">{snap.get('high_yield_spread', 3.40)}%</div>
|
| 373 |
+
</div>
|
| 374 |
+
<div class="hud-stat-box">
|
| 375 |
+
<div class="hud-lbl">SPY 200DMA STATUS</div>
|
| 376 |
+
<div class="hud-val" style="color:#00ff87; font-size:11px;">BULLISH TREND</div>
|
| 377 |
+
</div>
|
| 378 |
+
</div>
|
| 379 |
+
"""
|
| 380 |
+
|
| 381 |
return f"""
|
| 382 |
<div class="hud-container">
|
| 383 |
<!-- Card 1: Macro Radar -->
|
| 384 |
<div class="hud-card">
|
| 385 |
<div class="hud-title">
|
| 386 |
<span style="color:#00f2fe;">π MACROECONOMIC RADAR</span>
|
| 387 |
+
{fred_tag}
|
| 388 |
</div>
|
| 389 |
<div class="hud-grid-row">
|
| 390 |
<div class="hud-stat-box">
|
|
|
|
| 412 |
<div class="hud-val">{snap.get('usd_index', 104.20):.2f}</div>
|
| 413 |
</div>
|
| 414 |
</div>
|
| 415 |
+
{fedfunds_row}
|
| 416 |
</div>
|
| 417 |
|
| 418 |
<!-- Card 2: Quantitative Regime -->
|
|
|
|
| 771 |
traj_md += f"> **Mentor Critique & Feedback:**\n> {critique}\n\n"
|
| 772 |
|
| 773 |
status_badge = "π’ ACCEPTED INTO KNOWLEDGE BASE" if rec.get("status") == "ACCEPTED" else "π‘ PENDING REFINEMENT"
|
| 774 |
+
rec_name = str(rec.get('name', 'N/A'))
|
| 775 |
+
ic_str = f"{float(rec.get('ic_mean', 0.0)):.4f}"
|
| 776 |
+
ic_ir_str = f"{float(rec.get('ic_ir', 0.0)):.3f}"
|
| 777 |
+
sharpe_str = f"{float(rec.get('sharpe', 0.0)):.2f}"
|
| 778 |
+
ann_ret_str = f"{float(rec.get('annualized_return_pct', 0.0)):.2f}"
|
| 779 |
+
mdd_str = f"{float(rec.get('max_drawdown_pct', 0.0)):.2f}"
|
| 780 |
+
turnover_str = f"{float(rec.get('turnover_pct', 0.0)):.1f}"
|
| 781 |
+
valid_str = f"{float(rec.get('valid_ratio', 1.0)):.1%}"
|
| 782 |
+
|
| 783 |
status_md = f"""
|
| 784 |
### Outer Feedback Loop Backtest Results: {status_badge}
|
| 785 |
+
- **Signal Name**: `{rec_name}`
|
| 786 |
+
- **Information Coefficient (IC Mean)**: `{ic_str}`
|
| 787 |
+
- **IC Information Ratio (IC_IR)**: `{ic_ir_str}`
|
| 788 |
+
- **Annualized Sharpe Ratio**: **`{sharpe_str}`**
|
| 789 |
+
- **Annualized Return**: **`{ann_ret_str}%`**
|
| 790 |
+
- **Max Drawdown**: `{mdd_str}%`
|
| 791 |
+
- **Turnover Rate**: `{turnover_str}%`
|
| 792 |
+
- **Valid Data Ratio**: `{valid_str}`
|
| 793 |
"""
|
| 794 |
|
| 795 |
cum_fig = plot_backtest_cumulative(rec)
|
|
|
|
| 893 |
|
| 894 |
|
| 895 |
def handle_news_lookup(ticker: str):
|
| 896 |
+
"""Fetches live news headlines, analyst consensus, and calculates sentiment polarity."""
|
| 897 |
ticker = ticker.strip().upper()
|
| 898 |
+
items = news_service.fetch_ticker_news(ticker, max_items=8)
|
| 899 |
+
recs = news_service.fetch_finnhub_recommendations(ticker)
|
| 900 |
if not items:
|
| 901 |
return "No recent news found.", pd.DataFrame()
|
| 902 |
|
|
|
|
| 906 |
mean_score = df["Polarity Score"].mean()
|
| 907 |
sentiment_label = "π’ Bullish / Positive" if mean_score > 0.15 else ("π΄ Bearish / Negative" if mean_score < -0.15 else "π‘ Neutral")
|
| 908 |
|
| 909 |
+
rec_md = ""
|
| 910 |
+
if recs:
|
| 911 |
+
total = recs['strong_buy'] + recs['buy'] + recs['hold'] + recs['sell'] + recs['strong_sell']
|
| 912 |
+
bull_pct = round((recs['strong_buy'] + recs['buy']) / max(1, total) * 100, 1)
|
| 913 |
+
rec_md = f"""
|
| 914 |
+
#### π Wall Street Analyst Consensus (Finnhub Feed - {recs.get('period', 'Latest')})
|
| 915 |
+
- **Consensus Rating**: **{bull_pct}% Bullish Recommendation** ({recs['strong_buy'] + recs['buy']} Buy / {total} Total Analysts)
|
| 916 |
+
- **Breakdown**: π’ Strong Buy: `{recs['strong_buy']}` | π’ Buy: `{recs['buy']}` | π‘ Hold: `{recs['hold']}` | π΄ Sell: `{recs['sell']}` | π΄ Strong Sell: `{recs['strong_sell']}`
|
| 917 |
+
"""
|
| 918 |
+
|
| 919 |
summary_md = f"""
|
| 920 |
+
### π° Institutional News & Sentiment Catalyst Radar: `{ticker}`
|
| 921 |
+
{rec_md}
|
| 922 |
- **Overall Catalyst Bias**: **`{sentiment_label}`** (Average Polarity: `{mean_score:+.2f}`)
|
| 923 |
- **Analyzed Articles Count**: `{len(items)}`
|
| 924 |
+
- **Data Feed**: `{'Finnhub Institutional News API' if news_service.finnhub_key else 'Yahoo Financial RSS'}`
|
| 925 |
- **Signal Impact**: Real-time sentiment feeds modulate alpha exposure weights in the Multi-Agent Synthesizer.
|
| 926 |
"""
|
| 927 |
return summary_md, df
|
|
|
|
| 1552 |
gr.Markdown("### π Optional API Keys (Upgrades functionality)")
|
| 1553 |
with gr.Row():
|
| 1554 |
gemini_key_input = gr.Textbox(label="Google Gemini API Key", type="password", placeholder="AIza...")
|
| 1555 |
+
finnhub_key_input = gr.Textbox(label="Finnhub API Key", type="password", value=os.environ.get("FINNHUB_API_KEY", ""), placeholder="Optional...")
|
| 1556 |
+
fred_key_input = gr.Textbox(label="FRED API Key", type="password", value=os.environ.get("FRED_API_KEY", ""), placeholder="Optional...")
|
| 1557 |
save_keys_btn = gr.Button("Save API Keys")
|
| 1558 |
|
| 1559 |
def save_keys_action(gemini_k, finnhub_k, fred_k):
|
|
|
|
| 1562 |
quant_engine.llm = llm_client
|
| 1563 |
if finnhub_k:
|
| 1564 |
news_service.finnhub_key = finnhub_k
|
| 1565 |
+
os.environ["FINNHUB_API_KEY"] = finnhub_k
|
| 1566 |
if fred_k:
|
| 1567 |
macro_service.fred_key = fred_k
|
| 1568 |
+
os.environ["FRED_API_KEY"] = fred_k
|
| 1569 |
+
STATE["macro_snapshot"] = macro_service.get_macro_snapshot()
|
| 1570 |
+
# Persist to local .env
|
| 1571 |
+
try:
|
| 1572 |
+
with open(".env", "w", encoding="utf-8") as ef:
|
| 1573 |
+
if os.environ.get("FINNHUB_API_KEY"):
|
| 1574 |
+
ef.write(f"FINNHUB_API_KEY={os.environ['FINNHUB_API_KEY']}\n")
|
| 1575 |
+
if os.environ.get("FRED_API_KEY"):
|
| 1576 |
+
ef.write(f"FRED_API_KEY={os.environ['FRED_API_KEY']}\n")
|
| 1577 |
+
except Exception:
|
| 1578 |
+
pass
|
| 1579 |
+
return "β
API Keys updated, activated, and persisted!"
|
| 1580 |
|
| 1581 |
keys_status_md = gr.Markdown("")
|
| 1582 |
save_keys_btn.click(save_keys_action, [gemini_key_input, finnhub_key_input, fred_key_input], keys_status_md)
|