Robert Castagna commited on
Commit
f2b8e49
·
1 Parent(s): 38dbfc2

add search to portfolio optimizer

Browse files
pages/1_Fundamentals.py CHANGED
@@ -98,15 +98,21 @@ def get_list_of_tickers():
98
 
99
  # ---------------------------------------------------------------------------------------------- #
100
 
 
 
 
 
 
101
 
102
  symbols = []
103
 
104
  list_of_tickers = get_list_of_tickers()
105
 
106
  with st.form(key="selecting columns"):
107
- symbols = st.multiselect(label='Enter Tickers Here. Cannot check metrics for Funds.', options=list_of_tickers, placeholder='MSFT, AAPL, ...')
108
  strategy_selection = st.radio("Select Strategy", ('Value', 'Growth','Bypass'), horizontal=True)
109
  submit_button = st.form_submit_button(label='Compute Metrics')
 
110
 
111
  if submit_button and symbols and strategy_selection == 'Value':
112
  gains_data = {}
 
98
 
99
  # ---------------------------------------------------------------------------------------------- #
100
 
101
+ if 'tickers' not in st.session_state:
102
+ tickers = [
103
+ "AAPL", "MSFT", "GOOG", "NVDA", "TSLA",
104
+ ]
105
+ st.session_state['tickers'] = tickers
106
 
107
  symbols = []
108
 
109
  list_of_tickers = get_list_of_tickers()
110
 
111
  with st.form(key="selecting columns"):
112
+ symbols = st.multiselect(label='Enter Tickers Here. Cannot check metrics for Funds.', default=st.session_state['tickers'], options=list_of_tickers, placeholder='MSFT, AAPL, ...')
113
  strategy_selection = st.radio("Select Strategy", ('Value', 'Growth','Bypass'), horizontal=True)
114
  submit_button = st.form_submit_button(label='Compute Metrics')
115
+ st.session_state['tickers'] = symbols
116
 
117
  if submit_button and symbols and strategy_selection == 'Value':
118
  gains_data = {}
pages/2_Portfolio_Builder.py CHANGED
@@ -17,6 +17,7 @@ import datetime
17
  import asyncio
18
  import nest_asyncio
19
 
 
20
  def open_nested_event_loop():
21
  # Check if there is an existing event loop, if not, create a new one
22
  nest_asyncio.apply()
@@ -35,6 +36,16 @@ def get_positions(account):
35
  tickers.append([position.contract.symbol, position.position, position.marketPrice])
36
  return tickers
37
 
 
 
 
 
 
 
 
 
 
 
38
 
39
  load_dotenv()
40
 
@@ -75,157 +86,167 @@ elif re.search('AuthenticAMD', platform.processor()): # use live ibkr portfolio
75
  st.stop()
76
 
77
 
 
78
 
79
- st.write(tickers)
80
-
81
- # define range as today - 365 days to today
82
- start_date = (datetime.datetime.now() - datetime.timedelta(days=365)).strftime('%Y-%m-%d')
83
- end_date = datetime.datetime.now().strftime('%Y-%m-%d')
84
-
85
- data = (
86
- obb
87
- .equity
88
- .price
89
- .historical(tickers, start_date=start_date, end_date=end_date, provider="yfinance")
90
- .to_df()
91
- .pivot(columns="symbol", values="close")
92
- )
93
-
94
- returns = data.pct_change().dropna()
95
-
96
- # -------------------------- (Efficient Frontier Calculation) -------------------------------- #
97
- st.title('Efficient Frontier Portfolio')
98
- st.write("The efficient frontier is a set of optimal portfolios that offer the highest expected return for a defined level of risk or the lowest risk for a given level of expected return. Portfolios that lie below the efficient frontier are sub-optimal because they do not provide enough return for the level of risk. Portfolios that cluster to the right of the efficient frontier are also sub-optimal because they have a higher level of risk for the defined rate of return.")
99
-
100
- port = rp.Portfolio(returns=returns)
101
-
102
- # Step 2: Set portfolio optimization model
103
- port.assets_stats(model='hist') # Using historical data for estimation
104
-
105
- # Step 3: Configure the optimization model and calculate the efficient frontier
106
- ef = port.efficient_frontier(model='Classic', rm='MV', points=50, rf=0.0406, hist=True)
107
- w1 = port.optimization(model='Classic', rm='MV', obj='Sharpe', rf=0.0, l=0, hist=True)
108
-
109
- mu = port.mu # Expected returns
110
- cov = port.cov # Covariance matrix
111
-
112
-
113
-
114
- # ---------------------------- (Portfolio Statistics) -------------------------------- #
115
 
