# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

#Import all necessary libraries and import the data from excel
#Assume data is in same directory
import pandas as pd
import math
import seaborn as sn
import matplotlib.pyplot as plt
import numpy as np
returns = pd.read_excel('multi_asset_etf_data_hw_1_2021.xlsx',sheet_name=2,index_col='Date')
descriptions = pd.read_excel('multi_asset_etf_data_hw_1_2021.xlsx',sheet_name=0)
descriptions.rename(columns={descriptions.columns[0]:'Ticker'},inplace=True)
descriptions.set_index('Ticker',inplace=True)


# # 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.)
#
# (b) Which assets have the best and worst Sharpe ratios?
#
#     The SPDR S&P500 ETF and Invesco Commodity Index Tracking ETF have the best and worst annualized Sharpe ratios, respectively.

ann_returns = returns*12
stats = pd.DataFrame()
stats['Mean'] = ann_returns.mean()
stats['Volatility'] = returns.std()*math.sqrt(12)
stats['Sharpe'] = stats['Mean']/stats['Volatility']
stats = stats.join(descriptions,how='left')
stats.sort_values(by='Sharpe')

# # 2. Descriptive Analysis
# (a) Calculate the correlation matrix of the returns. Which pair has the highest correlation?
# And the lowest? 
#
#     EFA,PSP has the highest correlation while IEF, SPY has the lowest correlation
#     
# (b) How well have TIPS done in our sample? Have they outperformed domestic bonds? Foreign bonds?
#     
#     TIPS have outperformed commodities and every other bond category with the exception of the High Yield iShares ETF (HYG). Specifically, TIPS have outperformed domestic and foreign bonds with a higher Sharpe Ratio.
#     
# (c) Based on the data, do TIPS seem to expand the investment opportunity set, implying that
# Harvard should consider them as a separate asset? 
#     
#     Given the low correlations with other asset classes, TIPS appear to offer some of the best diversification benefits across all assets (second only to 7-10 year Treasuries). TIPS also have a relatively high Sharpe ratio and have been performed well in sample.

plt.figure(figsize=(16, 6)) #Chen
sn.heatmap(returns.corr(),annot=True,cmap='coolwarm',linewidths=3,linecolor='black').set_title("Assets Covariance Matrix") # Chen
plt.show()

plt.figure(figsize=(16, 6)) #Chen
sn.heatmap(returns.cov(),annot=True,cmap ="YlGnBu",linewidths=3,linecolor='black').set_title("Assets Covariance Matrix") # Chen
plt.show()

#Compare average annualized and total return for TIPS vs other assets
#TODO: Add a total return graph
stats['TIP_Avg_ER'] = stats['Mean'].loc['TIP']-stats['Mean']
total_returns = pd.DataFrame(pd.DataFrame(returns+1).cumprod()-1)
total_returns = total_returns.iloc[-1:].iloc[0]
stats['TIP_Tot_ER'] = total_returns.loc['TIP'] - total_returns
print(stats[['ETF Description','TIP_Avg_ER',]].sort_values(by='TIP_Avg_ER'))
print(stats[['ETF Description','TIP_Tot_ER',]].sort_values(by='TIP_Tot_ER'))

# # 3. The MV frontier.
#
# (a) Compute and display the weights of the tangency portfolios: wtan
#
# ![image.png](attachment:image.png)

sigma = returns.cov()
sigma_inv = np.linalg.inv(sigma)
mu_tilde = returns.mean()
ones = np.ones(sigma_inv.shape[0])
w_t = sigma_inv @ mu_tilde / (ones @ sigma_inv @ mu_tilde)
w_t = pd.DataFrame(w_t, index = sigma.index,columns=['w_t'])
w_t

# (b) Compute the mean, volatility, and Sharpe ratio for the tangency portfolio corresponding to
# wtan.
# ![image.png](attachment:image.png)
# ![image-2.png](attachment:image-2.png)

mu_t = w_t.T @ mu_tilde
vol_t = np.sqrt(w_t.T @ sigma @ w_t)
sharpe_t = mu_t/vol_t
t_stats = pd.DataFrame()
t_stats['Mean'] = mu_t*12
t_stats['Vol'] = vol_t * np.sqrt(12)
t_stats['Sharpe'] = t_stats['Mean']/t_stats['Vol']
t_stats

