import gradio as gr import json import os from datetime import datetime from src.pipeline import run_pipeline, STOCKS, CONFIG # ── CACHED RESULTS ──────────────────────────────────────────────────────────── cached_results = None def load_cached_results(): """Load last pipeline results if available.""" path = "artifacts/pipeline_results.json" if os.path.exists(path): with open(path) as f: return json.load(f) return None def run_and_display( selected_stocks: list, strategy: str, max_weight: float, horizon_days: int ): """Run pipeline and return results for Gradio display.""" global cached_results if not selected_stocks: return "⚠️ Please select at least 3 stocks.", "", "", "" config = { "horizon_days": int(horizon_days), "strategy": strategy, "max_weight": max_weight / 100, "min_weight": 0.02 } try: results = run_pipeline( stocks=selected_stocks, config=config, save_results=True, send_email=False # Don't send email from UI ) cached_results = results return format_outputs(results) except Exception as e: return f"❌ Pipeline failed: {e}", "", "", "" def format_outputs(results): """Format pipeline results for display.""" # Portfolio table portfolio = results.get("portfolio", {}) forecasts = results.get("forecasts", {}) sentiment = results.get("sentiment", {}) metrics = results.get("metrics", {}) explanation = results.get("explanation", "Not available") # Portfolio allocation text portfolio_text = "📊 PORTFOLIO ALLOCATION\n" portfolio_text += "─" * 45 + "\n" portfolio_text += f"{'Stock':<8} {'Weight':>8} {'Forecast':>10} {'Sentiment':>12}\n" portfolio_text += "─" * 45 + "\n" for stock, weight in sorted(portfolio.items(), key=lambda x: x[1], reverse=True): ret = forecasts.get(stock, {}).get("return", 0) sig = sentiment.get(stock, {}).get("signal", "neutral") icon = "🟢" if sig == "bullish" else "🔴" if sig == "bearish" else "⚪" bar = "█" * int(weight * 40) portfolio_text += (f"{stock:<8} {weight:>7.1%} {ret:>+10.2%} " f"{icon} {sig:<10}\n") portfolio_text += f" {bar}\n" # Metrics text metrics_text = "📈 PORTFOLIO METRICS\n" metrics_text += "─" * 35 + "\n" metrics_text += f"Expected Return: {metrics.get('expected_return', 0):>+8.2%}\n" metrics_text += f"Volatility: {metrics.get('volatility', 0):>8.2%}\n" metrics_text += f"Sharpe Ratio: {metrics.get('sharpe_ratio', 0):>8.2f}\n" metrics_text += f"Max Drawdown: {metrics.get('max_drawdown', 0):>8.2%}\n" metrics_text += f"Beta: {metrics.get('beta', 0):>8.2f}\n" # Sentiment text sentiment_text = "🤖 SENTIMENT SIGNALS\n" sentiment_text += "─" * 35 + "\n" for stock, sent in sentiment.items(): score = sent.get("sentiment_score", 0) sig = sent.get("signal", "neutral") conf = sent.get("confidence", 0) icon = "🟢" if sig == "bullish" else "🔴" if sig == "bearish" else "⚪" sentiment_text += (f"{stock:<6} {icon} {sig:<8} " f"score={score:+.2f} conf={conf:.0%}\n") return portfolio_text, metrics_text, sentiment_text, explanation custom_css = """ :root { --primary: #0D9488; --primary-light: #5EEAD4; --border: #ccfbf1; --surface: #f0fdfa; --radius: 16px; --shadow: 0 4px 24px rgba(13,148,136,0.10); } body, .gradio-container { background: linear-gradient(135deg, #f0fdfa, #e6fffa) !important; font-family: 'Inter', sans-serif !important; } .app-header { background: linear-gradient(135deg, #0D9488, #14B8A6); border-radius: var(--radius); padding: 2rem; text-align: center; color: white; margin-bottom: 1.5rem; } .card { background: white !important; border-radius: var(--radius) !important; border: 1px solid var(--border) !important; padding: 1rem !important; box-shadow: var(--shadow) !important; } button.primary { background: linear-gradient(135deg, #0D9488, #14B8A6) !important; border: none !important; color: white !important; font-weight: 600 !important; } """ ALL_STOCKS = [ "AAPL", "MSFT", "GOOGL", "TSLA", "NVDA", "META", "AMZN", "NFLX", "NOW", "AMD", "INTC", "CRM", "ORCL", "ADBE", "PYPL" ] def create_app(): with gr.Blocks(title="Portfolio Optimizer", css=custom_css) as app: gr.HTML("""
Time Series Forecasting · GenAI Sentiment · Portfolio Optimization