116
- st.write('**Portfolio Statistics Optimized for Max Sharpe Ratio:**')
117
- spy_prices = obb.equity.price.historical(symbol = "spy", provider="yfinance", start_date=start_date, end_date=end_date).to_df()
 
 
118
 
119
- # Calculate daily returns
120
- # Ensure you're using the adjusted close prices for accurate return calculation
121
- benchmark_returns = spy_prices['close'].pct_change().dropna()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
- port.rf = 0.0406 # Risk-free rate
124
- portfolio_return = np.dot(w1, mu)
125
-
126
- # market return
127
- spy_daily_return = benchmark_returns
128
- spy_expected_return = spy_daily_return.mean()
129
-
130
- # portfolio's beta
131
- covariance = returns.apply(lambda x: x.cov(spy_daily_return))
132
- spy_variance = spy_daily_return.var()
133
- beta_values = covariance / spy_variance
134
- portfolio_beta = np.dot(w1['weights'], beta_values)
135
- st.write('Portfolio Beta: ', np.round(portfolio_beta,3))
136
-
137
- # jensens alpha
138
- expected_return = port.rf + portfolio_beta * (spy_daily_return - port.rf)
139
- st.write('Jensen\'s Alpha: ', np.round(expected_return.iloc[-1],3))
140
-
141
- # treynor ratio
142
- treynor_ratio = (expected_return - port.rf) / portfolio_beta
143
- st.write('Treynor Ratio: ', np.round(treynor_ratio.iloc[-1],3))
144
-
145
- # Portfolio volatility
146
- portfolio_stddev = np.sqrt(np.dot(pd.Series(w1['weights']).T, np.dot(covariance, w1['weights'])))
147
-
148
- # Sharpe Ratio, adjusted for the risk-free rate
149
- sharpe_ratio = (expected_return.iloc[-1] - port.rf) / np.mean(portfolio_stddev[portfolio_stddev != 0])
150
- st.write('Sharpe Ratio: ', np.round(sharpe_ratio, 3))
151
-
152
- # -------------------------- (Plotting) -------------------------------- #
153
- # Step 4: Plot the efficient frontier
154
- fig_ef, ax_ef = plt.subplots()
155
- ax_ef = rp.plot_frontier(mu=mu, cov=cov, returns=port.returns, w=w1, rm='MV', w_frontier=ef, marker='*', label='Optimal Portfolio - Max. Sharpe' ,s=16)
156
- st.pyplot(fig_ef)
157
-
158
- st.write('**Asset Mix of Optimized Portfolio:**')
159
- st.dataframe(w1.T, use_container_width=True)
160
-
161
- # corr matrix
162
- fig, ax = plt.subplots()
163
- corr = returns.corr()
164
-
165
- # Create a heatmap
166
- heatmap = go.Heatmap(z=corr.values, x=corr.columns, y=corr.index, colorscale='RdYlBu')
167
- layout = go.Layout(title='Correlation Matrix', autosize=True)
168
- fig = go.Figure(data=[heatmap], layout=layout)
169
-
170
- st.plotly_chart(fig)
171
-
172
-
173
-
174
- # -------------------------- (HRP Portfolio) -------------------------------- #
175
- st.title('Hierarchical Risk Parity Portfolio')
176
- st.write("""
177
- HRP is unlike traditional portfolio optimization methods. It can create an optimized portfolio when the covariance matrix is ill-degenerated or singular. This is impossible for quadratic optimizers.
178
- Research has shown HRP to deliver lower out-of-sample variance than traditional optimization methods.
179
- """)
180
-
181
- fig1, ax1 = plt.subplots()
182
- ax1 = rp.plot_clusters(returns=returns,
183
- codependence='pearson',
184
- linkage='single',
185
- k=None,
186
- max_k=10,
187
- leaf_order=True,
188
- dendrogram=True,
189
- ax=None)
190
-
191
- st.pyplot(fig1)
192
-
193
- port = rp.HCPortfolio(returns=returns)
194
- w = port.optimization(
195
- model="HRP",
196
- codependence="pearson",
197
- rm="MV", # minimum variance
198
- rf=0.05,
199
- linkage="single",
200
- max_k=10,
201
- leaf_order=True,
202
- )
203
-
204
- fig2, ax2 = plt.subplots()
205
- ax2 = rp.plot_pie(
206
- w=w,
207
- title="HRP Naive Risk Parity",
208
- others=0.05,
209
- nrow=25,
210
- cmap="tab20",
211
- height=8,
212
- width=10,
213
- ax=None,
214
- )
215
- st.pyplot(fig2)
216
-
217
- fig3, ax3 = plt.subplots()
218
- ax3 = rp.plot_risk_con(
219
- w=w,
220
- cov=returns.cov(),
221
- returns=returns,
222
- rm="MV",
223
- rf=0,
224
- alpha=0.05,
225
- color="tab:blue",
226
- height=6,
227
- width=10,
228
- t_factor=252,
229
- ax=None,
230
- )
231
- st.pyplot(fig3)
 
