<jupyter_start><jupyter_text>Homework 1 - Group 20   <NAME>, <NAME>, <NAME>, <NAME>, <NAME><jupyter_code>import numpy as np
import pandas as pd

import matplotlib.pyplot as plt
import seaborn as sns

import statsmodels.api as sm
from sklearn.linear_model import LinearRegression<jupyter_output><empty_output><jupyter_text>Part 2.  Mean-Variance Optimization First, we import and print the data for total and excess returns.<jupyter_code># Total returns

path_to_data_file = 'multi_asset_etf_data.xlsx'
df_total = pd.read_excel(path_to_data_file, sheet_name='total returns')
df_total.set_index('Date',inplace=True) 
df_total.head()
# Excess returns

df_excess = pd.read_excel(path_to_data_file, sheet_name='excess returns')
df_excess.set_index('Date',inplace=True)
df_excess.head()
# Descriptions 

info = pd.read_excel(path_to_data_file, sheet_name='descriptions')
info.rename(columns={'Unnamed: 0':'Symbol'},inplace=True)
info.set_index('Symbol',inplace=True)
info<jupyter_output><empty_output><jupyter_text>1. Summary Statistics  (a) Calculate and display the mean and volatility of each asset’s excess return. (Recall we use volatility to refer to standard deviation.)<jupyter_code># Mean of each asset's excess return

mu = df_excess.mean()*12 # We use the built-in pandas function to compute the mean, 
                         ## and annualize it by scaling it by the factor of 12
mu
# Volatility of each asset's excess return

vol = df_excess.std()*np.sqrt(12) # Similarly to the previous code, we use the built-in function and 
                                  ## scale the result by the factor of square root of 12
vol<jupyter_output><empty_output><jupyter_text>(b) Which assets have the best and worst Sharpe ratios? We use the following code to find the Sharpe ratio of each asset, then we sort the resulting values in the descending order for easier interpretation of the data. As you can see from the table, the asset that has the best Sharpe ratio is SPY (Sharpe Ratio is approximately 1.18), and the asset that has the worst Sharpe ratio is DBC (Sharpe Ratio is approximately 0.06).<jupyter_code>Sharpe = df_excess.mean()/df_excess.std() # Calculates Sharpe ratio
Sharpe = Sharpe*np.sqrt(12) # Annualizes sharpe ratio
Sharpe.sort_values(ascending=False) # Sorts sharpe ratios in the descending order<jupyter_output><empty_output><jupyter_text>Below we collect all the information derived in this question in one table<jupyter_code>table1 = pd.DataFrame({'Mean':mu, 'Vol':vol, 'Sharpe':Sharpe})
table1<jupyter_output><empty_output><jupyter_text>2. Descriptive Analysis  (a) Calculate the correlation matrix of the returns. Which pair has the highest correlation? And the lowest?<jupyter_code># This code calculates the correlation matrix of the returns

corrmat = df_excess.corr() # computes the correlation matrix
corrmat[corrmat==1] = None  # ignores self correlation
sns.heatmap(corrmat) # draws the heatmap 

corrmat
# Next, we find the pairs with highest and lowest correlations by sorting values

corr_rank = corrmat.unstack().sort_values().dropna()
pair_max = corr_rank.index[-1]
pair_min = corr_rank.index[0]
#Highest correlation pair 

print(pair_max, 'have the highest correlation between two assets in our universe of securities.')
#Lowest correlation pair 

print(pair_min, 'have the lowest correlation between two assets in our universe of securities.')<jupyter_output>('SPY', 'IEF') have the lowest correlation between two assets in our universe of securities.<jupyter_text>(b) How well have TIPS done in our sample? Have they outperformed domestic bonds? Foreign bonds? Consider the following summary statistics for TIPS, domestic and foreign bonds (IEF and BWX respectively):<jupyter_code>table1.loc[["BWX", "TIP", "IEF"]]<jupyter_output><empty_output><jupyter_text>Observe that TIP's sharpe ratio (0.82) outperformed the sharpe ratios of domestic bonds (0.59) and foreign bonds (0.28). Based on the Sharpe ratio evaluation, TIPS have done well in comparisson to bonds.     (c) Based on the data, do TIPS seem to expand the investment opportunity set, implying that Harvard should consider them as a separate asset? Based on the heatmap from part a, TIPS have a relatively low correlation with most other assets from our sample. At the same time, TIPS have a high correlation with IEF. Since TIPS have a higher sharpe ratio than IEF, we recommend including TIPS in the portfolio under the class of domestic bonds. This will ensure portfolio diversification and will benefit the sharpe ratio.    3. The MV frontier<jupyter_code># First, we build a function to compute weights, mean, volatility and sharpe ratio for tangency portfolio

