File size: 3,119 Bytes
18ee127
b02bd01
 
 
c54e392
18ee127
c54e392
 
 
 
 
 
 
 
 
 
 
 
18ee127
c54e392
48663c9
785660b
e44a316
 
785660b
 
 
c54e392
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b02bd01
c54e392
 
e44a316
c54e392
 
 
 
 
 
 
 
 
 
 
 
785660b
c54e392
 
785660b
c54e392
 
 
e44a316
c54e392
b02bd01
c54e392
 
 
 
 
 
785660b
 
 
 
c54e392
 
 
 
785660b
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
91
92
93
94
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")
        if "Adj Close" not in data.columns:
            return pd.DataFrame(), "Error: 'Adj Close' column missing.", "", "", ""

        prices = data['Adj Close']
        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()