# +
# <NAME>
raw = pd.read_excel(r"D:\Study\AUG2021\FIN36700 Portfolio Theory\HW\multi_asset_etf_data.xlsx",sheet_name="excess returns")
df = raw.drop(columns="Date")
annualized_mean = pd.DataFrame.mean(df)*12
annualized_std =pd.DataFrame.std(df)*math.sqrt(12)
annualized_SR = annualized_mean/annualized_std
cov_matrix = pd.DataFrame.cov(df)
p_weights = []
p_vol = []
p_ret = []
p_sr = []
num_assets = len(df.columns)
num_portfolios = 50000
for portfolio in range(num_portfolios):
    weights = np.random.random(num_assets)
    weights = weights/np.sum(weights)  # normalize to 100% total wegiht
    p_weights.append(weights)
    returns = np.dot(weights.T, annualized_mean)
    p_ret.append(returns)
    var = np.dot(weights.T,np.dot(cov_matrix,weights))
    sd = math.sqrt(var)*math.sqrt(12)
    p_vol.append(sd)
    sr = returns/sd
    p_sr.append(sr)

data = {'Returns':p_ret,"Volatility":p_vol,"Sharpe":p_sr}
portfolios = pd.DataFrame(data)
max_sharpe_port = portfolios.iloc[portfolios['Sharpe'].idxmax()]
min_vol_port = portfolios.iloc[portfolios['Volatility'].idxmin()]
plt.figure(figsize=(16, 8))
plt.scatter(portfolios.Volatility,portfolios.Returns,c=portfolios.Sharpe,cmap='YlGnBu')
plt.title("Efficient Frontier")
plt.xlabel("Volatility")
plt.ylabel("Returns")
plt.colorbar()
plt.scatter(max_sharpe_port[1],max_sharpe_port[0],marker=(5,1,0),color='g',s=200)
plt.scatter(min_vol_port[1],min_vol_port[0],marker=(5,1,0),color='r',s=200)
max_sharpe_port
max_sharpe_port

# +
#Without TIP
raw2 = raw.drop(columns="TIP")
df2 = raw2.drop(columns="Date")
annualized_mean = pd.DataFrame.mean(df2)*12
annualized_std =pd.DataFrame.std(df2)*math.sqrt(12)
annualized_SR = annualized_mean/annualized_std
cov_matrix = pd.DataFrame.cov(df)
p_weights = []
p_vol = []
p_ret = []
p_sr = []
num_assets = len(df.columns)
num_portfolios = 50000
for portfolio in range(num_portfolios):
    weights = np.random.random(num_assets)
    weights = weights/np.sum(weights)  # normalize to 100% total wegiht
    p_weights.append(weights)
    returns = np.dot(weights.T, annualized_mean)
    p_ret.append(returns)
    var = np.dot(weights.T,np.dot(cov_matrix,weights))
    sd = math.sqrt(var)*math.sqrt(12)
    p_vol.append(sd)
    sr = returns/sd
    p_sr.append(sr)

data = {'Returns':p_ret,"Volatility":p_vol,"Sharpe":p_sr}
portfolios = pd.DataFrame(data)
max_sharpe_port1 = portfolios.iloc[portfolios['Sharpe'].idxmax()]
min_vol_port1 = portfolios.iloc[portfolios['Volatility'].idxmin()]
plt.figure(figsize=(16, 8))
plt.scatter(portfolios.Volatility,portfolios.Returns,c=portfolios.Sharpe,cmap='BuPu')
plt.title("Efficient Frontier without TIP")
plt.xlabel("Volatility")
plt.ylabel("Returns")
plt.colorbar()
plt.scatter(max_sharpe_port1[1],max_sharpe_port1[0],marker=(5,1,0),color='g',s=200)
plt.scatter(min_vol_port1[1],min_vol_port1[0],marker=(5,1,0),color='r',s=200)

# -

tangency_p = {'With TIP':max_sharpe_port,"Without TIP":max_sharpe_port1}
pd.DataFrame(tangency_p)

minvol_p = {'With TIP':min_vol_port,"Without TIP":min_vol_port1}
pd.DataFrame(minvol_p)

# # 4. The allocation.
#
# (a) Compute and display the weights of MV portfolios with target returns of µ˜p = .01

# ![image.png](attachment:image.png)

d_tilde = ((ones @ sigma_inv @ mu_tilde)/(mu_tilde.T @ sigma_inv @ mu_tilde))*0.01
w_p = d_tilde * w_t
w_p = pd.DataFrame(w_p,index=sigma.index).rename(columns={'w_t':'w_p'})
w_p

# (b) What is the mean, volatility, and Sharpe ratio for wp?

mu_p = w_p.T @ mu_tilde
vol_p = np.sqrt(w_p.T @ sigma @ w_p)
sharpe_p = mu_p/vol_p
p_stats = pd.DataFrame()
p_stats['Mean'] = mu_p*12
p_stats['Vol'] = vol_p*np.sqrt(12)
p_stats['Sharpe'] = p_stats['Mean']/p_stats['Vol']
p_stats

