Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import yfinance as yf | |
| import numpy as np | |
| import pandas as pd | |
| from scipy.optimize import minimize | |
| TICKERS = [ | |
| 'AAPL', 'MSFT', 'NVDA', 'AVGO', 'ADBE', | |
| 'AMZN', 'TSLA', 'HD', | |
| 'PG', 'COST', | |
| 'UNH', 'JNJ', 'LLY', | |
| 'JPM', 'GS', 'V', | |
| 'CAT', 'UNP', 'GE', | |
| 'XOM', 'NEE', | |
| 'D', | |
| 'GOOGL', 'META', 'CMCSA', | |
| 'PLD' | |
| ] | |
| def optimize_portfolio(years, target_return): | |
| try: | |
| data = yf.download(TICKERS, period=f"{years}y", interval="1mo", group_by="ticker", auto_adjust=True) | |
| if isinstance(data.columns, pd.MultiIndex): | |
| try: | |
| prices = pd.concat([data[ticker]['Close'] for ticker in TICKERS], axis=1) | |
| prices.columns = TICKERS | |
| except Exception: | |
| return pd.DataFrame(), "Error: Failed to extract Close prices from multi-index data.", "", "", "" | |
| else: | |
| prices = data.get("Adj Close") | |
| if prices is None or prices.empty: | |
| return pd.DataFrame(), "Error: 'Adj Close' data not found or empty.", "", "", "" | |
| returns = prices.pct_change().dropna() | |
| mean_returns = returns.mean() * 12 | |
| cov_matrix = returns.cov() * 12 | |
| num_assets = len(TICKERS) | |
| init_weights = np.ones(num_assets) / num_assets | |
| def portfolio_volatility(weights): | |
| return np.sqrt(weights @ cov_matrix @ weights) | |
| constraints = [ | |
| {"type": "eq", "fun": lambda w: np.sum(w) - 1}, | |
| {"type": "eq", "fun": lambda w: w @ mean_returns - target_return} | |
| ] | |
| bounds = tuple((0, 1) for _ in range(num_assets)) | |
| result = minimize( | |
| portfolio_volatility, | |
| init_weights, | |
| method="SLSQP", | |
| bounds=bounds, | |
| constraints=constraints | |
| ) | |
| if not result.success: | |
| return pd.DataFrame(), "Optimization failed. Try adjusting inputs.", "", "", "" | |
| weights = result.x | |
| port_return = weights @ mean_returns | |
| port_vol = np.sqrt(weights @ cov_matrix @ weights) | |
| risk_free_rate = 0.045 | |
| sharpe_ratio = (port_return - risk_free_rate) / port_vol | |
| df = pd.DataFrame({ | |
| "Ticker": TICKERS, | |
| "Weight (%)": np.round(weights * 100, 2) | |
| }).sort_values("Weight (%)", ascending=False).reset_index(drop=True) | |
| return df, "", f"{port_return*100:.2f}%", f"{port_vol*100:.2f}%", f"{sharpe_ratio:.2f}" | |
| except Exception as e: | |
| return pd.DataFrame(), f"Error: {str(e)}", "", "", "" | |
| with gr.Blocks() as demo: | |
| gr.Markdown("# π Modern Portfolio Optimizer (MPT)") | |
| gr.Markdown("Optimize a portfolio of 25 S&P 500 stocks for **minimum risk** with a target return.") | |
| with gr.Row(): | |
| years_slider = gr.Slider(1, 10, value=5, step=1, label="Years of Historical Data") | |
| return_slider = gr.Slider(1.0, 15.0, value=5.0, step=0.1, label="Target Annual Return (%)") | |
| run_button = gr.Button("Optimize Portfolio") | |
| output_table = gr.Dataframe(headers=["Ticker", "Weight (%)"], label="Optimal Allocation") | |
| error_box = gr.Textbox(label="Message", lines=1) | |
| ret_text = gr.Textbox(label="Expected Return") | |
| vol_text = gr.Textbox(label="Expected Volatility") | |
| sharpe_text = gr.Textbox(label="Sharpe Ratio") | |
| run_button.click( | |
| fn=lambda years, target: optimize_portfolio(years, target / 100), | |
| inputs=[years_slider, return_slider], | |
| outputs=[output_table, error_box, ret_text, vol_text, sharpe_text] | |
| ) | |
| demo.launch() | |