import streamlit as st from streamlit_lottie import st_lottie import pandas as pd import plotly.express as px import numpy as np from scipy.optimize import minimize from fredapi import Fred import warnings import time import json import plotly.graph_objects as go warnings.filterwarnings('ignore') np.set_printoptions(suppress=True) from PortfolioHelperFuncs import navigationBar from PortfolioHelperFuncs import commodities from PortfolioHelperFuncs import Data from PortfolioHelperFuncs import visualize from PortfolioHelperFuncs import retrieve_tickers from PortfolioHelperFuncs import KeyMetrics from PortfolioHelperFuncs import plot_optimal_weights st.set_page_config(layout="wide", page_title="Portfolio Optimization For Commodities", page_icon="Portfolio_Icon.jpg") if "dtVariable" not in st.session_state: st.session_state.dtVariable = Data(collectedData=pd.DataFrame(), commTickers={}, years=5, upperBoundLimit=0.5) if 'loadOnce' not in st.session_state: st.session_state.loadOnce = True if st.session_state.loadOnce: with open(file='Portfolio_Anim.json', mode = "r") as f: anim = json.load(f) with st.lottie_spinner(anim, height = 700): time.sleep(2) st.session_state.loadOnce = False #------------------------------------------------------------------------------ Navigation Bar selected_option = navigationBar() if selected_option == 'Sharpe Ratio Technique': _, c, _ = st.columns((3,5,3)) c.title('Portfolio Optimization with Commodities') with c.form(key='Parameters Button'): selected_commodities = st.multiselect('Select Commodities', list(commodities.keys()), default=list()) years_of_data = st.slider('Select Number of Years of Data', 1, 10, 5) upper_bound_limit = st.slider('Set Upper Bound Limit for Asset Allocation', 0.0, 1.0, 0.3) risk_free_rate_source = st.radio('Select Risk-Free Rate Source', ['Manual', 'FRED API']) disabledTF = True if risk_free_rate_source == "FRED API" else False risk_free_rate_manual = st.number_input('Enter Risk-Free Rate (%)', min_value=0.0, max_value=100.0, value=3.0,) log_normal_returns = st.checkbox('Use Log Normal Returns (Will use Simple Returns if not selected)', value=False) submitted = st.form_submit_button("Get me my Optimal Investment Strategy") if submitted: st.write('
', unsafe_allow_html=True) st.divider() st.write('
', unsafe_allow_html=True) st.session_state.dtVariable.collectedData = pd.DataFrame() st.session_state.dtVariable.commTickers = {k: commodities[k] for k in selected_commodities} st.session_state.dtVariable.years = years_of_data st.session_state.dtVariable.upperBoundLimit = upper_bound_limit retrieve_tickers() st.session_state.dtVariable.collectedData = st.session_state.dtVariable.collectedData[selected_commodities] f = visualize(st.session_state.dtVariable.collectedData, 'Historical Prices', {'value': 'Price', 'variable': 'Commodity'}, 'Date', 'Price', 'Commodities') _, cnt, _ = st.columns((2,5,2)) cnt.plotly_chart(f, use_container_width= True) if risk_free_rate_source == 'FRED API': f = Fred(api_key='385c2ddfc6d3c369a67ad83c63f6fd7d') treasuryRate = f.get_series_latest_release('GS10') risk_free_rate = treasuryRate.iloc[-1] / 100 else: risk_free_rate = risk_free_rate_manual / 100 constraints = {'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1} bounds = [(0, st.session_state.dtVariable.upperBoundLimit) for _ in range(len(st.session_state.dtVariable.collectedData.columns))] metrics = KeyMetrics(data=st.session_state.dtVariable.collectedData, logNormal=log_normal_returns) metrics.calculate_daily_returns() metrics.calculate_covariance() def objective(weights): metrics.initialWeights = weights metrics.calculate_standard_deviation() metrics.calculate_expected_return() metrics.calculate_sharpe_ratio(riskFreeRate=risk_free_rate) return -metrics.sharpeRatio result = minimize(fun=objective, x0=metrics.initialWeights, constraints=constraints, bounds=bounds, method='SLSQP') if result.success: optimal_weights = result.x optimal_weights = np.round(optimal_weights, 3) if np.isclose(np.sum(optimal_weights), 1): metrics.initialWeights = optimal_weights metrics.calculate_covariance() metrics.calculate_standard_deviation() metrics.calculate_expected_return() metrics.calculate_sharpe_ratio(riskFreeRate=risk_free_rate) # optimal_weights_df = pd.DataFrame(data=optimal_weights.reshape(-1, 1), index=st.session_state.dtVariable.collectedData.columns) optimal_weights_df = pd.DataFrame({ 'Tickers': np.array(st.session_state.dtVariable.collectedData.columns), 'Weights': optimal_weights }) st.write('
', unsafe_allow_html=True) st.divider() st.write('
', unsafe_allow_html=True) riskCol, sharpeRatioCol, returnsCol = st.columns((2,2,2)) riskCol.header("Investment Risk Associated with your Portfolio and the Weights Allocation") fig_gauge = go.Figure(go.Indicator( mode="gauge+number", value=metrics.standardDeviation * 100, title={'text': ""}, gauge={'axis': {'range': [0, 100]}} )) riskCol.plotly_chart(fig_gauge) # Metric Card sharpeRatioCol.header("Sharpe Ratio") sharpeRatioCol.metric(label="", value=np.round(metrics.sharpeRatio,4)) returnsCol.header("Expected Returns on your Portfolio & its Optimal Weights") fig_gauge = go.Figure(go.Indicator( mode="gauge+number", value=metrics.expectedReturns * 100, title={'text': ""}, gauge={'axis': {'range': [0, 100]}} )) returnsCol.plotly_chart(fig_gauge) st.write('
', unsafe_allow_html=True) st.divider() st.write('
', unsafe_allow_html=True) _, weightsDepictionColumns1, _, weightsDepictionColumns2, _ = st.columns((1,4,1,2,1)) weightsDepictionColumns1.plotly_chart(plot_optimal_weights(optimal_weights_df), use_container_width=True) weightsDepictionColumns2.header('Weights Allocation Pie Chart') fig_pie = px.pie(optimal_weights_df, values='Weights', names='Tickers', title='', height=400) weightsDepictionColumns2.plotly_chart(fig_pie) else: cnt.error("The sum of the optimal weights is not equal to 1.") else: cnt.error(f"Optimization failed: {result.message}") cnt.write("Try Different Combinations, add more tickers or adjusting Upper Bound Values or Something other")