# (c) Discuss the allocation. In which assets is the portfolio most long? And short?
#
#     The longest position is domestic bonds while absolute return is the most short. Some of the individual weights are close in magnitude to 1, which is extreme.
#
#
# (d) Does this line up with which assets have the strongest Sharpe ratios?
#
#     Not really. Domestic equities have 2x the Sharpe Ratio of domestic bonds but get a lower weight. Also the Sharpe ratio for Absolute Return is similar to that of domestic bonds but it gets massively shorted. This could be driven, for example, by the fact that QAI and SPY have a high correlation of ~0.82. SPY has a higher Sharpe Ratio and therefore the optimization goes long SPY while shorting QAI.

w_p.join(stats,how='left')[['ETF Description','w_p','Sharpe']].sort_values(by='w_p')

# # 5. Simple Portfolios
#
# (a) Calculate the performance of the equally-weighted portfolio over the sample. Rescale the
# entire weighting vector to have target mean w˜ = .01. Report its mean, volatility, and
# Sharpe ratio.

mu_target = 0.01
w_eq_unscale = np.ones(len(returns.columns))*(1/len(returns.columns))
mu_eq_unscale = w_eq_unscale.T @ mu_tilde
eq_scale = mu_eq_unscale/mu_target
w_eq = pd.DataFrame(w_eq_unscale/eq_scale,index=stats.index)
w_eq_stats = pd.DataFrame()
w_eq_stats['Mean'] = w_eq.T @ mu_tilde *12
w_eq_stats['Volatility'] = np.sqrt(w_eq.T @ sigma @ w_eq) * np.sqrt(12)
w_eq_stats['Sharpe'] = w_eq_stats['Mean']/w_eq_stats['Volatility']
w_eq_stats

# (b)  Calculate the performance of the “risk-parity” portfolio over the sample.  Risk-parity is aterm used in a variety of ways, but here we have in mind setting the weight of the portfolioto be proportional to the inverse of its full-sample volatility estimate.wi=1σi

w_rp_unscale = 1/(stats['Volatility'])
mu_rp_unscale = w_rp_unscale.T @ mu_tilde
rp_scale = mu_rp_unscale/mu_target
w_rp = pd.DataFrame(w_rp_unscale/rp_scale).rename(columns={'Volatility':'rp_weights'})
w_rp_stats = pd.DataFrame()
w_rp_stats['Mean'] = w_rp.T @ mu_tilde *12
w_rp_stats['Volatility'] = np.sqrt(w_rp.T @ sigma @ w_rp) * np.sqrt(12)
w_rp_stats['Sharpe'] = w_rp_stats['Mean']/w_rp_stats['Volatility']
w_rp_stats

# (c)  How does these compare to the MV portfolio from problem 2.4?
#
#     In terms of Sharpe Ratios, Equal Weight < Risk Parity < Mean Variance. These other portfolios don't consider covariances between asset classes, which is a major driver of overall volatility. It is not surprising that their Sharpe ratios are thus lower and they have higher portfolio volatilities for a given mean.

# # 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 wp for ̃μp=.01,  allocating to all 11 assets.

returns2020 = returns.loc[:'2020':]
sigma2020 = returns2020.cov()
sigma_inv2020 = np.linalg.inv(sigma2020)
mu_tilde2020 = returns2020.mean()
ones2020 = np.ones(sigma_inv2020.shape[0])
w_t2020 = sigma_inv2020 @ mu_tilde2020 / (ones2020 @ sigma_inv2020 @ mu_tilde2020)
w_t2020 = pd.DataFrame(w_t2020, index = sigma2020.index,columns=['w_t2020'])
d_tilde2020 = ((ones2020 @ sigma_inv2020 @ mu_tilde2020)/(mu_tilde2020.T @ sigma_inv2020 @ mu_tilde2020))*0.01
w_p2020 = pd.DataFrame(d_tilde2020 * w_t2020).rename(columns={'w_t2020':'w_p2020'})
w_p2020

# (b)  Using those weights, calculate the portfolio’s Sharpe ratio within that sample, through the end of 2020.

Sharpe_2020 = (w_p2020.T @ mu_tilde2020 *12) \
/(np.sqrt(w_p2020.T @ sigma2020 @ w_p2020) * np.sqrt(12))
Sharpe_2020

# (c)  Again  using  those  weights,  (derived  using  data  through  2020,)  calculate  the  portfolio’s Sharpe ratio based on performance in 2021.

mu_wp2020_2021 = w_p2020.T @ (returns.loc['2021'].mean() * 12)
sigma_2021 = returns.loc['2021'].cov()
vol_wp2020_2021 = np.sqrt(w_p2020.T @ sigma_2021 @ w_p2020) *np.sqrt(12)
Sharpe_w_p2020_2021 = mu_wp2020_2021/vol_wp2020_2021
Sharpe_w_p2020_2021

#     The Sharpe ratio for the performance on the out-of-sample data is noticeably lower. This is typical of out-of-sample data performance. IEF was weighted heavily in the portfolio and had underperformed in 2021. Real estate performed very strongly in 2021, but the portfolio did not go heavily long in it.



StarcoderdataJupyterScriptsDedupFiltered