Tangency Portfolio Analysis

Tue, 18 Mar 2025 20:03:15 GMT
User

use yfinance 0.2.54 to get monthly closing prices for SPY, IEF, GLD, IYR, and UUP beginning in 2010. Compute returns as percent changes. Keep only months for which all returns are available. Compute the means and the covariance matrix. Assume the risk-free rate is 0.04/12. Compute the tangency portfolio.

Tue, 18 Mar 2025 19:43:21 GMT
J
Julius

I'll help you analyze these ETFs and compute the tangency portfolio. Let's break this down into steps:

  1. First, let's get the data and compute returns
  2. Then calculate means and covariance matrix
  3. Finally compute the tangency portfolio weights

Python

This code snippet installs the yfinance package version 0.2.54 using pip.
{
…
}1 Item
Possibly trying to access a column that doesn't exist in the data.

Python

This code snippet downloads monthly adjusted closing prices for selected financial instruments, calculates their returns, and computes the mean returns and covariance matrix.
{
…
}2 Items
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File /opt/conda/lib/python3.11/site-packages/pandas/core/indexes/base.py:3805, in Index.get_loc(self, key)
   3804 try:
-> 3805     return self._engine.get_loc(casted_key)
   3806 except KeyError as err:

File index.pyx:167, in pandas._libs.index.IndexEngine.get_loc()

File index.pyx:196, in pandas._libs.index.IndexEngine.get_loc()

File pandas/_libs/hashtable_class_helper.pxi:7081, in pandas._libs.hashtable.PyObjectHashTable.get_item()

File pandas/_libs/hashtable_class_helper.pxi:7089, in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'Adj Close'

The above exception was the direct cause of the following exception:

KeyError                                  Traceback (most recent call last)
Cell In[5], line 15
     13 data = pd.DataFrame()
     14 for ticker in tickers:
---> 15     temp = yf.download(ticker, start=start_date, end=end_date, interval='1mo')['Adj Close']
     16     data[ticker] = temp
     18 # Calculate returns

File /opt/conda/lib/python3.11/site-packages/pandas/core/frame.py:4101, in DataFrame.__getitem__(self, key)
   4099 if is_single_key:
   4100     if self.columns.nlevels > 1:
-> 4101         return self._getitem_multilevel(key)
   4102     indexer = self.columns.get_loc(key)
   4103     if is_integer(indexer):

File /opt/conda/lib/python3.11/site-packages/pandas/core/frame.py:4159, in DataFrame._getitem_multilevel(self, key)
   4157 def _getitem_multilevel(self, key):
   4158     # self.columns is a MultiIndex
-> 4159     loc = self.columns.get_loc(key)
   4160     if isinstance(loc, (slice, np.ndarray)):
   4161         new_columns = self.columns[loc]

File /opt/conda/lib/python3.11/site-packages/pandas/core/indexes/multi.py:3040, in MultiIndex.get_loc(self, key)
   3037     return mask
   3039 if not isinstance(key, tuple):
-> 3040     loc = self._get_level_indexer(key, level=0)
   3041     return _maybe_to_slice(loc)
   3043 keylen = len(key)

File /opt/conda/lib/python3.11/site-packages/pandas/core/indexes/multi.py:3391, in MultiIndex._get_level_indexer(self, key, level, indexer)
   3388         return slice(i, j, step)
   3390 else:
-> 3391     idx = self._get_loc_single_level_index(level_index, key)
   3393     if level > 0 or self._lexsort_depth == 0:
   3394         # Desired level is not sorted
   3395         if isinstance(idx, slice):
   3396             # test_get_loc_partial_timestamp_multiindex

File /opt/conda/lib/python3.11/site-packages/pandas/core/indexes/multi.py:2980, in MultiIndex._get_loc_single_level_index(self, level_index, key)
   2978     return -1
   2979 else:
-> 2980     return level_index.get_loc(key)