def TanZeroPort(covm, mu, scale = 1, ilabels = np.array(df_excess.columns)):
    scalar1 = np.dot(np.ones(len(covm)),np.linalg.inv(covm)) #compute part of the denominator of the wt formula
    scalar1 = np.dot(scalar1, np.array(mu)) #compute the denominator of the wt formula
    scalar2 = np.dot(np.linalg.inv(covm), np.array(mu)) #compute the second multiplier
    wt = (1/scalar1)*scalar2 #formula from lecture 1 slide 50
    pmean = wt @ mu #compute mean 
    pvol = np.sqrt(wt @ covm @ wt)*np.sqrt(scale) #compute volatility 
    pSharpe = pmean/pvol #compute sharpe ratio
    
    dic = {'Mean': pmean, 'Volatility': pvol, 'Sharpe': pSharpe}
    df = pd.DataFrame(data = wt, index = ilabels, columns = ['WeightsT'])
    
    return df, dic<jupyter_output><empty_output><jupyter_text>a) Compute and display the weights of the tangency portfolios: $w^{tan}$.<jupyter_code>#Use the function TanZeroPort from above 

covm = df_excess.cov().to_numpy()
muv = np.array(mu)
[wtf, dic] = TanZeroPort(covm, muv, 12)

wtf<jupyter_output><empty_output><jupyter_text>(b) Compute the mean, volatility, and Sharpe ratio for the tangency portfolio corresponding to $w^{tan}$.<jupyter_code># Once again, we use the function from above

print(dic)<jupyter_output>{'Mean': 0.23776716339549783, 'Volatility': 0.10480197847812846, 'Sharpe': 2.2687278126635597}<jupyter_text>4. The allocation<jupyter_code># Here we build a function to compute weights, mean, volatility and sharpe ratio for MV portfolio with 
## given target return

def MVPortER(targetreturn, covm, mu, scale = 1, ilabels = np.array(df_excess.columns)):
    scalar1 = np.dot(np.ones(len(covm)),np.linalg.inv(covm)) #compute part of the denominator of the wt formula
    scalar1 = np.dot(scalar1, np.array(mu)) #compute the denominator of the wt formula
    scalar2 = np.dot(np.linalg.inv(covm), np.array(mu)) #compute the second multiplier
    scalar3 = np.dot(np.transpose(mu),np.linalg.inv(covm)) #compute part of the denominator of the delta formula
    scalar3 = np.dot(scalar3, np.array(mu)) #compute the denominator of the delta formula
    
    wt = (1/scalar1)*scalar2 #formula from lecture 1 slide 50
    delta = (scalar1/scalar3)*targetreturn*12 #here annualize target return 
    wp = delta*wt

    pmean = wp @ mu #compute mean
    pvol = np.sqrt(wp @ covm @ wp)*np.sqrt(scale) #compute volatility 
    pSharpe = pmean/pvol #compute sharpe ratio

    dic = {'Mean': pmean, 'Volatility': pvol, 'Sharpe': pSharpe}
    df = pd.DataFrame(data = wp, index = ilabels, columns = ['WeightsMVER'])
    
    return df, dic<jupyter_output><empty_output><jupyter_text>(a) Compute and display the weights of MV portfolios with target returns of  $û = .01$.<jupyter_code>#Use the function MVPortER from above 

[wp, dicp] = MVPortER(0.01, covm, muv, 12)
wp<jupyter_output><empty_output><jupyter_text>(b) What is the mean, volatility, and Sharpe ratio for wp?<jupyter_code># Once again, we use the function from above

print(dicp)<jupyter_output>{'Mean': 0.12000000000000002, 'Volatility': 0.05289307925357347, 'Sharpe': 2.2687278126635593}<jupyter_text>(c) Discuss the allocation. In which assets is the portfolio most long? And short? Based on the weights in 4(a), portfolio is most long in IEF with a weight of 0.88, and is most short in QAI with a weight of -1.25.  (d) Does this line up with which assets have the strongest Sharpe ratios? Consider the Sharpe ratios of assets in the portfolio:<jupyter_code>Sharpe.sort_values(ascending=False)<jupyter_output><empty_output><jupyter_text>Note that the portfolio is most long in IEF, that has a Sharpe ratio of 0.59, which is relatively small when compared to other assets. The portfolio is most short in QAI with a sharpe ratio of 0.58, which is the fourth lowest Sharpe ratio. Therefore, it seems that the Sharpe ratio does not affect the weights assigned to the assets in the portfolio. At the same time, IEF is the least correlated with all of the other assets, and the portfolio is the second most long in SPY which has the highest sharpe ratio and is the most negatively correlated with the asset with highest weight. The portfolio is the second most short in IYR, which is the 3rd most correlated with IEF.  5. Simple portfolios  (a) Calculate the performance of the equally-weighted portfolio over the sample. Rescale the entire weighting vector to have target mean $\tilde{w} = .01$. Report its mean, volatility, and Sharpe ratio.<jupyter_code># This code constructs the equally-weighted portfolio

wew = (np.ones(11)*1/11) # creates an array of equal weights
meanep = wew@mu # finds the mean of the equally-weighted portfolio

wtar = wew*(0.01*12)/meanep # rescales the weights vector to get the target mean 

meanwpe = wtar @ mu # computes the new mean of the portfolio
volatilitywpe = np.sqrt(wtar @ covm @ wtar)*np.sqrt(12) # computes the volatility of the portfolio
SharpeTpwpe = meanwpe/volatilitywpe  # computes Sharpe ratio of the portfolio