17
  import asyncio
18
  import nest_asyncio
19
 
20
+
21
  def open_nested_event_loop():
22
  # Check if there is an existing event loop, if not, create a new one
23
  nest_asyncio.apply()
 
36
  tickers.append([position.contract.symbol, position.position, position.marketPrice])
37
  return tickers
38
 
39
+ @st.cache_data
40
+ def get_list_of_tickers():
41
+ comp_info = get_finnhub_data('/stock/symbol?exchange=US')
42
+ list_of_tickers = []
43
+ for i in range(len(comp_info)-1):
44
+ for key in comp_info[i].keys():
45
+ if key == 'symbol':
46
+ list_of_tickers.append(comp_info[i]['symbol'])
47
+ return list_of_tickers
48
+
49
 
50
  load_dotenv()
51
 
 
86
  st.stop()
87
 
88
 
89
+ list_of_tickers = get_list_of_tickers()
90
 
91
+ with st.form(key="selecting columns"):
92
+ tickers = st.multiselect(label='Enter Tickers Here. ', options=list_of_tickers, placeholder='...', default=st.session_state['tickers'])
93
+ submit_button = st.form_submit_button(label='Optimize Portfolio')
94
+ st.session_state['tickers'] = tickers
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
+ if submit_button:
97
+ # define range as today - 365 days to today
98
+ start_date = (datetime.datetime.now() - datetime.timedelta(days=365)).strftime('%Y-%m-%d')
99
+ end_date = datetime.datetime.now().strftime('%Y-%m-%d')
100
 
