File size: 2,969 Bytes
18ee127
b02bd01
 
 
c54e392
18ee127
c54e392
 
 
 
 
 
 
 
 
 
 
 
 
18ee127
c54e392
48663c9
c54e392
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b02bd01
c54e392
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b02bd01
c54e392
 
 
 
 
 
 
 
 
 
 
 
 
 
b02bd01
18ee127
c54e392
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import gradio as gr
import yfinance as yf
import numpy as np
import pandas as pd
from scipy.optimize import minimize

# Define stock tickers (25 from S&P 500)
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")['Adj Close']
        returns = data.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 "Optimization failed. Try adjusting inputs.", None, None, None

        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 f"Error: {str(e)}", None, None, None

with gr.Blocks() as demo:
    gr.Markdown("# πŸ“ˆ Modern Portfolio Optimizer (MPT)")
    gr.Markdown("Select number of years of historical data and your target annual return. This app computes the **minimum risk portfolio** of 25 S&P 500 stocks.")

    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")
    ret_text = gr.Text(label="Expected Return")
    vol_text = gr.Text(label="Expected Volatility")
    sharpe_text = gr.Text(label="Sharpe Ratio")

    run_button.click(
        fn=lambda years, target: optimize_portfolio(years, target / 100),
        inputs=[years_slider, return_slider],
        outputs=[output_table, ret_text, vol_text, sharpe_text]
    )

demo.launch()