Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
| 4 |
+
import matplotlib.pyplot as plt
|
| 5 |
+
import plotly.express as px
|
| 6 |
+
import scipy.optimize as sco
|
| 7 |
+
from datetime import datetime, timedelta
|
| 8 |
+
import requests
|
| 9 |
+
import random
|
| 10 |
+
|
| 11 |
+
# Predefined S&P 500 Stock List (Example Tickers, replace with real ones if needed)
|
| 12 |
+
SP500_TICKERS = ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA', 'BRK-B', 'NVDA', 'JPM', 'JNJ', 'V', 'PG', 'UNH', 'HD', 'MA', 'DIS', 'PYPL', 'NFLX', 'KO', 'XOM', 'PFE']
|
| 13 |
+
|
| 14 |
+
def fetch_stock_data(tickers, start_date, end_date):
|
| 15 |
+
url = "https://yahoo-finance166.p.rapidapi.com/api/news/list-by-symbol"
|
| 16 |
+
querystring = {"s": ",".join(tickers), "region": "US", "snippetCount": "500"}
|
| 17 |
+
headers = {
|
| 18 |
+
"x-rapidapi-key": "YOUR_RAPIDAPI_KEY",
|
| 19 |
+
"x-rapidapi-host": "yahoo-finance166.p.rapidapi.com"
|
| 20 |
+
}
|
| 21 |
+
try:
|
| 22 |
+
response = requests.get(url, headers=headers, params=querystring)
|
| 23 |
+
data = response.json()
|
| 24 |
+
if not data:
|
| 25 |
+
raise ValueError("No stock data returned. Please check the API response.")
|
| 26 |
+
return pd.DataFrame(data)
|
| 27 |
+
except Exception as e:
|
| 28 |
+
return f"Error fetching stock data: {str(e)}"
|
| 29 |
+
|
| 30 |
+
def calculate_portfolio_metrics(weights, returns):
|
| 31 |
+
portfolio_return = np.sum(returns.mean() * weights) * 252
|
| 32 |
+
portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(returns.cov() * 252, weights)))
|
| 33 |
+
sharpe_ratio = portfolio_return / portfolio_volatility
|
| 34 |
+
return portfolio_return, portfolio_volatility, sharpe_ratio
|
| 35 |
+
|
| 36 |
+
def optimize_portfolio(returns, max_volatility):
|
| 37 |
+
num_assets = len(returns.columns)
|
| 38 |
+
args = (returns,)
|
| 39 |
+
constraints = (
|
| 40 |
+
{'type': 'eq', 'fun': lambda x: np.sum(x) - 1}, # Sum of weights must be 1
|
| 41 |
+
{'type': 'ineq', 'fun': lambda x: max_volatility - np.sqrt(np.dot(x.T, np.dot(returns.cov() * 252, x)))} # Volatility constraint
|
| 42 |
+
)
|
| 43 |
+
bounds = tuple((0, 1) for _ in range(num_assets))
|
| 44 |
+
result = sco.minimize(lambda weights, returns: -calculate_portfolio_metrics(weights, returns)[2],
|
| 45 |
+
num_assets * [1. / num_assets,],
|
| 46 |
+
args=args,
|
| 47 |
+
method='SLSQP',
|
| 48 |
+
bounds=bounds,
|
| 49 |
+
constraints=constraints)
|
| 50 |
+
return result.x
|
| 51 |
+
num_assets = len(returns.columns)
|
| 52 |
+
args = (returns,)
|
| 53 |
+
constraints = (
|
| 54 |
+
{'type': 'eq', 'fun': lambda x: np.sum(x) - 1}, # Sum of weights must be 1
|
| 55 |
+
)
|
| 56 |
+
bounds = tuple((0, 1) for _ in range(num_assets))
|
| 57 |
+
result = sco.minimize(lambda weights, returns: -calculate_portfolio_metrics(weights, returns)[2],
|
| 58 |
+
num_assets * [1. / num_assets,],
|
| 59 |
+
args=args,
|
| 60 |
+
method='SLSQP',
|
| 61 |
+
bounds=bounds,
|
| 62 |
+
constraints=constraints)
|
| 63 |
+
return result.x
|
| 64 |
+
|
| 65 |
+
def simulate_investment(weights, mu, years, initial_investment=1000):
|
| 66 |
+
projected_return = np.dot(weights, mu) * years
|
| 67 |
+
return initial_investment * (1 + projected_return)
|
| 68 |
+
|
| 69 |
+
def output_results(risk_level):
|
| 70 |
+
try:
|
| 71 |
+
start_date = (datetime.now() - timedelta(days=365 * 5)).strftime('%Y-%m-%d')
|
| 72 |
+
end_date = datetime.now().strftime('%Y-%m-%d')
|
| 73 |
+
selected_tickers = random.sample(SP500_TICKERS, 15)
|
| 74 |
+
|
| 75 |
+
stocks_df = fetch_stock_data(selected_tickers, start_date, end_date)
|
| 76 |
+
if isinstance(stocks_df, str):
|
| 77 |
+
return stocks_df, None, None, "Error", "Error", "Error", None, None, None
|
| 78 |
+
|
| 79 |
+
returns = stocks_df.pct_change().dropna()
|
| 80 |
+
risk_thresholds = {"Low": 0.10, "Medium": 0.20, "High": 0.30}
|
| 81 |
+
max_volatility = risk_thresholds.get(risk_level, 0.20)
|
| 82 |
+
optimized_weights = optimize_portfolio(returns, max_volatility)
|
| 83 |
+
mu = returns.mean() * 252
|
| 84 |
+
portfolio_return, portfolio_volatility, sharpe_ratio = calculate_portfolio_metrics(optimized_weights, returns)
|
| 85 |
+
|
| 86 |
+
expected_annual_return = f'{(portfolio_return * 100):.2f}%'
|
| 87 |
+
annual_volatility = f'{(portfolio_volatility * 100):.2f}%'
|
| 88 |
+
sharpe_ratio_str = f'{sharpe_ratio:.2f}'
|
| 89 |
+
|
| 90 |
+
weights_df = pd.DataFrame({'Ticker': selected_tickers, 'Weight': optimized_weights})
|
| 91 |
+
correlation_matrix = stocks_df.corr()
|
| 92 |
+
fig_corr = px.imshow(correlation_matrix, text_auto=True, title='Stock Correlation Matrix')
|
| 93 |
+
cumulative_returns = (returns + 1).cumprod()
|
| 94 |
+
fig_cum_returns = px.line(cumulative_returns, title='Cumulative Returns of Individual Stocks')
|
| 95 |
+
|
| 96 |
+
projected_1yr = simulate_investment(optimized_weights, mu, 1)
|
| 97 |
+
projected_5yr = simulate_investment(optimized_weights, mu, 5)
|
| 98 |
+
projected_10yr = simulate_investment(optimized_weights, mu, 10)
|
| 99 |
+
projection_df = pd.DataFrame({"Years": [1, 5, 10], "Projected Value": [projected_1yr, projected_5yr, projected_10yr]})
|
| 100 |
+
fig_simulation = px.line(projection_df, x='Years', y='Projected Value', title='Projected Investment Growth')
|
| 101 |
+
|
| 102 |
+
return fig_cum_returns, weights_df, fig_corr, expected_annual_return, annual_volatility, sharpe_ratio_str, fig_simulation
|
| 103 |
+
except Exception as e:
|
| 104 |
+
return f"Unexpected error: {str(e)}", None, None, "Error", "Error", "Error", None, None, None
|
| 105 |
+
|
| 106 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue='yellow')) as app:
|
| 107 |
+
gr.Markdown("## Beginner-Friendly Investment Portfolio Generator")
|
| 108 |
+
gr.Markdown("Select your risk level and let the app generate a balanced portfolio based on the S&P 500.")
|
| 109 |
+
|
| 110 |
+
risk_level = gr.Radio(["Low", "Medium", "High"], label="Select Your Risk Level")
|
| 111 |
+
btn = gr.Button("Generate Portfolio", elem_id='highlighted-button')
|
| 112 |
+
|
| 113 |
+
with gr.Row():
|
| 114 |
+
expected_annual_return = gr.Textbox(label="Expected Annual Return")
|
| 115 |
+
annual_volatility = gr.Textbox(label="Annual Volatility")
|
| 116 |
+
sharpe_ratio = gr.Textbox(label="Sharpe Ratio")
|
| 117 |
+
|
| 118 |
+
with gr.Row():
|
| 119 |
+
fig_cum_returns = gr.Plot(label="Cumulative Returns of Individual Stocks")
|
| 120 |
+
weights_df = gr.DataFrame(label="Optimized Weights")
|
| 121 |
+
|
| 122 |
+
with gr.Row():
|
| 123 |
+
fig_corr = gr.Plot(label="Stock Correlation Matrix")
|
| 124 |
+
fig_simulation = gr.Plot(label="Projected Investment Growth")
|
| 125 |
+
|
| 126 |
+
btn.click(output_results, inputs=[risk_level], outputs=[
|
| 127 |
+
fig_cum_returns, weights_df, fig_corr, expected_annual_return,
|
| 128 |
+
annual_volatility, sharpe_ratio, fig_simulation
|
| 129 |
+
])
|
| 130 |
+
|
| 131 |
+
app.css = """
|
| 132 |
+
#highlighted-button {
|
| 133 |
+
background-color: yellow !important;
|
| 134 |
+
font-weight: bold;
|
| 135 |
+
border: 2px solid black;
|
| 136 |
+
padding: 10px;
|
| 137 |
+
font-size: 16px;
|
| 138 |
+
cursor: pointer;
|
| 139 |
+
}
|
| 140 |
+
"""
|
| 141 |
+
|
| 142 |
+
app.launch()
|