101
+ data = (
102
+ obb
103
+ .equity
104
+ .price
105
+ .historical(tickers, start_date=start_date, end_date=end_date, provider="yfinance")
106
+ .to_df()
107
+ .pivot(columns="symbol", values="close")
108
+ )
109
+
110
+ returns = data.pct_change().dropna()
111
+
112
+ # -------------------------- (Efficient Frontier Calculation) -------------------------------- #
113
+ st.title('Efficient Frontier Portfolio')
114
+ st.write("The efficient frontier is a set of optimal portfolios that offer the highest expected return for a defined level of risk or the lowest risk for a given level of expected return. Portfolios that lie below the efficient frontier are sub-optimal because they do not provide enough return for the level of risk. Portfolios that cluster to the right of the efficient frontier are also sub-optimal because they have a higher level of risk for the defined rate of return.")
115
+
116
+ port = rp.Portfolio(returns=returns)
117
+
118
+ # Step 2: Set portfolio optimization model
119
+ port.assets_stats(model='hist') # Using historical data for estimation
120
+ # Step 3: Configure the optimization model and calculate the efficient frontier
121
+ ef = port.efficient_frontier(model='Classic', rm='MSV', points=50, rf=0.0406, hist=True)
122
+ w1 = port.optimization(model='Classic', rm='MSV', obj='Sharpe', rf=0.0, hist=True)
123
+
124
+ mu = port.mu # Expected returns
125
+ cov = port.cov # Covariance matrix
126
+
127
+
128
+ # ---------------------------- (Portfolio Statistics) -------------------------------- #
129
+
130
+ st.write('**Portfolio Statistics Optimized for Max Sharpe Ratio:**')
131
+ spy_prices = obb.equity.price.historical(symbol = "spy", provider="yfinance", start_date=start_date, end_date=end_date).to_df()
132
+
133
+ # Calculate daily returns
134
+ # Ensure you're using the adjusted close prices for accurate return calculation
135
+ benchmark_returns = spy_prices['close'].pct_change().dropna()
136
+
137
+ port.rf = 0.0406 # Risk-free rate
138
+ portfolio_return = np.dot(w1, mu)
139
+
140
+ # market return
141
+ spy_daily_return = benchmark_returns
142
+ spy_expected_return = spy_daily_return.mean()
143
+
144
+ # portfolio's beta
145
+ covariance = returns.apply(lambda x: x.cov(spy_daily_return))
146
+ spy_variance = spy_daily_return.var()
147
+ beta_values = covariance / spy_variance
148
+ portfolio_beta = np.dot(w1['weights'], beta_values)
149
+ st.write('Portfolio Beta: ', np.round(portfolio_beta,3))
150
+
151
+ # jensens alpha
152
+ expected_return = port.rf + portfolio_beta * (spy_daily_return - port.rf)
153
+ st.write('Jensen\'s Alpha: ', np.round(expected_return.iloc[-1],3))
154
+
155
+ # treynor ratio
156
+ treynor_ratio = (expected_return - port.rf) / portfolio_beta
157
+ st.write('Treynor Ratio: ', np.round(treynor_ratio.iloc[-1],3))
158
+
159
+ # Portfolio volatility
160
+ portfolio_stddev = np.sqrt(np.dot(pd.Series(w1['weights']).T, np.dot(covariance, w1['weights'])))
161
+
162
+ # Sharpe Ratio, adjusted for the risk-free rate
163
+ sharpe_ratio = rp.RiskFunctions.Sharpe(
164
+ mu=mu,
165
+ cov=cov,
166
+ returns=returns,
167
+ rf=port.rf,
168
+ w=w1,
169
+ )
170
+ st.write('Sharpe Ratio: ', np.round(sharpe_ratio, 3))
171
+
172
+ # -------------------------- (Plotting) -------------------------------- #
173
+ # Step 4: Plot the efficient frontier
174
+ fig_ef, ax_ef = plt.subplots()
175
+ ax_ef = rp.plot_frontier(mu=mu, cov=cov, returns=port.returns, w=w1, rm='MV', w_frontier=ef, marker='*', label='Optimal Portfolio - Max. Sharpe' ,s=16)
176
+ st.pyplot(fig_ef)
177
+
178
+ st.write('**Asset Mix of Optimized Portfolio:**')
179
+ st.dataframe(w1.T, use_container_width=True)
180
+
181
+ # corr matrix
182
+ fig, ax = plt.subplots()
183
+ corr = returns.corr()
184
+
185
+ # Create a heatmap
186
+ heatmap = go.Heatmap(z=corr.values, x=corr.columns, y=corr.index, colorscale='RdYlBu')
187
+ layout = go.Layout(title='Correlation Matrix', autosize=True)
188
+ fig = go.Figure(data=[heatmap], layout=layout)
189
+
190
+ st.plotly_chart(fig)
191
+
192
+
193
+
194
+ # -------------------------- (HRP Portfolio) -------------------------------- #
195
+ st.title('Hierarchical Risk Parity Portfolio')
196
+ st.write("""
197
+ HRP is unlike traditional portfolio optimization methods. It can create an optimized portfolio when the covariance matrix is ill-degenerated or singular. This is impossible for quadratic optimizers.
198
+ Research has shown HRP to deliver lower out-of-sample variance than traditional optimization methods.
199
+ """)
200
+
201
+ fig1, ax1 = plt.subplots()
202
+ ax1 = rp.plot_clusters(returns=returns,
203
+ codependence='pearson',
204
+ linkage='single',
205
+ k=None,
206
+ max_k=10,
207
+ leaf_order=True,
208
+ dendrogram=True,
209
+ ax=None)
210
+
211
+ st.pyplot(fig1)
212
+
213
+ port = rp.HCPortfolio(returns=returns)
214
+ w = port.optimization(
215
+ model="HRP",
216
+ codependence="pearson",
217
+ rm="MV", # minimum variance
218
+ rf=0.05,
219
+ linkage="single",
220
+ max_k=10,
221
+ leaf_order=True,
222
+ )
223
+
224
+ fig2, ax2 = plt.subplots()
225
+ ax2 = rp.plot_pie(
226
+ w=w,
227
+ title="HRP Naive Risk Parity",
228
+ others=0.05,
229
+ nrow=25,
230
+ cmap="tab20",
231
+ height=8,
232
+ width=10,
233
+ ax=None,
234
+ )
235
+ st.pyplot(fig2)
236
+
237
+ fig3, ax3 = plt.subplots()
238
+ ax3 = rp.plot_risk_con(
239
+ w=w,
240
+ cov=returns.cov(),
241
+ returns=returns,
242
+ rm="MV",
243
+ rf=0,
244
+ alpha=0.05,
245
+ color="tab:blue",
246
+ height=6,
247
+ width=10,
248
+ t_factor=252,
249
+ ax=None,
250
+ )
251
+ st.pyplot(fig3)
252