import streamlit as st from pandas_datareader.data import DataReader from pypfopt.discrete_allocation import DiscreteAllocation, get_latest_prices from pypfopt import EfficientFrontier from pypfopt import risk_models from pypfopt import expected_returns from pypfopt import plotting import copy import numpy as np import pandas as pd import plotly.express as px import matplotlib.pyplot as plt import seaborn as sns from datetime import datetime from io import BytesIO def plot_cum_returns(data, title): daily_cum_returns = 1 + data.dropna().pct_change() daily_cum_returns = daily_cum_returns.cumprod()*100 fig = px.line(daily_cum_returns, title=title) return fig def plot_efficient_frontier_and_max_sharpe(mu, S): # Optimize portfolio for max Sharpe ratio and plot it out with efficient frontier curve ef = EfficientFrontier(mu, S) fig, ax = plt.subplots(figsize=(6,4)) ef_max_sharpe = copy.deepcopy(ef) plotting.plot_efficient_frontier(ef, ax=ax, show_assets=False) # Find the max sharpe portfolio ef_max_sharpe.max_sharpe(risk_free_rate=0.02) ret_tangent, std_tangent, _ = ef_max_sharpe.portfolio_performance() ax.scatter(std_tangent, ret_tangent, marker="*", s=100, c="r", label="Max Sharpe") # Generate random portfolios n_samples = 1000 w = np.random.dirichlet(np.ones(ef.n_assets), n_samples) rets = w.dot(ef.expected_returns) stds = np.sqrt(np.diag(w @ ef.cov_matrix @ w.T)) sharpes = rets / stds ax.scatter(stds, rets, marker=".", c=sharpes, cmap="viridis_r") # Output ax.legend() return fig st.set_page_config(page_title = "Bohmian's Stock Portfolio Optimizer", layout = "wide") st.header("Bohmian's Stock Portfolio Optimizer") col1, col2 = st.columns(2) with col1: start_date = st.date_input("Start Date",datetime(2013, 1, 1)) with col2: end_date = st.date_input("End Date") # it defaults to current date tickers_string = st.text_input('Enter all stock tickers to be included in portfolio separated by commas \ WITHOUT spaces, e.g. "MA,FB,V,AMZN,JPM,BA"', '').upper() tickers = tickers_string.split(',') try: # Get Stock Prices using pandas_datareader Library stocks_df = DataReader(tickers, 'yahoo', start = start_date, end = end_date)['Adj Close'] # Plot Individual Stock Prices fig_price = px.line(stocks_df, title='Price of Individual Stocks') # Plot Individual Cumulative Returns fig_cum_returns = plot_cum_returns(stocks_df, 'Cumulative Returns of Individual Stocks Starting with $100') # Calculatge and Plot Correlation Matrix between Stocks corr_df = stocks_df.corr().round(2) fig_corr = px.imshow(corr_df, text_auto=True, title = 'Correlation between Stocks') # Calculate expected returns and sample covariance matrix for portfolio optimization later mu = expected_returns.mean_historical_return(stocks_df) S = risk_models.sample_cov(stocks_df) # Plot efficient frontier curve fig = plot_efficient_frontier_and_max_sharpe(mu, S) fig_efficient_frontier = BytesIO() fig.savefig(fig_efficient_frontier, format="png") # Get optimized weights ef = EfficientFrontier(mu, S) ef.max_sharpe(risk_free_rate=0.02) weights = ef.clean_weights() expected_annual_return, annual_volatility, sharpe_ratio = ef.portfolio_performance() weights_df = pd.DataFrame.from_dict(weights, orient = 'index') weights_df.columns = ['weights'] # Calculate returns of portfolio with optimized weights stocks_df['Optimized Portfolio'] = 0 for ticker, weight in weights.items(): stocks_df['Optimized Portfolio'] += stocks_df[ticker]*weight # Plot Cumulative Returns of Optimized Portfolio fig_cum_returns_optimized = plot_cum_returns(stocks_df['Optimized Portfolio'], 'Cumulative Returns of Optimized Portfolio Starting with $100') # Display everything on Streamlit st.subheader("Your Portfolio Consists of {} Stocks".format(tickers_string)) st.plotly_chart(fig_cum_returns_optimized) st.subheader("Optimized Max Sharpe Portfolio Weights") st.dataframe(weights_df) st.subheader("Optimized Max Sharpe Portfolio Performance") st.image(fig_efficient_frontier) st.subheader('Expected annual return: {}%'.format((expected_annual_return*100).round(2))) st.subheader('Annual volatility: {}%'.format((annual_volatility*100).round(2))) st.subheader('Sharpe Ratio: {}'.format(sharpe_ratio.round(2))) st.plotly_chart(fig_corr) # fig_corr is not a plotly chart st.plotly_chart(fig_price) st.plotly_chart(fig_cum_returns) except: st.write('Enter correct stock tickers to be included in portfolio separated\ by commas WITHOUT spaces, e.g. "MA,FB,V,AMZN,JPM,BA"and hit Enter.') hide_streamlit_style = """ """ st.markdown(hide_streamlit_style, unsafe_allow_html=True)