print('Mean        :', meanwpe)
print('Volatility  :', volatilitywpe)
print('Sharpe Ratio:', SharpeTpwpe)<jupyter_output>Mean        : 0.12000000000000001
Volatility  : 0.14355613150835042
Sharpe Ratio: 0.8359099589767074<jupyter_text>(b) Calculate the performance of the "risk-parity" portfolio over the sample. Risk-parity is a term used in a variety of ways, but here we have in mind setting the weight of the portfolio to be proportional to the inverse of its full-sample volatility estimate.$$w_{i} = \frac{1}{\sigma_{i}}$$ This will give the weight vector, $w$, but you will need to rescale it to have a target mean of $\tilde{\mu} = .01$.<jupyter_code># This code constructs the risk-parity portfolio

wSigInv = np.array((np.reciprocal(df_excess.std()))) # computes the weights of risk-parity portfolio
meanSigInv = wSigInv@mu # finds the mean of the risk-parity portfolio 

wrSigInv = wSigInv*(0.01*12)/meanSigInv # rescales the weights vector to get the target mean

meanwrSigInv = wrSigInv @ mu  # computes the new mean of the portfolio
volatilitywrSigInv = np.sqrt(wrSigInv @ covm @ wrSigInv)*np.sqrt(12) # computes the volatility of the portfolio
SharpeTpSigInv = meanwrSigInv/volatilitywrSigInv # computes Sharpe ratio of the portfolio

print('MV Portfolio:        ',dicp['Mean'], dicp['Volatility'], dicp['Sharpe'])
print('Equally W Portfolio: ',meanwpe, volatilitywpe, SharpeTpwpe)
print('RP Portfolio:        ',meanwrSigInv, volatilitywrSigInv, SharpeTpSigInv)<jupyter_output>MV Portfolio:         0.12000000000000002 0.05289307925357347 2.2687278126635593
Equally W Portfolio:  0.12000000000000001 0.14355613150835042 0.8359099589767074
RP Portfolio:         0.12 0.12835250527091432 0.9349252649702111<jupyter_text>(c) How does these compare to the MV portfolio from problem 2.4  The portfolios have the same mean returns, but MV portfolio has the highest sharpe ratio given that it has the lowest volatility of the sets of portfolio. The RP portfolio does better than the equally weighted portfolio with higher sharpe ratio and lower volatility.   6. Out-of-Sample Performance  Let's divide the sample to both compute a portfolio and then check its performance out of sample.  (a) Using only data through the end of 2020, compute $w_{p}$ for $\tilde{\mu}_{p} = .01$, allocating to all 11 assets.<jupyter_code># First we isolate the data thorugh the end of 2020

insample = df_excess[:'2021-01-01']
insample.tail()
# Now we compute the parameters necessary for MVPortER function
muis = insample.mean()*12
muvis = np.array(muis)
covmis = insample.cov().to_numpy()

# We reuse the previously defined function to compute the weights
[wpis, dicis] = MVPortER(0.01,covmis, muvis, 12)

wpis<jupyter_output><empty_output><jupyter_text>(b) Using those weights, calculate the portfolio's Sharpe ratio within that sample, through the end of 2020.<jupyter_code># We reuse the output of the MVPortER function
print('The portfolio\'s Sharpe ratio is ', dicis['Sharpe'], ' through the end of 2020.')<jupyter_output>The portfolio's Sharpe ratio is  2.289852813940019  through the end of 2020.<jupyter_text>(c) Again using those weights, (derived using data through 2020,) calculate the portfolio's Sharpe ratio based on performance in 2021.<jupyter_code># Define out-of-sample with data through 2021

outsample = df_excess['2021-01-01':]
outsample.head()
covmos = outsample.cov().to_numpy() # get the outsample covariance matrix 
muout = np.array(outsample.mean()*12) # get the outsample mean 
wpisx = np.transpose(np.array(wpis.to_numpy())) # use wpis from part a

outpmean = wpisx @ muout # compute the new mean
outpvol = np.sqrt(wpisx @ covmos @ np.transpose(wpisx))*np.sqrt(12) # compute the volatility
pSharpe = outpmean/outpvol # compute the Sharpe ratio

print('Mean        :', float(outpmean))
print('Volatility  :', float(outpvol))
print('Sharpe Ratio:', float(pSharpe))<jupyter_output>Mean        : 0.0998786401556127
Volatility  : 0.06615860010125083
Sharpe Ratio: 1.509684908730775
{'fraction_non_alphanumeric': 0.089347331326628, 'fraction_numerical': 0.04059907495778577, 'mean_word_length': 5.059608540925267, 'pattern_counts': {'":': 0, '<': 75, '<?xml version=': 0, '>': 75, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '19', 'dir': 'jupyter-structured-clean-dedup', 'id': '621806', 'license': 'MIT', 'n_tokens_mistral': 4747, 'n_tokens_neox': 4206, 'n_words': 1569, 'path': 'solutions/hw1/Homework 1 Final - Group 20.ipynb', 'repo_name': 'tulyu96/finm-portfolio-2021', 'size': '13646'}