File /opt/conda/lib/python3.11/site-packages/pandas/core/indexes/base.py:3812, in Index.get_loc(self, key)
   3807     if isinstance(casted_key, slice) or (
   3808         isinstance(casted_key, abc.Iterable)
   3809         and any(isinstance(x, slice) for x in casted_key)
   3810     ):
   3811         raise InvalidIndexError(key)
-> 3812     raise KeyError(key) from err
   3813 except TypeError:
   3814     # If we have a listlike key, _check_indexing_error will raise
   3815     #  InvalidIndexError. Otherwise we fall through and re-raise
   3816     #  the TypeError.
   3817     self._check_indexing_error(key)

KeyError: 'Adj Close'
Also, evaluate what went wrong and consider options for how to fix it. Break the execution up into cell blocks.

Let me fix the code by adjusting the data download approach:

Python

This code downloads monthly closing prices for selected financial instruments, computes their returns, and calculates the mean returns and covariance matrix.
{
…
}1 Item

Python

This code snippet calculates the weights of the tangency portfolio based on excess returns and the covariance matrix, and then computes the expected return, volatility, and Sharpe ratio of the portfolio.
{
…
}1 Item

Based on the analysis, here are the results:

The tangency portfolio weights for the assets are:

  • SPY: 115.22%
  • IEF: -46.20%
  • GLD: 35.13%
  • IYR: -37.29%
  • UUP: 33.13%

The portfolio statistics on a monthly basis are:

  • Expected Return: 1.21%
  • Volatility: 3.91%
  • Monthly Sharpe Ratio: 0.23

The weights sum to 100% but involve both long and short positions. The portfolio takes substantial long positions in SPY (115.22%) while shorting IEF and IYR. The portfolio also takes moderate long positions in GLD and UUP. This allocation achieves a monthly expected return of 1.21% with a volatility of 3.91%, resulting in a monthly Sharpe ratio of 0.23 given the assumed monthly risk-free rate of 0.33% (4% annual / 12).

Tue, 18 Mar 2025 19:44:38 GMT
User

create a streamlit app that allows the user to input the number of tickers, the specific tickers, and the monthly risk-free rate. Use the above code but use a start date of 1970-01-01 when getting closing prices from yahoo. Output the tangency portfolio, its expected return, and its standard deviation.

Tue, 18 Mar 2025 20:01:24 GMT
J
Julius

Python

This code snippet installs the Streamlit library using pip.
{
…
}1 Item
Possibly the 'yfinance' module is not installed in your environment.

Python

This code snippet generates a Streamlit application for calculating the tangency portfolio weights and statistics based on user-defined stock tickers and a risk-free rate.
{
…
}1 Item
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[5], line 3
      1 # Create streamlit app
      2 import streamlit as st
----> 3 import yfinance as yf
      4 import pandas as pd
      5 import numpy as np

ModuleNotFoundError: No module named 'yfinance'
Also, evaluate what went wrong and consider options for how to fix it. Break the execution up into cell blocks.

Python

This code snippet installs the yfinance library version 0.2.54 using pip.
{
…
}1 Item

Python

This code snippet generates a Streamlit application for calculating the tangency portfolio weights and statistics based on user-provided stock tickers and a risk-free rate.
{
…
}1 Item

I've created a Streamlit app that calculates the tangency portfolio based on user inputs. The app has been saved as 'tangency_portfolio_app.py' and can be downloaded from https://julius.ai/files/tangency_portfolio_app.py

The app allows users to:

  • Input the number of tickers they want to analyze
  • Enter the specific ticker symbols
  • Set the monthly risk-free rate

The app will then:

  • Download monthly closing prices from 1970 onwards
  • Calculate returns and the covariance matrix
  • Compute the tangency portfolio weights
  • Display the expected return and standard deviation

To run the app locally, you'll need to:

  1. Download the app file from the link above
  2. Make sure you have streamlit and yfinance installed
  3. Run the command: streamlit run tangency_portfolio_app.py

The app includes error handling and will display appropriate messages if there are any issues with the data or calculations.

Tue, 18 Mar 2025 20:03:20 GMT