FINM 33150 - Macro Finance - Project¶

REDACTED, Jared Szajkowski (REDACTED), REDACTED¶

Python Imports¶

In [1]:
import os
import pandas as pd
import numpy as np
import datetime
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from matplotlib import dates as mdates
from matplotlib.ticker import FuncFormatter
from matplotlib.ticker import FormatStrFormatter
import warnings
import seaborn as sns
import statsmodels.api as sm
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import random
warnings.filterwarnings("ignore")

Python Functions¶

In [2]:
# Set number of decimal places
def dp(decimal_places):
    pd.set_option('display.float_format', lambda x: f'%.{decimal_places}f' % x)

dp(3)
In [3]:
def load_data(file):
    # Import excel
    df = pd.read_excel('Fund Data/' + file, sheet_name='data', engine='openpyxl')
    df = df[['Date', 'adj_close']]

    # Filter data & resample
    df = df[(df['Date'] >= '2010-01-01') & (df['Date'] <= '2023-06-30')]
    df.sort_values(by = ['Date'], ascending = True, inplace = True)
    df.set_index('Date', inplace=True)
    df = df.resample('M').last()
    return df
In [4]:
def load_data_interpolate(file):
    # Import excel
    df = pd.read_excel('Fund Data/' + file, sheet_name='data', engine='openpyxl')
    df = df[['Date', 'adj_close']]
    df.rename(columns = {'adj_close':'adj_close_orig'}, inplace = True)

    # Filter data & resample
    df = df[(df['Date'] >= '2010-01-01') & (df['Date'] <= '2023-06-30')]
    df.sort_values(by = ['Date'], ascending = True, inplace = True)
    df.set_index('Date', inplace=True)
    df = df.resample('M').last()
    df.interpolate(method='linear', inplace=True)
    df['adj_close'] = df['adj_close_orig'].shift(2)
    return df
In [5]:
# Function to format y-axis as percentage
def percentage(x, pos):
    return '{:.0%}'.format(x)
In [6]:
# Function to calculate returns for a given period
def calculate_returns(start_date, end_date, data):
    data_period = data[(data.index >= start_date) & (data.index <= end_date)]
    data_period['monthly_return'] = data_period['adj_close'].pct_change()
    data_period['cum_monthly_return'] = (1 + data_period['monthly_return']).cumprod()
    return data_period['cum_monthly_return'][-1] - 1  # Return cumulative return at the end of the period
In [7]:
# Function to calculate volatility for a given period
def calculate_vol(start_date, end_date, data):
    data_period = data[(data.index >= start_date) & (data.index <= end_date)]
    data_period['monthly_return'] = data_period['adj_close'].pct_change()
    return data_period['monthly_return'].std() * np.sqrt(12) # annualized for 12 months
In [8]:
# Function to calculate volatility for a given period
def calculate_dd(start_date, end_date, data):
    data_period = data[(data.index >= start_date) & (data.index <= end_date)]
    data_period['monthly_return'] = data_period['adj_close'].pct_change()
    wealth_index = 1000*(1 + data_period).cumprod()
    previous_peaks = wealth_index.cummax()
    drawdowns = (wealth_index - previous_peaks)/previous_peaks
    return drawdowns.min() # annualized for 12 months
In [9]:
# def display_correlation(df, annot=True, list_maxmin=True, cmap='coolwarm', figsize=(10, 8)):
def display_correlation(df, annot=True, list_maxmin=True, figsize=(10, 8)):
    corrmat = df.corr()
    # Ignore self-correlation
    corrmat.values[[i for i in range(len(corrmat))], [i for i in range(len(corrmat))]] = None
    sns.set(font_scale=0.75)
    plt.figure(figsize=figsize)
    # sns.heatmap(corrmat, annot=annot, cmap=cmap, fmt=".2f")  # Setting fmt to display values with 2 decimal places
    sns.heatmap(corrmat, annot=annot, fmt=".2f")  # Setting fmt to display values with 2 decimal places

    if list_maxmin:
        corr_rank = corrmat.unstack().sort_values().dropna()
        pair_max = corr_rank.index[-1]
        pair_min = corr_rank.index[0]

        print(f'MIN Correlation pair is {pair_min}')
        print(f'MAX Correlation pair is {pair_max}')

ISBI Composite Benchmark Index Returns¶

Load Data For Composite Benchmark Proxies¶

In [10]:
iwv = load_data('IWV_NDL.xlsx')
efa = load_data('EFA_NDL.xlsx')
eem = load_data('EEM_NDL.xlsx')
acwx = load_data('ACWX_NDL.xlsx')
agg = load_data('AGG_NDL.xlsx')
spti = load_data('SPTI_NDL.xlsx')
vglt = load_data('VGLT_NDL.xlsx')
tip = load_data('TIP_NDL.xlsx')
LF98TRUU = load_data('LF98TRUU_BB.xlsx')
SPBDLLB = load_data('SPBDLLB_BB.xlsx')
NPPI0DIV = load_data_interpolate('NPPI0DIV_BB.xlsx')
LPX50TR = load_data_interpolate('LPX50TR_BB.xlsx')
psp = load_data('PSP_NDL.xlsx')
emb = load_data('EMB_NDL.xlsx')
hdg = load_data('HDG_NDL.xlsx')
cpi = load_data('CPIAUCSL.xlsx')

Calculate And Combine Annual Returns For Composite Benchmark Proxies¶

In [11]:
# Generate start and end dates
start_dates = pd.date_range(start='2013-06-30', end='2022-06-30', freq='6M')
start_dates = start_dates[start_dates.month != 12]
end_dates = pd.date_range(start='2014-06-30', end='2023-06-30', freq='6M')
end_dates = end_dates[end_dates.month != 12]

index_dfs = [(iwv, 'IWV'), (efa, 'EFA'), (eem, 'EEM'), (acwx, 'ACWX'), (agg, 'AGG'), (spti, 'SPTI'), (vglt, 'VGLT'), (tip, 'TIP'), 
             (LF98TRUU, 'LF98TRUU'), (SPBDLLB, 'SPBDLLB'), (NPPI0DIV, 'NPPI0DIV'), (LPX50TR, 'LPX50TR'),
             (psp, 'PSP'), (emb, 'EMB'), (hdg, 'HDG'), (cpi, 'CPI')]

fy_index = ['FY 2014', 'FY 2015', 'FY 2016', 'FY 2017', 'FY 2018', 'FY 2019', 'FY 2020', 'FY 2021', 'FY 2022', 'FY 2023']
In [12]:
# Create a DataFrame to store results
cbm_proxy_fund_returns = pd.DataFrame(columns=['Start_Date', 'End_Date', 'IWV'])

for df, name in index_dfs:
    if df.equals(iwv):
        for start, end in zip(start_dates, end_dates):
            start_date_str = start.strftime('%Y-%m-%d')
            end_date_str = end.strftime('%Y-%m-%d')
            returns = calculate_returns(start_date_str, end_date_str, df)
            new_row_data = pd.DataFrame({'Start_Date': [start_date_str], 'End_Date': [end_date_str], 'IWV': [returns]})
            cbm_proxy_fund_returns = pd.concat([cbm_proxy_fund_returns, new_row_data], ignore_index=True)

    else:
        for i, (start, end) in enumerate(zip(start_dates, end_dates)):
            start_date_str = start.strftime('%Y-%m-%d')
            end_date_str = end.strftime('%Y-%m-%d')
            returns = calculate_returns(start_date_str, end_date_str, df)
            cbm_proxy_fund_returns.loc[i, name] = returns

# Set fy_index as the index of the DataFrame
cbm_proxy_fund_returns.index = fy_index
cbm_proxy_fund_returns = cbm_proxy_fund_returns.T
cbm_proxy_fund_returns = cbm_proxy_fund_returns[cbm_proxy_fund_returns.columns[::-1]]
cbm_proxy_fund_returns.index.name = 'Ticker'
cbm_proxy_fund_returns.drop(['Start_Date', 'End_Date'], inplace = True)
display(cbm_proxy_fund_returns)
FY 2023 FY 2022 FY 2021 FY 2020 FY 2019 FY 2018 FY 2017 FY 2016 FY 2015 FY 2014
Ticker
IWV 0.174 -0.140 0.440 0.063 0.087 0.146 0.183 0.021 0.071 0.243
EFA 0.182 -0.174 0.326 -0.050 0.013 0.059 0.199 -0.095 -0.047 0.233
EEM -0.006 -0.256 0.399 -0.043 0.013 0.070 0.224 -0.112 -0.064 0.142
ACWX 0.121 -0.191 0.357 -0.049 0.015 0.064 0.199 -0.095 -0.056 0.217
AGG -0.026 -0.103 -0.005 0.089 0.077 -0.005 -0.004 0.060 0.017 0.044
SPTI -0.031 -0.083 -0.021 -0.454 0.077 -0.009 -0.015 0.036 0.009 0.013
VGLT -0.068 -0.184 -0.106 0.253 0.123 -0.004 -0.071 0.196 0.063 0.067
TIP -0.032 -0.053 0.061 0.083 0.046 0.020 -0.008 0.045 -0.021 0.044
LF98TRUU 0.091 -0.128 0.154 0.000 0.075 0.026 0.127 0.016 -0.004 0.117
SPBDLLB 0.039 -0.076 0.073 -0.057 -0.005 -0.002 0.084 -0.045 -0.039 0.019
NPPI0DIV -0.055 0.288 0.042 0.040 0.071 0.082 0.082 0.130 0.138 0.134
LPX50TR -0.055 0.060 0.640 -0.081 0.136 -0.006 0.270 -0.073 0.314 0.125
PSP 0.102 -0.304 0.578 -0.052 0.033 0.058 0.323 -0.128 -0.050 0.266
EMB 0.066 -0.209 0.070 0.004 0.116 -0.025 0.037 0.100 -0.005 0.103
HDG 0.048 -0.112 0.143 0.012 0.020 0.020 0.056 -0.026 0.016 0.054
CPI 0.031 0.090 0.053 0.007 0.017 0.028 0.016 0.011 0.002 0.021

Calculate And Combine Annual Volatility For Composite Benchmark Proxies¶

In [13]:
# Create a DataFrame to store results
cbm_proxy_fund_vol = pd.DataFrame(columns=['Start_Date', 'End_Date', 'IWV'])

for df, name in index_dfs:
    if df.equals(iwv):
        for start, end in zip(start_dates, end_dates):
            start_date_str = start.strftime('%Y-%m-%d')
            end_date_str = end.strftime('%Y-%m-%d')
            returns = calculate_vol(start_date_str, end_date_str, df)
            new_row_data = pd.DataFrame({'Start_Date': [start_date_str], 'End_Date': [end_date_str], 'IWV': [returns]})
            cbm_proxy_fund_vol = pd.concat([cbm_proxy_fund_vol, new_row_data], ignore_index=True)

    else:
        for i, (start, end) in enumerate(zip(start_dates, end_dates)):
            start_date_str = start.strftime('%Y-%m-%d')
            end_date_str = end.strftime('%Y-%m-%d')
            returns = calculate_vol(start_date_str, end_date_str, df)
            cbm_proxy_fund_vol.loc[i, name] = returns

# Set fy_index as the index of the DataFrame
cbm_proxy_fund_vol.index = fy_index
cbm_proxy_fund_vol = cbm_proxy_fund_vol.T
cbm_proxy_fund_vol = cbm_proxy_fund_vol[cbm_proxy_fund_vol.columns[::-1]]
cbm_proxy_fund_vol.index.name = 'Ticker'
cbm_proxy_fund_vol.drop(['Start_Date', 'End_Date'], inplace = True)
display(cbm_proxy_fund_vol)
FY 2023 FY 2022 FY 2021 FY 2020 FY 2019 FY 2018 FY 2017 FY 2016 FY 2015 FY 2014
Ticker
IWV 0.210 0.175 0.148 0.231 0.194 0.082 0.066 0.149 0.094 0.095
EFA 0.228 0.143 0.159 0.204 0.158 0.089 0.071 0.154 0.106 0.124
EEM 0.250 0.107 0.118 0.236 0.195 0.147 0.101 0.211 0.141 0.139
ACWX 0.223 0.126 0.136 0.210 0.161 0.096 0.068 0.159 0.108 0.119
AGG 0.092 0.051 0.032 0.037 0.034 0.025 0.032 0.025 0.032 0.025
SPTI 0.077 0.044 0.021 0.514 0.034 0.018 0.022 0.020 0.025 0.019
VGLT 0.171 0.121 0.122 0.156 0.116 0.077 0.099 0.085 0.138 0.082
TIP 0.111 0.059 0.038 0.047 0.037 0.025 0.029 0.038 0.051 0.047
LF98TRUU 0.095 0.080 0.056 0.140 0.064 0.020 0.033 0.086 0.050 0.029
SPBDLLB 0.058 0.040 0.036 0.106 0.061 0.013 0.063 0.052 0.029 0.016
NPPI0DIV 0.041 0.017 0.018 0.010 0.004 0.002 0.002 0.008 0.003 0.006
LPX50TR 0.339 0.206 0.184 0.335 0.195 0.095 0.098 0.188 0.097 0.089
PSP 0.302 0.245 0.181 0.360 0.212 0.119 0.074 0.169 0.142 0.133
EMB 0.146 0.104 0.080 0.184 0.076 0.046 0.064 0.063 0.053 0.070
HDG 0.078 0.060 0.064 0.090 0.072 0.026 0.017 0.054 0.033 0.037
CPI 0.007 0.010 0.008 0.012 0.006 0.005 0.005 0.007 0.010 0.003

Calculate And Combine Annual Max Drawdown For Composite Benchmark Proxies¶

In [14]:
# Create a DataFrame to store results
cbm_proxy_fund_dd = pd.DataFrame(columns=['Start_Date', 'End_Date', 'IWV'])

for df, name in index_dfs:
    if df.equals(iwv):
        for start, end in zip(start_dates, end_dates):
            start_date_str = start.strftime('%Y-%m-%d')
            end_date_str = end.strftime('%Y-%m-%d')
            returns = calculate_dd(start_date_str, end_date_str, df)
            new_row_data = pd.DataFrame({'Start_Date': [start_date_str], 'End_Date': [end_date_str], 'IWV': [returns[1]]})
            cbm_proxy_fund_dd = pd.concat([cbm_proxy_fund_dd, new_row_data], ignore_index=True)

    else:
        for i, (start, end) in enumerate(zip(start_dates, end_dates)):
            start_date_str = start.strftime('%Y-%m-%d')
            end_date_str = end.strftime('%Y-%m-%d')
            returns = calculate_dd(start_date_str, end_date_str, df)
            cbm_proxy_fund_dd.loc[i, name] = returns[1]

# Set fy_index as the index of the DataFrame
cbm_proxy_fund_dd.index = fy_index
cbm_proxy_fund_dd = cbm_proxy_fund_dd.T
cbm_proxy_fund_dd = cbm_proxy_fund_dd[cbm_proxy_fund_dd.columns[::-1]]
cbm_proxy_fund_dd.index.name = 'Ticker'
cbm_proxy_fund_dd.drop(['Start_Date', 'End_Date'], inplace = True)
display(cbm_proxy_fund_dd)
FY 2023 FY 2022 FY 2021 FY 2020 FY 2019 FY 2018 FY 2017 FY 2016 FY 2015 FY 2014
Ticker
IWV -0.130 -0.212 -0.055 -0.209 -0.142 -0.056 -0.022 -0.089 -0.028 -0.032
EFA -0.148 -0.192 -0.055 -0.230 -0.137 -0.075 -0.040 -0.165 -0.079 -0.052
EEM -0.144 -0.217 -0.010 -0.239 -0.127 -0.145 -0.054 -0.171 -0.122 -0.092
ACWX -0.140 -0.189 -0.038 -0.234 -0.129 -0.089 -0.038 -0.166 -0.093 -0.061
AGG -0.086 -0.112 -0.036 -0.006 -0.013 -0.025 -0.035 -0.008 -0.023 -0.008
SPTI -0.070 -0.094 -0.036 -0.506 -0.008 -0.025 -0.027 -0.007 -0.014 -0.008
VGLT -0.166 -0.224 -0.193 -0.068 -0.055 -0.061 -0.140 -0.015 -0.123 -0.044
TIP -0.104 -0.090 -0.019 -0.018 -0.025 -0.019 -0.026 -0.023 -0.031 -0.025
LF98TRUU -0.062 -0.142 -0.010 -0.127 -0.045 -0.014 -0.005 -0.078 -0.031 -0.006
SPBDLLB -0.034 -0.077 -0.011 -0.111 -0.058 -0.010 -0.007 -0.081 -0.034 -0.008
NPPI0DIV 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
LPX50TR 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
PSP -0.218 -0.368 -0.037 -0.347 -0.186 -0.074 -0.024 -0.194 -0.070 -0.040
EMB -0.093 -0.221 -0.055 -0.160 -0.033 -0.065 -0.064 -0.022 -0.033 -0.025
HDG -0.051 -0.107 -0.014 -0.088 -0.052 -0.018 -0.006 -0.052 -0.014 -0.020
CPI 0.000 0.000 0.000 -0.013 -0.001 0.000 -0.001 -0.003 -0.012 0.000

Load Composite Benchmark Data¶

In [15]:
file = 'ISBI Benchmark Indices.xlsx'
df = pd.read_excel('ISBI Data/' + file, sheet_name = 'Benchmark Indices', engine='openpyxl')
df.drop([20, 21], inplace = True)
df.set_index('Ticker', inplace = True)
cbm_weights = df.copy()
In [16]:
cbm_weights
Out[16]:
Asset Class Fund Index Proxy Index FY 2023 FY 2022 FY 2021 FY 2020 FY 2019 FY 2018 FY 2017 FY 2016 FY 2015 FY 2014
Ticker
IWV Domestic Equity Russell 3000 iShares Russell 3000 ETF (IWV) 0.230 0.230 0.230 0.230 0.230 0.230 0.230 0.230 0.300 0.300
EFA International Equity MSCI EAFE iShares MSCI EAFE ETF (EFA) 0.130 0.130 0.130 0.130 0.130 0.130 0.130 0.130 0.000 0.000
EEM International Equity MSCI EM iShares MSCI Emerging Markets ETF (EEM) 0.080 0.080 0.080 0.080 0.080 0.080 0.070 0.070 0.000 0.000
ACWX International Equity MSCI ACWI Ex US IMI iShares MSCI ACWI ex U.S. ETF (ACWX) 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.200 0.200
AGG Fixed Income Barclays Capital US Universal US Aggregate Bond (AGG) 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.250 0.250
AGG Fixed Income Barclays Aggregate US Aggregate Bond (AGG) 0.100 0.100 0.100 0.100 0.100 0.100 0.110 0.110 0.000 0.000
SPTI Fixed Income Barclays Intermediate Treasuries SPDR Series Trust - SPDR Portfolio Intermediat... 0.050 0.050 0.040 0.040 0.040 0.040 0.000 0.000 0.000 0.000
VGLT Fixed Income Barclays Long Term Treasury Index Vanguard Long-Term Treasury ETF (VGLT) 0.050 0.050 0.040 0.040 0.040 0.040 0.030 0.030 0.000 0.000
TIP Fixed Income Custom TIPS Index iShares TIPS Bond ETF (TIP) 0.030 0.030 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
TIP Fixed Income Barclays US TIPS Index iShares TIPS Bond ETF (TIP) 0.000 0.000 0.040 0.040 0.040 0.040 0.050 0.050 0.000 0.000
LF98TRUU Fixed Income Barclays High Yield Index Bloomberg US Corp HY TR Index (LF98TRUU) 0.010 0.010 0.025 0.025 0.025 0.025 0.030 0.030 0.000 0.000
SPBDLLB Private Credit CSFB Leveraged Loan Index Morningstar LSTA US Leveraged Loan 100 Index (... 0.010 0.010 0.025 0.025 0.025 0.025 0.030 0.030 0.000 0.000
SPBDLLB Private Credit S&P/LSTA US Levered Loan 100 Index Morningstar LSTA US Leveraged Loan 100 Index (... 0.090 0.090 0.080 0.080 0.080 0.080 0.000 0.000 0.000 0.000
NPPI0DIV Real Estate NCREIF ODCE NCREIF Fund Index Open End Diversified Core (N... 0.100 0.100 0.100 0.100 0.100 0.100 0.110 0.110 0.100 0.100
LPX50TR Private Equity Cambridge Private Equity Index LPX Listed Private Equity Index TR (LPX50TR) 0.090 0.090 0.070 0.070 0.070 0.070 0.100 0.100 0.000 0.000
EMB Fixed Income JPM GBI EM Global Diversified (unhedged) iShares J.P. Morgan USD Emerging Markets Bond ... 0.000 0.000 0.010 0.010 0.010 0.010 0.015 0.015 0.000 0.000
EMB Fixed Income JPM EMBI Global Diversified (hedged) iShares J.P. Morgan USD Emerging Markets Bond ... 0.000 0.000 0.010 0.010 0.010 0.010 0.015 0.015 0.000 0.000
HDG Hedge Fund HFRI Fund Of Fund Composite ProShares Hedge Replication ETF (HDG) 0.000 0.000 0.000 0.000 0.000 0.000 0.030 0.030 0.100 0.100
PSP Private Equity Custom Private Equity Invesco Global Listed Private Equity ETF (PSP) 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.050 0.050
CPI Other CPI CPI 0.030 0.030 0.020 0.020 0.020 0.020 0.050 0.050 0.000 0.000

Combine Composite Benchmark Proxy Fund Returns With Weights¶

In [17]:
cbm_proxy_returns = pd.DataFrame(columns = ['ISBI CB Proxy Returns', 'ISBI CB Proxy Volatility', 'ISBI CB Proxy Drawdown'])

for year in fy_index:
    combined = pd.merge(cbm_weights[year], cbm_proxy_fund_returns[year], left_on = 'Ticker', right_on = 'Ticker')
    combined.rename(columns = {year + '_x' : year + ' weights', year + '_y': year + ' returns'}, inplace = True)
    combined = pd.merge(combined, cbm_proxy_fund_vol[year], left_on = 'Ticker', right_on = 'Ticker')
    combined.rename(columns = {year:year + ' volatility'}, inplace = True)
    combined = pd.merge(combined, cbm_proxy_fund_dd[year], left_on = 'Ticker', right_on = 'Ticker')
    combined.rename(columns = {year:year + ' drawdown'}, inplace = True)
    combined['weighted_ret'] = combined[year + ' weights'] * combined[year + ' returns']
    weighted_ret = combined['weighted_ret'].sum()
    combined['weighted_vol'] = combined[year + ' weights'] * combined[year + ' volatility']
    weighted_vol = combined['weighted_vol'].sum()
    combined['weighted_dd'] = combined[year + ' weights'] * combined[year + ' drawdown']
    weighted_dd = combined['weighted_dd'].sum()
    cbm_proxy_returns.loc[year] = [weighted_ret, weighted_vol, weighted_dd]

cbm_proxy_returns
Out[17]:
ISBI CB Proxy Returns ISBI CB Proxy Volatility ISBI CB Proxy Drawdown
FY 2014 0.159 0.070 -0.028
FY 2015 0.027 0.069 -0.038
FY 2016 0.008 0.104 -0.063
FY 2017 0.127 0.055 -0.026
FY 2018 0.055 0.059 -0.044
FY 2019 0.061 0.115 -0.073
FY 2020 0.001 0.174 -0.140
FY 2021 0.235 0.096 -0.037
FY 2022 -0.072 0.108 -0.130
FY 2023 0.050 0.164 -0.088

ISBI Composite Benchmark - By Index¶

This is used for the appendix.

In [18]:
cbm_weights_index = cbm_weights.reset_index()
cbm_weights_index.set_index('Fund Index', inplace = True)
cbm_weights_index.drop(columns = {'Asset Class', 'Proxy Index', 'Ticker'}, inplace = True)
cbm_weights_index
Out[18]:
FY 2023 FY 2022 FY 2021 FY 2020 FY 2019 FY 2018 FY 2017 FY 2016 FY 2015 FY 2014
Fund Index
Russell 3000 0.230 0.230 0.230 0.230 0.230 0.230 0.230 0.230 0.300 0.300
MSCI EAFE 0.130 0.130 0.130 0.130 0.130 0.130 0.130 0.130 0.000 0.000
MSCI EM 0.080 0.080 0.080 0.080 0.080 0.080 0.070 0.070 0.000 0.000
MSCI ACWI Ex US IMI 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.200 0.200
Barclays Capital US Universal 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.250 0.250
Barclays Aggregate 0.100 0.100 0.100 0.100 0.100 0.100 0.110 0.110 0.000 0.000
Barclays Intermediate Treasuries 0.050 0.050 0.040 0.040 0.040 0.040 0.000 0.000 0.000 0.000
Barclays Long Term Treasury Index 0.050 0.050 0.040 0.040 0.040 0.040 0.030 0.030 0.000 0.000
Custom TIPS Index 0.030 0.030 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
Barclays US TIPS Index 0.000 0.000 0.040 0.040 0.040 0.040 0.050 0.050 0.000 0.000
Barclays High Yield Index 0.010 0.010 0.025 0.025 0.025 0.025 0.030 0.030 0.000 0.000
CSFB Leveraged Loan Index 0.010 0.010 0.025 0.025 0.025 0.025 0.030 0.030 0.000 0.000
S&P/LSTA US Levered Loan 100 Index 0.090 0.090 0.080 0.080 0.080 0.080 0.000 0.000 0.000 0.000
NCREIF ODCE 0.100 0.100 0.100 0.100 0.100 0.100 0.110 0.110 0.100 0.100
Cambridge Private Equity Index 0.090 0.090 0.070 0.070 0.070 0.070 0.100 0.100 0.000 0.000
JPM GBI EM Global Diversified (unhedged) 0.000 0.000 0.010 0.010 0.010 0.010 0.015 0.015 0.000 0.000
JPM EMBI Global Diversified (hedged) 0.000 0.000 0.010 0.010 0.010 0.010 0.015 0.015 0.000 0.000
HFRI Fund Of Fund Composite 0.000 0.000 0.000 0.000 0.000 0.000 0.030 0.030 0.100 0.100
Custom Private Equity 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.050 0.050
CPI 0.030 0.030 0.020 0.020 0.020 0.020 0.050 0.050 0.000 0.000
In [19]:
df = cbm_weights_index.T
In [20]:
# Create the initial plot
plt.figure(figsize=(10, 5), facecolor = '#F5F5F5')

for i, asset_class in enumerate(df.columns):
    plt.bar(df.index, df[asset_class], label=asset_class, bottom=df.iloc[:, :i].sum(axis=1))
    
# Set X axis
plt.xlabel('Fiscal Year')
plt.xticks(rotation = 0, fontsize = 9)

# Set Y axis
plt.gca().yaxis.set_major_formatter(FuncFormatter(percentage)) # Formatting y-axis as percentage
plt.ylabel('Allocation Percentage')
plt.yticks(fontsize = 9)
plt.ylim(0, 1)

# Set title, etc.
plt.title('ISBI Composite Benchmark For Each Fiscal Year - By Index', fontsize = 12)
plt.tight_layout()
plt.legend(bbox_to_anchor=(1, 1), fontsize = 8)
plt.grid(True)
plt.show()
No description has been provided for this image

ISBI Composite Benchmark - By Asset Class¶

This is used for the appendix.

In [21]:
cbm_weights_assetclass = cbm_weights.reset_index()
cbm_weights_assetclass.set_index('Asset Class', inplace = True)
cbm_weights_assetclass.drop(columns = {'Fund Index', 'Proxy Index', 'Ticker'}, inplace = True)
cbm_weights_assetclass = cbm_weights_assetclass.groupby(level=0).sum()
cbm_weights_assetclass
Out[21]:
FY 2023 FY 2022 FY 2021 FY 2020 FY 2019 FY 2018 FY 2017 FY 2016 FY 2015 FY 2014
Asset Class
Domestic Equity 0.230 0.230 0.230 0.230 0.230 0.230 0.230 0.230 0.300 0.300
Fixed Income 0.240 0.240 0.265 0.265 0.265 0.265 0.250 0.250 0.250 0.250
Hedge Fund 0.000 0.000 0.000 0.000 0.000 0.000 0.030 0.030 0.100 0.100
International Equity 0.210 0.210 0.210 0.210 0.210 0.210 0.200 0.200 0.200 0.200
Other 0.030 0.030 0.020 0.020 0.020 0.020 0.050 0.050 0.000 0.000
Private Credit 0.100 0.100 0.105 0.105 0.105 0.105 0.030 0.030 0.000 0.000
Private Equity 0.090 0.090 0.070 0.070 0.070 0.070 0.100 0.100 0.050 0.050
Real Estate 0.100 0.100 0.100 0.100 0.100 0.100 0.110 0.110 0.100 0.100
In [22]:
df = cbm_weights_assetclass.T
In [23]:
# Create the initial plot
plt.figure(figsize=(10, 5), facecolor = '#F5F5F5')

for i, asset_class in enumerate(df.columns):
    plt.bar(df.index, df[asset_class], label=asset_class, bottom=df.iloc[:, :i].sum(axis=1))
    
# Set X axis
plt.xlabel('Fiscal Year')
plt.xticks(rotation = 0, fontsize = 9)

# Set Y axis
plt.gca().yaxis.set_major_formatter(FuncFormatter(percentage)) # Formatting y-axis as percentage
plt.ylabel('Allocation Percentage')
plt.yticks(fontsize = 9)
plt.ylim(0, 1)

# Set title, etc.
plt.title('ISBI Composite Benchmark For Each Fiscal Year - By Asset Class', fontsize = 12)
plt.tight_layout()
plt.legend(bbox_to_anchor=(1, 1), fontsize = 8)
plt.grid(True)
plt.show()
No description has been provided for this image

ISBI Policy Targets¶

This is used for the appendix.

In [24]:
file = 'ISBI Policy Targets.xlsx'
df = pd.read_excel('ISBI Data/' + file, sheet_name = 'Policy Targets', engine='openpyxl')
df.drop([11, 12], inplace = True)
df.set_index('Asset Class / Fiscal Year', inplace = True)
In [25]:
df
Out[25]:
FY 2023 FY 2022 FY 2021 FY 2020 FY 2019 FY 2018 FY 2017 FY 2016 FY 2015 FY 2014
Asset Class / Fiscal Year
Domestic Equity 0.220 0.230 0.230 0.230 0.230 0.230 0.230 0.230 0.300 0.300
International Equity 0.210 0.210 0.210 0.210 0.210 0.210 0.200 0.200 0.200 0.200
Fixed Income 0.240 0.250 0.290 0.290 0.290 0.260 0.240 0.250 0.160 0.160
Real Estate 0.100 0.100 0.100 0.100 0.100 0.100 0.100 0.110 0.100 0.100
Bank Loans 0.000 0.000 0.000 0.000 0.000 0.030 0.020 0.030 0.040 0.040
Private Equity 0.100 0.090 0.070 0.070 0.070 0.070 0.090 0.100 0.050 0.050
Private Credit 0.100 0.090 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
Real Assets 0.000 0.000 0.000 0.000 0.000 0.000 0.050 0.050 0.050 0.050
Opportunistic Debt 0.000 0.000 0.080 0.080 0.080 0.080 0.040 0.000 0.000 0.000
Infrastructure 0.030 0.030 0.020 0.020 0.020 0.020 0.000 0.000 0.000 0.000
Hedge Funds 0.000 0.000 0.000 0.000 0.000 0.000 0.030 0.030 0.100 0.100
In [26]:
df = df.T
In [27]:
# Create the initial plot
plt.figure(figsize=(10, 5), facecolor = '#F5F5F5')

for i, asset_class in enumerate(df.columns):
    plt.bar(df.index, df[asset_class], label=asset_class, bottom=df.iloc[:, :i].sum(axis=1))
    
# Set X axis
plt.xlabel('Fiscal Year')
plt.xticks(rotation = 0, fontsize = 9)

# Set Y axis
plt.gca().yaxis.set_major_formatter(FuncFormatter(percentage)) # Formatting y-axis as percentage
plt.ylabel('Allocation Percentage')
plt.yticks(fontsize = 9)
plt.ylim(0, 1)

# Set title, etc.
plt.title('ISBI Policy Targets For Each Fiscal Year', fontsize = 12)
plt.tight_layout()
plt.legend(bbox_to_anchor=(1, 1), fontsize = 8)
plt.grid(True)
plt.show()
No description has been provided for this image

New Fund Returns¶

Load Data For New Funds¶

In [28]:
gsg = load_data('GSG_NDL.xlsx')
gld = load_data('GLD_NDL.xlsx')
btc = load_data('BTC-USD.xlsx')

Calculate And Combine Annnual Returns For New Funds¶

In [29]:
# Generate start and end dates
start_dates = pd.date_range(start='2015-06-30', end='2022-06-30', freq='6M')
start_dates = start_dates[start_dates.month != 12]
end_dates = pd.date_range(start='2016-06-30', end='2023-06-30', freq='6M')
end_dates = end_dates[end_dates.month != 12]

index_dfs = [(gsg, 'GSG'), (gld, 'GLD'), (btc, 'BTC')]

fy_index = ['FY 2016', 'FY 2017', 'FY 2018', 'FY 2019', 'FY 2020', 'FY 2021', 'FY 2022', 'FY 2023']
In [30]:
# Create a DataFrame to store results
new_fund_returns = pd.DataFrame(columns=['Start_Date', 'End_Date', 'GSG'])

for df, name in index_dfs:
    if df.equals(gsg):
        for start, end in zip(start_dates, end_dates):
            start_date_str = start.strftime('%Y-%m-%d')
            end_date_str = end.strftime('%Y-%m-%d')
            returns = calculate_returns(start_date_str, end_date_str, df)
            # new_fund_returns = new_fund_returns.append({'Start_Date': start_date_str, 'End_Date': end_date_str, name: returns}, ignore_index=True)
            new_row_data = pd.DataFrame({'Start_Date': [start_date_str], 'End_Date': [end_date_str], 'GSG': [returns]})
            new_fund_returns = pd.concat([new_fund_returns, new_row_data], ignore_index=True)
    else:
        for i, (start, end) in enumerate(zip(start_dates, end_dates)):
            start_date_str = start.strftime('%Y-%m-%d')
            end_date_str = end.strftime('%Y-%m-%d')
            returns = calculate_returns(start_date_str, end_date_str, df)
            new_fund_returns.loc[i, name] = returns

# Set fy_index as the index of the DataFrame
new_fund_returns.index = fy_index
new_fund_returns = new_fund_returns.T
new_fund_returns = new_fund_returns[new_fund_returns.columns[::-1]]
new_fund_returns.index.name = 'Ticker'
new_fund_returns.drop(['Start_Date', 'End_Date'], inplace = True)
display(new_fund_returns)
FY 2023 FY 2022 FY 2021 FY 2020 FY 2019 FY 2018 FY 2017 FY 2016
Ticker
GSG -0.156 0.436 0.552 -0.337 -0.126 0.277 -0.097 -0.265
GLD 0.058 0.017 -0.010 0.257 0.123 0.005 -0.067 0.125
BTC 0.540 -0.435 2.835 -0.155 0.689 1.581 2.684 1.560

Calculate And Combine Annnual Volatility For New Funds¶

In [31]:
# Create a DataFrame to store results
new_fund_vol = pd.DataFrame(columns=['Start_Date', 'End_Date', 'GSG'])

for df, name in index_dfs:
    if df.equals(iwv):
        for start, end in zip(start_dates, end_dates):
            start_date_str = start.strftime('%Y-%m-%d')
            end_date_str = end.strftime('%Y-%m-%d')
            returns = calculate_vol(start_date_str, end_date_str, df)
            # cbm_proxy_returns = cbm_proxy_returns.append({'Start_Date': start_date_str, 'End_Date': end_date_str, name: returns}, ignore_index=True)
            new_row_data = pd.DataFrame({'Start_Date': [start_date_str], 'End_Date': [end_date_str], 'GSG': [returns]})
            new_fund_vol = pd.concat([new_fund_vol, new_row_data], ignore_index=True)

    else:
        for i, (start, end) in enumerate(zip(start_dates, end_dates)):
            start_date_str = start.strftime('%Y-%m-%d')
            end_date_str = end.strftime('%Y-%m-%d')
            returns = calculate_vol(start_date_str, end_date_str, df)
            new_fund_vol.loc[i, name] = returns

# Set fy_index as the index of the DataFrame
new_fund_vol.index = fy_index
new_fund_vol = new_fund_vol.T
new_fund_vol = new_fund_vol[new_fund_vol.columns[::-1]]
new_fund_vol.index.name = 'Ticker'
new_fund_vol.drop(['Start_Date', 'End_Date'], inplace = True)
display(new_fund_vol)
FY 2023 FY 2022 FY 2021 FY 2020 FY 2019 FY 2018 FY 2017 FY 2016
Ticker
GSG 0.134 0.238 0.179 0.396 0.212 0.084 0.140 0.230
GLD 0.160 0.099 0.205 0.126 0.107 0.087 0.124 0.206
BTC 0.561 0.734 0.854 0.626 0.864 1.193 0.761 0.553

Calculate And Combine Annnual Max Drawdown For New Funds¶

In [32]:
# Create a DataFrame to store results
new_fund_dd = pd.DataFrame(columns=['Start_Date', 'End_Date', 'GSG'])

for df, name in index_dfs:
    if df.equals(iwv):
        for start, end in zip(start_dates, end_dates):
            start_date_str = start.strftime('%Y-%m-%d')
            end_date_str = end.strftime('%Y-%m-%d')
            returns = calculate_dd(start_date_str, end_date_str, df)
            new_row_data = pd.DataFrame({'Start_Date': [start_date_str], 'End_Date': [end_date_str], 'IWV': [returns[1]]})
            new_fund_dd = pd.concat([new_fund_dd, new_row_data], ignore_index=True)

    else:
        for i, (start, end) in enumerate(zip(start_dates, end_dates)):
            start_date_str = start.strftime('%Y-%m-%d')
            end_date_str = end.strftime('%Y-%m-%d')
            returns = calculate_dd(start_date_str, end_date_str, df)
            new_fund_dd.loc[i, name] = returns[1]

# Set fy_index as the index of the DataFrame
new_fund_dd.index = fy_index
new_fund_dd = new_fund_dd.T
new_fund_dd = new_fund_dd[new_fund_dd.columns[::-1]]
new_fund_dd.index.name = 'Ticker'
new_fund_dd.drop(['Start_Date', 'End_Date'], inplace = True)
display(new_fund_dd)
FY 2023 FY 2022 FY 2021 FY 2020 FY 2019 FY 2018 FY 2017 FY 2016
Ticker
GSG -0.186 -0.106 -0.077 -0.477 -0.226 -0.037 -0.105 -0.274
GLD -0.074 -0.067 -0.137 -0.041 -0.028 -0.071 -0.150 -0.072
BTC -0.291 -0.677 -0.405 -0.362 -0.556 -0.548 -0.092 -0.192

Modified ISBI Composite Benchmark - By Index¶

Here we pull in the composite benchmark data and rescale to allow for an allocation to additional asset classes.

In [33]:
# start with the df for the composite benchmark weights
cbm_weights_mod = cbm_weights.copy()
cbm_weights_mod.drop(columns = {'Asset Class', 'Proxy Index', 'Fund Index'}, inplace = True)
In [34]:
# Adding a new row for the new asset class
# new_asset_class1 = 'GSG'
new_asset_class2 = 'BTC'
# new_asset_class3 = 'GLD'

# allocation_percentage = 0.111
# allocation_percentage = 0.075
# allocation_percentage = 0.053
allocation_percentage = 0.099
# allocation_percentage = 0.01

# cbm_weights_mod.loc[new_asset_class1] = allocation_percentage
cbm_weights_mod.loc[new_asset_class2] = allocation_percentage
# cbm_weights_mod.loc[new_asset_class3] = allocation_percentage

# Re-scaling the weightings to ensure they sum up to 1
total_weight = cbm_weights_mod.sum(axis=0)
cbm_weights_mod = cbm_weights_mod.div(total_weight)
cbm_weights_mod.loc['Total'] = cbm_weights_mod.sum()
In [35]:
cbm_weights_mod
Out[35]:
FY 2023 FY 2022 FY 2021 FY 2020 FY 2019 FY 2018 FY 2017 FY 2016 FY 2015 FY 2014
Ticker
IWV 0.209 0.209 0.209 0.209 0.209 0.209 0.209 0.209 0.273 0.273
EFA 0.118 0.118 0.118 0.118 0.118 0.118 0.118 0.118 0.000 0.000
EEM 0.073 0.073 0.073 0.073 0.073 0.073 0.064 0.064 0.000 0.000
ACWX 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.182 0.182
AGG 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.227 0.227
AGG 0.091 0.091 0.091 0.091 0.091 0.091 0.100 0.100 0.000 0.000
SPTI 0.045 0.045 0.036 0.036 0.036 0.036 0.000 0.000 0.000 0.000
VGLT 0.045 0.045 0.036 0.036 0.036 0.036 0.027 0.027 0.000 0.000
TIP 0.027 0.027 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
TIP 0.000 0.000 0.036 0.036 0.036 0.036 0.045 0.045 0.000 0.000
LF98TRUU 0.009 0.009 0.023 0.023 0.023 0.023 0.027 0.027 0.000 0.000
SPBDLLB 0.009 0.009 0.023 0.023 0.023 0.023 0.027 0.027 0.000 0.000
SPBDLLB 0.082 0.082 0.073 0.073 0.073 0.073 0.000 0.000 0.000 0.000
NPPI0DIV 0.091 0.091 0.091 0.091 0.091 0.091 0.100 0.100 0.091 0.091
LPX50TR 0.082 0.082 0.064 0.064 0.064 0.064 0.091 0.091 0.000 0.000
EMB 0.000 0.000 0.009 0.009 0.009 0.009 0.014 0.014 0.000 0.000
EMB 0.000 0.000 0.009 0.009 0.009 0.009 0.014 0.014 0.000 0.000
HDG 0.000 0.000 0.000 0.000 0.000 0.000 0.027 0.027 0.091 0.091
PSP 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.045 0.045
CPI 0.027 0.027 0.018 0.018 0.018 0.018 0.045 0.045 0.000 0.000
BTC 0.090 0.090 0.090 0.090 0.090 0.090 0.090 0.090 0.090 0.090
Total 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
In [36]:
combined_returns = pd.concat([cbm_proxy_fund_returns, new_fund_returns])
combined_vol = pd.concat([cbm_proxy_fund_vol, new_fund_vol])
combined_dd = pd.concat([cbm_proxy_fund_dd, new_fund_dd])
In [37]:
cbm_proxy_mod_returns = pd.DataFrame(columns = ['Modified ISBI CB Proxy Returns', 'Modified ISBI CB Proxy Volatility', 'Modified ISBI CB Proxy Drawdown'])

for year in fy_index:
    combined = pd.merge(cbm_weights_mod[year], combined_returns[year], left_on = 'Ticker', right_on = 'Ticker')
    combined.rename(columns = {year + '_x' : year + ' weights', year + '_y': year + ' returns'}, inplace = True)
    combined = pd.merge(combined, combined_vol[year], left_on = 'Ticker', right_on = 'Ticker')
    combined.rename(columns = {year:year + ' volatility'}, inplace = True)
    combined = pd.merge(combined, combined_dd[year], left_on = 'Ticker', right_on = 'Ticker')
    combined.rename(columns = {year:year + ' drawdown'}, inplace = True)
    combined['weighted_ret'] = combined[year + ' weights'] * combined[year + ' returns']
    weighted_ret = combined['weighted_ret'].sum()
    combined['weighted_vol'] = combined[year + ' weights'] * combined[year + ' volatility']
    weighted_vol = combined['weighted_vol'].sum()
    combined['weighted_dd'] = combined[year + ' weights'] * combined[year + ' drawdown']
    weighted_dd = combined['weighted_dd'].sum()
    cbm_proxy_mod_returns.loc[year] = [weighted_ret, weighted_vol, weighted_dd]

cbm_proxy_mod_returns
Out[37]:
Modified ISBI CB Proxy Returns Modified ISBI CB Proxy Volatility Modified ISBI CB Proxy Drawdown
FY 2016 0.148 0.144 -0.075
FY 2017 0.357 0.119 -0.032
FY 2018 0.192 0.161 -0.089
FY 2019 0.117 0.183 -0.117
FY 2020 -0.013 0.215 -0.160
FY 2021 0.469 0.165 -0.070
FY 2022 -0.105 0.164 -0.179
FY 2023 0.094 0.200 -0.106

Composite Benchmark Returns & Volatility Comparison¶

Here we compare the ISBI composite benchmark, the proxy composite benchmark, and the modified benchmark returns.

In [38]:
ret_comp = cbm_proxy_returns.copy()
ret_comp['ISBI CB Returns'] = [0.163, 0.040, 0.007, 0.120, 0.074, 0.070, 0.049, 0.219, -0.059, 0.063]
ret_comp['ISBI Fund Returns'] = [0.179, 0.047, -0.008, 0.123, 0.076, 0.071, 0.046, 0.258, -0.063, 0.062]
ret_comp = ret_comp[['ISBI Fund Returns', 'ISBI CB Returns', 
                     'ISBI CB Proxy Returns', 'ISBI CB Proxy Volatility', 'ISBI CB Proxy Drawdown']]
ret_comp = pd.merge(ret_comp, cbm_proxy_mod_returns, left_on = ret_comp.index, right_on = cbm_proxy_mod_returns.index)
ret_comp.set_index('key_0', inplace = True)
ret_comp.index.name = ''
In [39]:
ifr_cagr = (1 + ret_comp['ISBI Fund Returns']).cumprod()
ifr_cagr = (ifr_cagr[-1] / 1) ** (1/8) - 1

icbr_cagr = (1 + ret_comp['ISBI CB Returns']).cumprod()
icbr_cagr = (icbr_cagr[-1] / 1) ** (1/8) - 1

icbpr_cagr = (1 + ret_comp['ISBI CB Proxy Returns']).cumprod()
icbpr_cagr = (icbpr_cagr[-1] / 1) ** (1/8) - 1

icbpr_anvol = ret_comp['ISBI CB Proxy Volatility'].mean()

micbpr_cagr = (1 + ret_comp['Modified ISBI CB Proxy Returns']).cumprod()
micbpr_cagr = (micbpr_cagr[-1] / 1) ** (1/8) - 1

micbpr_anvol = ret_comp['Modified ISBI CB Proxy Volatility'].mean()

ret_comp.loc['CAGR'] = ['','','','','','','','']
ret_comp.loc['Mean Vol'] = ['','','','','','','','']

ret_comp.at['CAGR', 'ISBI Fund Returns'] = ifr_cagr
ret_comp.at['CAGR', 'ISBI CB Returns'] = icbr_cagr
ret_comp.at['CAGR', 'ISBI CB Proxy Returns'] = icbpr_cagr
ret_comp.at['CAGR', 'Modified ISBI CB Proxy Returns'] = micbpr_cagr
ret_comp.at['Mean Vol', 'ISBI CB Proxy Volatility'] = icbpr_anvol
ret_comp.at['Mean Vol', 'Modified ISBI CB Proxy Volatility'] = micbpr_anvol

ret_comp
Out[39]:
ISBI Fund Returns ISBI CB Returns ISBI CB Proxy Returns ISBI CB Proxy Volatility ISBI CB Proxy Drawdown Modified ISBI CB Proxy Returns Modified ISBI CB Proxy Volatility Modified ISBI CB Proxy Drawdown
FY 2016 -0.008 0.007 0.008 0.104 -0.063 0.148 0.144 -0.075
FY 2017 0.123 0.120 0.127 0.055 -0.026 0.357 0.119 -0.032
FY 2018 0.076 0.074 0.055 0.059 -0.044 0.192 0.161 -0.089
FY 2019 0.071 0.070 0.061 0.115 -0.073 0.117 0.183 -0.117
FY 2020 0.046 0.049 0.001 0.174 -0.140 -0.013 0.215 -0.160
FY 2021 0.258 0.219 0.235 0.096 -0.037 0.469 0.165 -0.070
FY 2022 -0.063 -0.059 -0.072 0.108 -0.130 -0.105 0.164 -0.179
FY 2023 0.062 0.063 0.050 0.164 -0.088 0.094 0.200 -0.106
CAGR 0.067 0.065 0.055 0.145
Mean Vol 0.110 0.169

Portfolio Weighting Analysis - BTC¶

Here we pull in the composite benchmark data and rescale to allow for an allocation to additional asset classes.

In [65]:
btc_weights = [(0.01, 0.01, '1%'), (0.02, 0.02, '2%'), (0.031, 0.03, '3%'), (0.042, 0.04, '4%'), (0.053, 0.05, '5%'), 
               (0.064, 0.06, '6%'), (0.075, 0.07, '7%'), (0.087, 0.08, '8%'), (0.099, 0.09, '9%'), (0.111, 0.10, '10%')]
# btc_weights = [(0.01, 0.01), (0.031, 0.03), (0.053, 0.05), (0.075, 0.07)]

ret_comp_all = pd.DataFrame(columns = ['ISBI CB Proxy CAGR', 'ISBI CB Proxy Mean Annual Volatility', 'ISBI CB Proxy Max Drawdown',
                                       'Modified ISBI CB Proxy CAGR', 'Modified ISBI CB Proxy Mean Annual Volatility',
                                       'Modified ISBI CB Proxy Max Drawdown'])

fy_index = ['FY 2016', 'FY 2017', 'FY 2018', 'FY 2019', 'FY 2020', 'FY 2021', 'FY 2022', 'FY 2023']
cum_ret = pd.DataFrame(index = fy_index)

for weight, size, percent in btc_weights:
    # start with the df for the composite benchmark weights
    cbm_weights_mod = cbm_weights.copy()
    cbm_weights_mod.drop(columns = {'Asset Class', 'Proxy Index', 'Fund Index'}, inplace = True)

    new_asset_class1 = 'BTC'
    allocation_percentage = weight
    cbm_weights_mod.loc[new_asset_class1] = allocation_percentage
    
    # Re-scaling the weightings to ensure they sum up to 1
    total_weight = cbm_weights_mod.sum(axis=0)
    cbm_weights_mod = cbm_weights_mod.div(total_weight)

    cbm_proxy_mod_returns = pd.DataFrame(columns = ['Modified ISBI CB Proxy Returns', 'Modified ISBI CB Proxy Volatility', 'Modified ISBI CB Proxy Drawdown'])

    for year in fy_index:
        combined = pd.merge(cbm_weights_mod[year], combined_returns[year], left_on = 'Ticker', right_on = 'Ticker')
        combined.rename(columns = {year + '_x' : year + ' weights', year + '_y': year + ' returns'}, inplace = True)
        combined = pd.merge(combined, combined_vol[year], left_on = 'Ticker', right_on = 'Ticker')
        combined.rename(columns = {year:year + ' volatility'}, inplace = True)
        combined = pd.merge(combined, combined_dd[year], left_on = 'Ticker', right_on = 'Ticker')
        combined.rename(columns = {year:year + ' drawdown'}, inplace = True)
        combined['weighted_ret'] = combined[year + ' weights'] * combined[year + ' returns']
        weighted_ret = combined['weighted_ret'].sum()
        combined['weighted_vol'] = combined[year + ' weights'] * combined[year + ' volatility']
        weighted_vol = combined['weighted_vol'].sum()
        combined['weighted_dd'] = combined[year + ' weights'] * combined[year + ' drawdown']
        weighted_dd = combined['weighted_dd'].sum()
        cbm_proxy_mod_returns.loc[year] = [weighted_ret, weighted_vol, weighted_dd]

    ret_comp = pd.merge(cbm_proxy_returns, cbm_proxy_mod_returns, left_on = cbm_proxy_returns.index, right_on = cbm_proxy_mod_returns.index)
    ret_comp.set_index('key_0', inplace = True)
    ret_comp.index.name = ''

    icbpr_cagr = (1 + ret_comp['ISBI CB Proxy Returns']).cumprod()
    icbpr_cagr = (icbpr_cagr[-1] / 1) ** (1/8) - 1
    
    icbpr_anvol = ret_comp['ISBI CB Proxy Volatility'].mean()

    icbpr_dd = ret_comp['ISBI CB Proxy Drawdown'].min()
        
    micbpr_cagr = (1 + ret_comp['Modified ISBI CB Proxy Returns']).cumprod()
    micbpr_cagr = (micbpr_cagr[-1] / 1) ** (1/8) - 1
    
    micbpr_anvol = ret_comp['Modified ISBI CB Proxy Volatility'].mean()

    micbpr_dd = ret_comp['Modified ISBI CB Proxy Drawdown'].min()

    ret_comp_all.loc['BTC ' + str(size)] = [icbpr_cagr, icbpr_anvol, icbpr_dd, micbpr_cagr, micbpr_anvol, micbpr_dd]

    cum_ret = pd.concat([cum_ret, ret_comp['Modified ISBI CB Proxy Returns']], axis = 1)
    cum_ret.rename(columns = {'Modified ISBI CB Proxy Returns':'ISBI CB + ' + percent + ' BTC'}, inplace = True)

cum_ret = pd.concat([cum_ret, ret_comp['ISBI CB Proxy Returns']], axis = 1)
    
ret_comp_all.insert(2, 'ISBI CB Proxy Sharpe Ratio', ret_comp_all['ISBI CB Proxy CAGR'] / ret_comp_all['ISBI CB Proxy Mean Annual Volatility'])
ret_comp_all.insert(5, 'Modified ISBI CB Proxy Sharpe Ratio', ret_comp_all['Modified ISBI CB Proxy CAGR'] / ret_comp_all['Modified ISBI CB Proxy Mean Annual Volatility'])

ret_comp_all
Out[65]:
ISBI CB Proxy CAGR ISBI CB Proxy Mean Annual Volatility ISBI CB Proxy Sharpe Ratio ISBI CB Proxy Max Drawdown Modified ISBI CB Proxy CAGR Modified ISBI CB Proxy Sharpe Ratio Modified ISBI CB Proxy Mean Annual Volatility Modified ISBI CB Proxy Max Drawdown
BTC 0.01 0.055 0.110 0.499 -0.140 0.065 0.559 0.116 -0.142
BTC 0.02 0.055 0.110 0.499 -0.140 0.075 0.611 0.122 -0.144
BTC 0.03 0.055 0.110 0.499 -0.140 0.085 0.661 0.129 -0.146
BTC 0.04 0.055 0.110 0.499 -0.140 0.096 0.704 0.136 -0.152
BTC 0.05 0.055 0.110 0.499 -0.140 0.106 0.742 0.143 -0.158
BTC 0.06 0.055 0.110 0.499 -0.140 0.116 0.775 0.149 -0.163
BTC 0.07 0.055 0.110 0.499 -0.140 0.125 0.804 0.155 -0.168
BTC 0.08 0.055 0.110 0.499 -0.140 0.135 0.832 0.162 -0.174
BTC 0.09 0.055 0.110 0.499 -0.140 0.145 0.857 0.169 -0.179
BTC 0.1 0.055 0.110 0.499 -0.140 0.154 0.879 0.175 -0.185
In [66]:
final_df = pd.DataFrame()
# CAGR
final_df.loc['ISBI CB Proxy', 'CAGR'] = ret_comp_all.loc['BTC 0.03', 'ISBI CB Proxy CAGR']
final_df.loc['ISBI CB + 1% BTC', 'CAGR'] = ret_comp_all.loc['BTC 0.01', 'Modified ISBI CB Proxy CAGR']
final_df.loc['ISBI CB + 2% BTC', 'CAGR'] = ret_comp_all.loc['BTC 0.02', 'Modified ISBI CB Proxy CAGR']
final_df.loc['ISBI CB + 3% BTC', 'CAGR'] = ret_comp_all.loc['BTC 0.03', 'Modified ISBI CB Proxy CAGR']
final_df.loc['ISBI CB + 4% BTC', 'CAGR'] = ret_comp_all.loc['BTC 0.04', 'Modified ISBI CB Proxy CAGR']
final_df.loc['ISBI CB + 5% BTC', 'CAGR'] = ret_comp_all.loc['BTC 0.05', 'Modified ISBI CB Proxy CAGR']
final_df.loc['ISBI CB + 6% BTC', 'CAGR'] = ret_comp_all.loc['BTC 0.06', 'Modified ISBI CB Proxy CAGR']
final_df.loc['ISBI CB + 7% BTC', 'CAGR'] = ret_comp_all.loc['BTC 0.07', 'Modified ISBI CB Proxy CAGR']
final_df.loc['ISBI CB + 8% BTC', 'CAGR'] = ret_comp_all.loc['BTC 0.08', 'Modified ISBI CB Proxy CAGR']
final_df.loc['ISBI CB + 9% BTC', 'CAGR'] = ret_comp_all.loc['BTC 0.09', 'Modified ISBI CB Proxy CAGR']
final_df.loc['ISBI CB + 10% BTC', 'CAGR'] = ret_comp_all.loc['BTC 0.1', 'Modified ISBI CB Proxy CAGR']

# vol
final_df.loc['ISBI CB Proxy', 'Mean Annual Volatility'] = ret_comp_all.loc['BTC 0.03', 'ISBI CB Proxy Mean Annual Volatility']
final_df.loc['ISBI CB + 1% BTC', 'Mean Annual Volatility'] = ret_comp_all.loc['BTC 0.01', 'Modified ISBI CB Proxy Mean Annual Volatility']
final_df.loc['ISBI CB + 2% BTC', 'Mean Annual Volatility'] = ret_comp_all.loc['BTC 0.02', 'Modified ISBI CB Proxy Mean Annual Volatility']
final_df.loc['ISBI CB + 3% BTC', 'Mean Annual Volatility'] = ret_comp_all.loc['BTC 0.03', 'Modified ISBI CB Proxy Mean Annual Volatility']
final_df.loc['ISBI CB + 4% BTC', 'Mean Annual Volatility'] = ret_comp_all.loc['BTC 0.04', 'Modified ISBI CB Proxy Mean Annual Volatility']
final_df.loc['ISBI CB + 5% BTC', 'Mean Annual Volatility'] = ret_comp_all.loc['BTC 0.05', 'Modified ISBI CB Proxy Mean Annual Volatility']
final_df.loc['ISBI CB + 6% BTC', 'Mean Annual Volatility'] = ret_comp_all.loc['BTC 0.06', 'Modified ISBI CB Proxy Mean Annual Volatility']
final_df.loc['ISBI CB + 7% BTC', 'Mean Annual Volatility'] = ret_comp_all.loc['BTC 0.07', 'Modified ISBI CB Proxy Mean Annual Volatility']
final_df.loc['ISBI CB + 8% BTC', 'Mean Annual Volatility'] = ret_comp_all.loc['BTC 0.08', 'Modified ISBI CB Proxy Mean Annual Volatility']
final_df.loc['ISBI CB + 9% BTC', 'Mean Annual Volatility'] = ret_comp_all.loc['BTC 0.09', 'Modified ISBI CB Proxy Mean Annual Volatility']
final_df.loc['ISBI CB + 10% BTC', 'Mean Annual Volatility'] = ret_comp_all.loc['BTC 0.1', 'Modified ISBI CB Proxy Mean Annual Volatility']

# sharpe
final_df.loc['ISBI CB Proxy', 'Sharpe Ratio'] = ret_comp_all.loc['BTC 0.03', 'ISBI CB Proxy Sharpe Ratio']
final_df.loc['ISBI CB + 1% BTC', 'Sharpe Ratio'] = ret_comp_all.loc['BTC 0.01', 'Modified ISBI CB Proxy Sharpe Ratio']
final_df.loc['ISBI CB + 2% BTC', 'Sharpe Ratio'] = ret_comp_all.loc['BTC 0.02', 'Modified ISBI CB Proxy Sharpe Ratio']
final_df.loc['ISBI CB + 3% BTC', 'Sharpe Ratio'] = ret_comp_all.loc['BTC 0.03', 'Modified ISBI CB Proxy Sharpe Ratio']
final_df.loc['ISBI CB + 4% BTC', 'Sharpe Ratio'] = ret_comp_all.loc['BTC 0.04', 'Modified ISBI CB Proxy Sharpe Ratio']
final_df.loc['ISBI CB + 5% BTC', 'Sharpe Ratio'] = ret_comp_all.loc['BTC 0.05', 'Modified ISBI CB Proxy Sharpe Ratio']
final_df.loc['ISBI CB + 6% BTC', 'Sharpe Ratio'] = ret_comp_all.loc['BTC 0.06', 'Modified ISBI CB Proxy Sharpe Ratio']
final_df.loc['ISBI CB + 7% BTC', 'Sharpe Ratio'] = ret_comp_all.loc['BTC 0.07', 'Modified ISBI CB Proxy Sharpe Ratio']
final_df.loc['ISBI CB + 8% BTC', 'Sharpe Ratio'] = ret_comp_all.loc['BTC 0.08', 'Modified ISBI CB Proxy Sharpe Ratio']
final_df.loc['ISBI CB + 9% BTC', 'Sharpe Ratio'] = ret_comp_all.loc['BTC 0.09', 'Modified ISBI CB Proxy Sharpe Ratio']
final_df.loc['ISBI CB + 10% BTC', 'Sharpe Ratio'] = ret_comp_all.loc['BTC 0.1', 'Modified ISBI CB Proxy Sharpe Ratio']

# drawdown
final_df.loc['ISBI CB Proxy', 'Max Drawdown'] = ret_comp_all.loc['BTC 0.03', 'ISBI CB Proxy Max Drawdown']
final_df.loc['ISBI CB + 1% BTC', 'Max Drawdown'] = ret_comp_all.loc['BTC 0.01', 'Modified ISBI CB Proxy Max Drawdown']
final_df.loc['ISBI CB + 2% BTC', 'Max Drawdown'] = ret_comp_all.loc['BTC 0.02', 'Modified ISBI CB Proxy Max Drawdown']
final_df.loc['ISBI CB + 3% BTC', 'Max Drawdown'] = ret_comp_all.loc['BTC 0.03', 'Modified ISBI CB Proxy Max Drawdown']
final_df.loc['ISBI CB + 4% BTC', 'Max Drawdown'] = ret_comp_all.loc['BTC 0.04', 'Modified ISBI CB Proxy Max Drawdown']
final_df.loc['ISBI CB + 5% BTC', 'Max Drawdown'] = ret_comp_all.loc['BTC 0.05', 'Modified ISBI CB Proxy Max Drawdown']
final_df.loc['ISBI CB + 6% BTC', 'Max Drawdown'] = ret_comp_all.loc['BTC 0.06', 'Modified ISBI CB Proxy Max Drawdown']
final_df.loc['ISBI CB + 7% BTC', 'Max Drawdown'] = ret_comp_all.loc['BTC 0.07', 'Modified ISBI CB Proxy Max Drawdown']
final_df.loc['ISBI CB + 8% BTC', 'Max Drawdown'] = ret_comp_all.loc['BTC 0.08', 'Modified ISBI CB Proxy Max Drawdown']
final_df.loc['ISBI CB + 9% BTC', 'Max Drawdown'] = ret_comp_all.loc['BTC 0.09', 'Modified ISBI CB Proxy Max Drawdown']
final_df.loc['ISBI CB + 10% BTC', 'Max Drawdown'] = ret_comp_all.loc['BTC 0.1', 'Modified ISBI CB Proxy Max Drawdown']
In [67]:
final_df
Out[67]:
CAGR Mean Annual Volatility Sharpe Ratio Max Drawdown
ISBI CB Proxy 0.055 0.110 0.499 -0.140
ISBI CB + 1% BTC 0.065 0.116 0.559 -0.142
ISBI CB + 2% BTC 0.075 0.122 0.611 -0.144
ISBI CB + 3% BTC 0.085 0.129 0.661 -0.146
ISBI CB + 4% BTC 0.096 0.136 0.704 -0.152
ISBI CB + 5% BTC 0.106 0.143 0.742 -0.158
ISBI CB + 6% BTC 0.116 0.149 0.775 -0.163
ISBI CB + 7% BTC 0.125 0.155 0.804 -0.168
ISBI CB + 8% BTC 0.135 0.162 0.832 -0.174
ISBI CB + 9% BTC 0.145 0.169 0.857 -0.179
ISBI CB + 10% BTC 0.154 0.175 0.879 -0.185
In [68]:
final_df['CAGR Percent Change'] = final_df['CAGR'].pct_change()
final_df['Volatility Percent Change'] = final_df['Mean Annual Volatility'].pct_change()
final_df['Sharpe Percent Change'] = final_df['Sharpe Ratio'].pct_change()
In [69]:
final_df
Out[69]:
CAGR Mean Annual Volatility Sharpe Ratio Max Drawdown CAGR Percent Change Volatility Percent Change Sharpe Percent Change
ISBI CB Proxy 0.055 0.110 0.499 -0.140 NaN NaN NaN
ISBI CB + 1% BTC 0.065 0.116 0.559 -0.142 0.187 0.060 0.120
ISBI CB + 2% BTC 0.075 0.122 0.611 -0.144 0.153 0.055 0.093
ISBI CB + 3% BTC 0.085 0.129 0.661 -0.146 0.142 0.056 0.081
ISBI CB + 4% BTC 0.096 0.136 0.704 -0.152 0.121 0.052 0.065
ISBI CB + 5% BTC 0.106 0.143 0.742 -0.158 0.104 0.049 0.053
ISBI CB + 6% BTC 0.116 0.149 0.775 -0.163 0.092 0.045 0.045
ISBI CB + 7% BTC 0.125 0.155 0.804 -0.168 0.082 0.042 0.038
ISBI CB + 8% BTC 0.135 0.162 0.832 -0.174 0.080 0.044 0.035
ISBI CB + 9% BTC 0.145 0.169 0.857 -0.179 0.072 0.041 0.030
ISBI CB + 10% BTC 0.154 0.175 0.879 -0.185 0.065 0.038 0.026
In [70]:
# Plotting the percentage change
plt.figure(figsize=(10, 5), facecolor = '#F5F5F5')

plt.plot(final_df['Sharpe Ratio'], label = 'Sharpe Ratio', marker='o', linestyle='-')

# Set X axis
plt.xlabel('Scenario')
plt.xticks(rotation = 45, fontsize = 9)

# Set Y axis
# plt.gca().yaxis.set_major_formatter(FuncFormatter(percentage)) # Formatting y-axis as percentage
plt.ylabel('Sharpe Ratio')
plt.yticks(fontsize = 9)

# Set title, etc.
plt.title('Sharpe Ratio With BTC Allocation', fontsize = 12)

# Display the plot
plt.grid(True)
plt.legend(fontsize = 8)
plt.tight_layout()
plt.show()
No description has been provided for this image
In [71]:
# Plotting the percentage change
plt.figure(figsize=(10, 5), facecolor = '#F5F5F5')

plt.plot(final_df['CAGR Percent Change'], label = 'CAGR Percent Change', marker='o', linestyle='-')
plt.plot(final_df['Volatility Percent Change'], label = 'Volatility Percent Change', marker='o', linestyle='-')
plt.plot(final_df['Sharpe Percent Change'], label = 'Sharpe Percent Change', marker='o', linestyle='-')

# Set X axis
plt.xlabel('Scenario')
plt.xticks(rotation = 45, fontsize = 9)

# Set Y axis
plt.gca().yaxis.set_major_formatter(FuncFormatter(percentage)) # Formatting y-axis as percentage
plt.ylabel('Percentage Change')
plt.yticks(fontsize = 9)

# Set title, etc.
plt.title('Percentage Change Of Sharpe Ratio With BTC Allocation', fontsize = 12)

# Display the plot
plt.grid(True)
plt.legend(fontsize = 8)
plt.tight_layout()
plt.show()
No description has been provided for this image

Cumulative Returns - BTC¶

In [72]:
cum_ret['ISBI CB Proxy Returns CR'] = (1 + cum_ret['ISBI CB Proxy Returns']).cumprod()
cum_ret['ISBI CB + 1% BTC CR'] = (1 + cum_ret['ISBI CB + 1% BTC']).cumprod()
cum_ret['ISBI CB + 2% BTC CR'] = (1 + cum_ret['ISBI CB + 2% BTC']).cumprod()
cum_ret['ISBI CB + 3% BTC CR'] = (1 + cum_ret['ISBI CB + 3% BTC']).cumprod()
cum_ret['ISBI CB + 4% BTC CR'] = (1 + cum_ret['ISBI CB + 4% BTC']).cumprod()
cum_ret['ISBI CB + 5% BTC CR'] = (1 + cum_ret['ISBI CB + 5% BTC']).cumprod()
cum_ret['ISBI CB + 6% BTC CR'] = (1 + cum_ret['ISBI CB + 6% BTC']).cumprod()
cum_ret['ISBI CB + 7% BTC CR'] = (1 + cum_ret['ISBI CB + 7% BTC']).cumprod()
cum_ret['ISBI CB + 8% BTC CR'] = (1 + cum_ret['ISBI CB + 8% BTC']).cumprod()
cum_ret['ISBI CB + 9% BTC CR'] = (1 + cum_ret['ISBI CB + 9% BTC']).cumprod()
cum_ret['ISBI CB + 10% BTC CR'] = (1 + cum_ret['ISBI CB + 10% BTC']).cumprod()
# cum_ret = cum_ret[['ISBI CB Proxy Returns CR', 'ISBI CB + 3% BTC CR', 'ISBI CB + 5% BTC CR', 'ISBI CB + 7% BTC CR', 'ISBI CB + 10% BTC CR']]
In [73]:
cum_ret
Out[73]:
ISBI CB + 1% BTC ISBI CB + 2% BTC ISBI CB + 3% BTC ISBI CB + 4% BTC ISBI CB + 5% BTC ISBI CB + 6% BTC ISBI CB + 7% BTC ISBI CB + 8% BTC ISBI CB + 9% BTC ISBI CB + 10% BTC ... ISBI CB + 1% BTC CR ISBI CB + 2% BTC CR ISBI CB + 3% BTC CR ISBI CB + 4% BTC CR ISBI CB + 5% BTC CR ISBI CB + 6% BTC CR ISBI CB + 7% BTC CR ISBI CB + 8% BTC CR ISBI CB + 9% BTC CR ISBI CB + 10% BTC CR
FY 2016 0.024 0.039 0.055 0.071 0.086 0.102 0.116 0.132 0.148 0.163 ... 1.024 1.039 1.055 1.071 1.086 1.102 1.116 1.132 1.148 1.163
FY 2017 0.152 0.177 0.203 0.230 0.255 0.280 0.305 0.331 0.357 0.382 ... 1.179 1.222 1.269 1.317 1.364 1.410 1.457 1.507 1.558 1.608
FY 2018 0.070 0.085 0.101 0.116 0.132 0.147 0.161 0.177 0.192 0.207 ... 1.262 1.326 1.397 1.470 1.543 1.617 1.692 1.774 1.857 1.941
FY 2019 0.067 0.073 0.080 0.086 0.093 0.099 0.105 0.111 0.117 0.124 ... 1.346 1.423 1.509 1.597 1.686 1.777 1.869 1.972 2.076 2.181
FY 2020 -0.000 -0.002 -0.003 -0.005 -0.007 -0.008 -0.010 -0.011 -0.013 -0.014 ... 1.346 1.420 1.504 1.589 1.675 1.763 1.851 1.950 2.049 2.150
FY 2021 0.261 0.286 0.313 0.340 0.366 0.392 0.417 0.443 0.469 0.495 ... 1.697 1.827 1.975 2.129 2.288 2.453 2.623 2.814 3.011 3.214
FY 2022 -0.076 -0.080 -0.083 -0.087 -0.091 -0.094 -0.098 -0.101 -0.105 -0.109 ... 1.568 1.682 1.811 1.944 2.081 2.222 2.366 2.528 2.695 2.865
FY 2023 0.055 0.059 0.065 0.070 0.075 0.079 0.084 0.089 0.094 0.099 ... 1.654 1.782 1.928 2.079 2.236 2.398 2.565 2.754 2.948 3.148

8 rows × 22 columns

In [74]:
# Create the initial plot
plt.figure(figsize=(10, 5), facecolor = '#F5F5F5')

# Plot the data
plt.plot(cum_ret.index, cum_ret['ISBI CB Proxy Returns CR'], label = 'ISBI CB Proxy', linestyle='-', color='b', linewidth=2)
plt.plot(cum_ret.index, cum_ret['ISBI CB + 1% BTC CR'], label = 'ISBI CB + 1% BTC', linestyle='-', color='g', linewidth=2)
plt.plot(cum_ret.index, cum_ret['ISBI CB + 2% BTC CR'], label = 'ISBI CB + 2% BTC', linestyle='-', color='r', linewidth=2)
plt.plot(cum_ret.index, cum_ret['ISBI CB + 3% BTC CR'], label = 'ISBI CB + 3% BTC', linestyle='-', color='orange', linewidth=2)
plt.plot(cum_ret.index, cum_ret['ISBI CB + 4% BTC CR'], label = 'ISBI CB + 4% BTC', linestyle='-', color='purple', linewidth=2)
plt.plot(cum_ret.index, cum_ret['ISBI CB + 5% BTC CR'], label = 'ISBI CB + 5% BTC', linestyle='-', color='gray', linewidth=2)
plt.plot(cum_ret.index, cum_ret['ISBI CB + 6% BTC CR'], label = 'ISBI CB + 6% BTC', linestyle='-', color='lightblue', linewidth=2)
plt.plot(cum_ret.index, cum_ret['ISBI CB + 7% BTC CR'], label = 'ISBI CB + 7% BTC', linestyle='-', color='lightgreen', linewidth=2)
plt.plot(cum_ret.index, cum_ret['ISBI CB + 8% BTC CR'], label = 'ISBI CB + 8% BTC', linestyle='-', color='darkred', linewidth=2)
plt.plot(cum_ret.index, cum_ret['ISBI CB + 9% BTC CR'], label = 'ISBI CB + 9% BTC', linestyle='-', color='yellow', linewidth=2)
plt.plot(cum_ret.index, cum_ret['ISBI CB + 10% BTC CR'], label = 'ISBI CB + 10% BTC', linestyle='-', color='black', linewidth=2)

# Set X axis
plt.xlabel('Fiscal Year')
plt.xticks(rotation = 0, fontsize = 9)

# Set Y axis
plt.gca().yaxis.set_major_formatter(FuncFormatter(percentage)) # Formatting y-axis as percentage
plt.ylabel('Cumulative Return Percentage')
plt.yticks(fontsize = 9)

# Set title, etc.
plt.title('Cumulative Return Comparison', fontsize = 12)
plt.tight_layout()
plt.legend(fontsize = 8)
plt.grid(True)
plt.show()
No description has been provided for this image

Mean Variance Optimization - BTC¶

In [84]:
all_fund_mon_ret = iwv.copy()
all_fund_mon_ret['IWV'] = all_fund_mon_ret['adj_close'].pct_change()
all_fund_mon_ret.drop(columns = {'adj_close'}, inplace = True)

index_dfs = [(efa, 'EFA'), (eem, 'EEM'), (acwx, 'ACWX'), (agg, 'AGG'), (spti, 'SPTI'), (vglt, 'VGLT'), (tip, 'TIP'), 
             (LF98TRUU, 'LF98TRUU'), (SPBDLLB, 'SPBDLLB'), (NPPI0DIV, 'NPPI0DIV'), (LPX50TR, 'LPX50TR'),
             (emb, 'EMB'), (hdg, 'HDG'),(psp, 'PSP'), (cpi, 'CPI'), (gsg, 'GSG'), (gld, 'GLD'), (btc, 'BTC')]

# index_dfs = [(efa, 'iShares MSCI EAFE ETF (EFA)'),
#              (eem, 'iShares MSCI Emerging Markets ETF (EEM)'), 
#              (acwx, 'iShares MSCI ACWI ex U.S. ETF (ACWX)'), 
#              (agg, 'US Aggregate Bond (AGG)'), 
#              (spti, 'SPDR Portfolio Intermediate Term Treasury ETF (SPTI)'),
#              (vglt, 'Vanguard Long-Term Treasury ETF (VGLT)'),
#              (tip, 'iShares TIPS Bond ETF (TIP)'), 
#              (LF98TRUU, 'Bloomberg US Corp HY TR Index (LF98TRUU)'),
#              (SPBDLLB, 'Morningstar LSTA US Leveraged Loan 100 Index (SPBDLLB)'), 
#              (NPPI0DIV, 'NCREIF Fund Index Open End Diversified Core (NPPI0DIV)'),
#              (LPX50TR, 'LPX Listed Private Equity Index TR (LPX50TR)'),
#              (emb, 'iShares J.P. Morgan USD Emerging Markets Bond ETF (EMB)'),
#              (hdg, 'ProShares Hedge Replication ETF (HDG)'),
#              (psp, 'Invesco Global Listed Private Equity ETF (PSP)'), 
#              (cpi, 'CPI'), 
#              (gsg, 'iShares S&P GSCI Commodity-Indexed Trust (GSG)'), 
#              (gld, 'SPDR Gold Shares ETF (GLD)'), 
#              (btc, 'Bitcoin Total Return (BTCUSD)')]

for df, name in index_dfs:
    corr_df = df.copy()
    corr_df[name] = corr_df['adj_close'].pct_change()
    corr_df.drop(columns = {'adj_close'}, inplace = True)
    try:
        corr_df.drop(columns = {'adj_close_orig'}, inplace = True)
    except:
        pass
    all_fund_mon_ret = pd.merge(all_fund_mon_ret, corr_df, left_on = 'Date', right_on = 'Date')

year = 'FY 2023'

cbm_weights_mod_mvo = cbm_weights_mod.copy()
# cbm_weights_mod_mvo.drop(columns = {'Asset Class', 'Fund Index', 'Proxy Index'}, inplace = True)
cbm_weights_mod_mvo = cbm_weights_mod_mvo[[year]]
cbm_weights_mod_mvo = cbm_weights_mod_mvo[cbm_weights_mod_mvo[year] != 0]

all_fund_mon_ret_mvo = all_fund_mon_ret.copy()
columns_to_keep = cbm_weights_mod_mvo.index
all_fund_mon_ret_mvo_filtered = all_fund_mon_ret_mvo.loc[:, all_fund_mon_ret_mvo.columns.isin(columns_to_keep)]
all_fund_mon_ret_mvo_filtered
Out[84]:
IWV EFA EEM AGG SPTI VGLT TIP LF98TRUU SPBDLLB NPPI0DIV LPX50TR CPI BTC
Date
2014-09-30 -0.021 -0.039 -0.078 -0.006 -0.004 -0.020 -0.026 -0.021 -0.013 0.011 -0.020 0.000 NaN
2014-10-31 0.027 -0.003 0.014 0.009 0.006 0.025 0.009 0.012 0.002 0.011 0.028 -0.000 -0.126
2014-11-30 0.025 0.001 -0.015 0.008 0.004 0.027 0.002 -0.007 0.000 0.011 -0.016 -0.002 0.117
2014-12-31 -0.000 -0.040 -0.040 0.001 -0.004 0.031 -0.011 -0.014 -0.019 0.011 0.007 -0.003 -0.153
2015-01-31 -0.027 0.006 -0.007 0.021 0.016 0.087 0.032 0.007 0.001 0.011 0.041 -0.006 -0.321
... ... ... ... ... ... ... ... ... ... ... ... ... ...
2023-02-28 -0.024 -0.031 -0.076 -0.029 -0.027 -0.047 -0.014 -0.013 -0.005 -0.017 -0.096 0.004 0.000
2023-03-31 0.024 0.031 0.032 0.024 0.028 0.047 0.029 0.011 -0.008 -0.011 0.105 0.001 0.230
2023-04-30 0.011 0.029 -0.008 0.006 0.007 0.005 0.001 0.010 0.005 -0.011 0.021 0.004 0.028
2023-05-31 0.004 -0.040 -0.024 -0.011 -0.010 -0.028 -0.012 -0.009 -0.014 -0.011 -0.069 0.001 -0.070
2023-06-30 0.068 0.045 0.044 -0.004 -0.013 0.000 -0.003 0.017 0.018 -0.009 0.013 0.002 0.120

106 rows × 13 columns

In [85]:
returns_df = all_fund_mon_ret_mvo_filtered
In [86]:
import numpy as np
import pandas as pd
import scipy.optimize as sco
import matplotlib.pyplot as plt

# Assuming you have a DataFrame called returns_df
# returns_df = pd.read_csv('path_to_your_file.csv')

# Calculate the mean returns and the covariance matrix
mean_returns = returns_df.mean()
cov_matrix = returns_df.cov()

# Define the number of assets
num_assets = len(mean_returns)

# Function to calculate portfolio statistics
def portfolio_statistics(weights, mean_returns, cov_matrix):
    portfolio_return = np.sum(mean_returns * weights) * 12  # Annualize return
    portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) * np.sqrt(12)  # Annualize volatility
    return portfolio_return, portfolio_volatility

# Function to minimize (negative Sharpe Ratio)
def negative_sharpe_ratio(weights, mean_returns, cov_matrix, risk_free_rate):
    p_return, p_volatility = portfolio_statistics(weights, mean_returns, cov_matrix)
    return -(p_return - risk_free_rate) / p_volatility

# Constraints and bounds
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})

# Define custom bounds for each asset
custom_bounds = [(0.01, 0.2) for asset in range(0, num_assets - 2)] + [(0, 0), (0.01, 0.2)]

# Initial guess (equal weights)
init_guess = num_assets * [1. / num_assets]

# Risk-free rate (assumed to be zero here, adjust as necessary)
risk_free_rate = 0.0

# Optimize the portfolio
opt_result = sco.minimize(negative_sharpe_ratio, init_guess, args=(mean_returns, cov_matrix, risk_free_rate),
                          method='SLSQP', bounds=custom_bounds, constraints=constraints)

# Get the optimal weights
optimal_weights = opt_result.x

# Calculate the optimized portfolio statistics
optimized_return, optimized_volatility = portfolio_statistics(optimal_weights, mean_returns, cov_matrix)

# Generate portfolios for the efficient frontier
num_portfolios = 100
results = np.zeros((3, num_portfolios))

for i in range(num_portfolios):
    weights = np.random.random(num_assets)
    weights /= np.sum(weights)
    portfolio_return, portfolio_volatility = portfolio_statistics(weights, mean_returns, cov_matrix)
    results[0,i] = portfolio_volatility
    results[1,i] = portfolio_return
    results[2,i] = (portfolio_return - risk_free_rate) / portfolio_volatility

# Plot the Efficient Frontier
# Plotting the percentage change
plt.figure(figsize=(10, 5), facecolor = '#F5F5F5')

plt.scatter(results[0,:], results[1,:], c=results[2,:], cmap='viridis', marker='o')
plt.colorbar(label='Sharpe Ratio')

# Set X axis
plt.xlabel('Volatility (Annualized)')
plt.xticks(rotation = 0, fontsize = 9)
plt.xlim(0, 0.25)

# Set Y axis
plt.ylabel('Return (Annualized)')
plt.yticks(fontsize = 9)
plt.ylim(0, 0.20)


# Set title, etc.
plt.title('Efficient Frontier Using FY 2023 Benchmark Funds - Including Bitcoin (BTC)', fontsize = 12)
plt.scatter(optimized_volatility, optimized_return, marker='*', color='r', s=100, label='Optimal Portfolio')

# Display the plot
plt.grid(True)
plt.tight_layout()
plt.legend(loc='best')
plt.show()

# Print the results
print("Optimal Weights:", optimal_weights)
print("Expected Annual Return:", optimized_return)
print("Expected Annual Volatility:", optimized_volatility)
No description has been provided for this image
Optimal Weights: [0.19286008 0.01       0.01       0.2        0.01       0.01
 0.08947315 0.01       0.01       0.2        0.2        0.
 0.05766677]
Expected Annual Return: 0.11329316015286128
Expected Annual Volatility: 0.08039959720800045
In [87]:
mvo_results = pd.DataFrame(optimal_weights, index = all_fund_mon_ret_mvo_filtered.columns)
mvo_results.rename(columns = {0: 'MVO Weights'}, inplace = True)
mvo_results
Out[87]:
MVO Weights
IWV 0.193
EFA 0.010
EEM 0.010
AGG 0.200
SPTI 0.010
VGLT 0.010
TIP 0.089
LF98TRUU 0.010
SPBDLLB 0.010
NPPI0DIV 0.200
LPX50TR 0.200
CPI 0.000
BTC 0.058

Mean Variance Optimization - Without BTC¶

In [88]:
all_fund_mon_ret = iwv.copy()
all_fund_mon_ret['IWV'] = all_fund_mon_ret['adj_close'].pct_change()
all_fund_mon_ret.drop(columns = {'adj_close'}, inplace = True)

index_dfs = [(efa, 'EFA'), (eem, 'EEM'), (acwx, 'ACWX'), (agg, 'AGG'), (spti, 'SPTI'), (vglt, 'VGLT'), (tip, 'TIP'), 
             (LF98TRUU, 'LF98TRUU'), (SPBDLLB, 'SPBDLLB'), (NPPI0DIV, 'NPPI0DIV'), (LPX50TR, 'LPX50TR'),
             (emb, 'EMB'), (hdg, 'HDG'),(psp, 'PSP'), (cpi, 'CPI'), (gsg, 'GSG'), (gld, 'GLD'), (btc, 'BTC')]

# index_dfs = [(efa, 'iShares MSCI EAFE ETF (EFA)'),
#              (eem, 'iShares MSCI Emerging Markets ETF (EEM)'), 
#              (acwx, 'iShares MSCI ACWI ex U.S. ETF (ACWX)'), 
#              (agg, 'US Aggregate Bond (AGG)'), 
#              (spti, 'SPDR Portfolio Intermediate Term Treasury ETF (SPTI)'),
#              (vglt, 'Vanguard Long-Term Treasury ETF (VGLT)'),
#              (tip, 'iShares TIPS Bond ETF (TIP)'), 
#              (LF98TRUU, 'Bloomberg US Corp HY TR Index (LF98TRUU)'),
#              (SPBDLLB, 'Morningstar LSTA US Leveraged Loan 100 Index (SPBDLLB)'), 
#              (NPPI0DIV, 'NCREIF Fund Index Open End Diversified Core (NPPI0DIV)'),
#              (LPX50TR, 'LPX Listed Private Equity Index TR (LPX50TR)'),
#              (emb, 'iShares J.P. Morgan USD Emerging Markets Bond ETF (EMB)'),
#              (hdg, 'ProShares Hedge Replication ETF (HDG)'),
#              (psp, 'Invesco Global Listed Private Equity ETF (PSP)'), 
#              (cpi, 'CPI'), 
#              (gsg, 'iShares S&P GSCI Commodity-Indexed Trust (GSG)'), 
#              (gld, 'SPDR Gold Shares ETF (GLD)'), 
#              (btc, 'Bitcoin Total Return (BTCUSD)')]

for df, name in index_dfs:
    corr_df = df.copy()
    corr_df[name] = corr_df['adj_close'].pct_change()
    corr_df.drop(columns = {'adj_close'}, inplace = True)
    try:
        corr_df.drop(columns = {'adj_close_orig'}, inplace = True)
    except:
        pass
    all_fund_mon_ret = pd.merge(all_fund_mon_ret, corr_df, left_on = 'Date', right_on = 'Date')

year = 'FY 2023'

cbm_weights_mvo = cbm_weights.copy()
cbm_weights_mvo.drop(columns = {'Asset Class', 'Fund Index', 'Proxy Index'}, inplace = True)
cbm_weights_mvo = cbm_weights_mvo[[year]]
cbm_weights_mvo = cbm_weights_mvo[cbm_weights_mvo[year] != 0]

all_fund_mon_ret_mvo = all_fund_mon_ret.copy()
columns_to_keep = cbm_weights_mvo.index
all_fund_mon_ret_mvo_filtered = all_fund_mon_ret_mvo.loc[:, all_fund_mon_ret_mvo.columns.isin(columns_to_keep)]
all_fund_mon_ret_mvo_filtered
Out[88]:
IWV EFA EEM AGG SPTI VGLT TIP LF98TRUU SPBDLLB NPPI0DIV LPX50TR CPI
Date
2014-09-30 -0.021 -0.039 -0.078 -0.006 -0.004 -0.020 -0.026 -0.021 -0.013 0.011 -0.020 0.000
2014-10-31 0.027 -0.003 0.014 0.009 0.006 0.025 0.009 0.012 0.002 0.011 0.028 -0.000
2014-11-30 0.025 0.001 -0.015 0.008 0.004 0.027 0.002 -0.007 0.000 0.011 -0.016 -0.002
2014-12-31 -0.000 -0.040 -0.040 0.001 -0.004 0.031 -0.011 -0.014 -0.019 0.011 0.007 -0.003
2015-01-31 -0.027 0.006 -0.007 0.021 0.016 0.087 0.032 0.007 0.001 0.011 0.041 -0.006
... ... ... ... ... ... ... ... ... ... ... ... ...
2023-02-28 -0.024 -0.031 -0.076 -0.029 -0.027 -0.047 -0.014 -0.013 -0.005 -0.017 -0.096 0.004
2023-03-31 0.024 0.031 0.032 0.024 0.028 0.047 0.029 0.011 -0.008 -0.011 0.105 0.001
2023-04-30 0.011 0.029 -0.008 0.006 0.007 0.005 0.001 0.010 0.005 -0.011 0.021 0.004
2023-05-31 0.004 -0.040 -0.024 -0.011 -0.010 -0.028 -0.012 -0.009 -0.014 -0.011 -0.069 0.001
2023-06-30 0.068 0.045 0.044 -0.004 -0.013 0.000 -0.003 0.017 0.018 -0.009 0.013 0.002

106 rows × 12 columns

In [89]:
returns_df = all_fund_mon_ret_mvo_filtered
In [90]:
import numpy as np
import pandas as pd
import scipy.optimize as sco
import matplotlib.pyplot as plt

# Assuming you have a DataFrame called returns_df
# returns_df = pd.read_csv('path_to_your_file.csv')

# Calculate the mean returns and the covariance matrix
mean_returns = returns_df.mean()
cov_matrix = returns_df.cov()

# Define the number of assets
num_assets = len(mean_returns)

# Function to calculate portfolio statistics
def portfolio_statistics(weights, mean_returns, cov_matrix):
    portfolio_return = np.sum(mean_returns * weights) * 12  # Annualize return
    portfolio_volatility = np.sqrt(np.dot(weights.T, np.dot(cov_matrix, weights))) * np.sqrt(12)  # Annualize volatility
    return portfolio_return, portfolio_volatility

# Function to minimize (negative Sharpe Ratio)
def negative_sharpe_ratio(weights, mean_returns, cov_matrix, risk_free_rate):
    p_return, p_volatility = portfolio_statistics(weights, mean_returns, cov_matrix)
    return -(p_return - risk_free_rate) / p_volatility

# Constraints and bounds
constraints = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1})

# Define custom bounds for each asset
custom_bounds = [(0.01, 0.2) for asset in range(0, num_assets - 1)] + [(0, 0)]

# Initial guess (equal weights)
init_guess = num_assets * [1. / num_assets]

# Risk-free rate (assumed to be zero here, adjust as necessary)
risk_free_rate = 0.0

# Optimize the portfolio
opt_result = sco.minimize(negative_sharpe_ratio, init_guess, args=(mean_returns, cov_matrix, risk_free_rate),
                          method='SLSQP', bounds=custom_bounds, constraints=constraints)

# Get the optimal weights
optimal_weights = opt_result.x

# Calculate the optimized portfolio statistics
optimized_return, optimized_volatility = portfolio_statistics(optimal_weights, mean_returns, cov_matrix)

# Generate portfolios for the efficient frontier
num_portfolios = 100
results = np.zeros((3, num_portfolios))

for i in range(num_portfolios):
    weights = np.random.random(num_assets)
    weights /= np.sum(weights)
    portfolio_return, portfolio_volatility = portfolio_statistics(weights, mean_returns, cov_matrix)
    results[0,i] = portfolio_volatility
    results[1,i] = portfolio_return
    results[2,i] = (portfolio_return - risk_free_rate) / portfolio_volatility

# Plot the Efficient Frontier
# Plotting the percentage change
plt.figure(figsize=(10, 5), facecolor = '#F5F5F5')

plt.scatter(results[0,:], results[1,:], c=results[2,:], cmap='viridis', marker='o')
plt.colorbar(label='Sharpe Ratio')

# Set X axis
plt.xlabel('Volatility (Annualized)')
plt.xticks(rotation = 0, fontsize = 9)
plt.xlim(0, 0.1)

# Set Y axis
plt.ylabel('Return (Annualized)')
plt.yticks(fontsize = 9)
plt.ylim(0, 0.1)


# Set title, etc.
plt.title('Efficient Frontier Using FY 2023 Benchmark Funds - Not Including Bitcoin (BTC)', fontsize = 12)
plt.scatter(optimized_volatility, optimized_return, marker='*', color='r', s=100, label='Optimal Portfolio')

# Display the plot
plt.grid(True)
plt.tight_layout()
plt.legend(loc='best')
plt.show()

# Print the results
print("Optimal Weights:", optimal_weights)
print("Expected Annual Return:", optimized_return)
print("Expected Annual Volatility:", optimized_volatility)
No description has been provided for this image
Optimal Weights: [0.2        0.01       0.01       0.2        0.01       0.01
 0.10756707 0.06542661 0.01       0.2        0.17700632 0.        ]
Expected Annual Return: 0.06839336519658981
Expected Annual Volatility: 0.05705027760010963
In [91]:
mvo_results = pd.DataFrame(optimal_weights, index = all_fund_mon_ret_mvo_filtered.columns)
mvo_results.rename(columns = {0: 'MVO Weights'}, inplace = True)
mvo_results
Out[91]:
MVO Weights
IWV 0.200
EFA 0.010
EEM 0.010
AGG 0.200
SPTI 0.010
VGLT 0.010
TIP 0.108
LF98TRUU 0.065
SPBDLLB 0.010
NPPI0DIV 0.200
LPX50TR 0.177
CPI 0.000
In [92]:
x = pd.DataFrame()
x.at['MVO Without BTC', 'Expected Return'] = 0.06839336519658981
x.at['MVO Without BTC', 'Expected Volatility'] = 0.05705027760010963
x.at['MVO With BTC', 'Expected Return'] = 0.11329316015286128
x.at['MVO With BTC', 'Expected Volatility'] = 0.08039959720800045

x['Sharpe Ratio'] = x['Expected Return'] / x['Expected Volatility']
In [93]:
x
Out[93]:
Expected Return Expected Volatility Sharpe Ratio
MVO Without BTC 0.068 0.057 1.199
MVO With BTC 0.113 0.080 1.409

BTC Cumulative Returns¶

In [97]:
btc_ret = all_fund_mon_ret[['BTC']]
btc_ret['cum_ret'] = (1 + btc_ret['BTC']).cumprod()
btc_ret
Out[97]:
BTC cum_ret
Date
2014-09-30 NaN NaN
2014-10-31 -0.126 0.874
2014-11-30 0.117 0.977
2014-12-31 -0.153 0.827
2015-01-31 -0.321 0.562
... ... ...
2023-02-28 0.000 59.821
2023-03-31 0.230 73.598
2023-04-30 0.028 75.641
2023-05-31 -0.070 70.345
2023-06-30 0.120 78.764

106 rows × 2 columns

In [98]:
# Plotting the percentage change
plt.figure(figsize=(10, 5), facecolor = '#F5F5F5')

plt.plot(btc_ret['cum_ret'], label = 'BTC Cumulative Return', marker='o', linestyle='-')

# Set X axis
plt.xlabel('Date')
plt.xticks(rotation = 45, fontsize = 9)

# Set Y axis
plt.gca().yaxis.set_major_formatter(FuncFormatter(percentage)) # Formatting y-axis as percentage
plt.ylabel('Cumulative Total Return')
plt.yticks(fontsize = 9)

# Set title, etc.
plt.title('Bitcoin (BTC) Cumulative Total Return', fontsize = 12)

# Display the plot
plt.grid(True)
# plt.legend(fontsize = 8)
plt.tight_layout()
plt.show()
No description has been provided for this image

BTC Drawdown¶

In [99]:
btc_ret['cum_ret']
Out[99]:
Date
2014-09-30      NaN
2014-10-31    0.874
2014-11-30    0.977
2014-12-31    0.827
2015-01-31    0.562
              ...  
2023-02-28   59.821
2023-03-31   73.598
2023-04-30   75.641
2023-05-31   70.345
2023-06-30   78.764
Name: cum_ret, Length: 106, dtype: float64
In [100]:
rolling_max = btc_ret['cum_ret'].cummax()
drawdown = (btc_ret['cum_ret'] - rolling_max) / rolling_max

# Plotting the percentage change
plt.figure(figsize=(10, 5), facecolor = '#F5F5F5')

drawdown.plot(marker='o', linestyle='-')

# Set X axis
plt.xlabel('Date')
plt.xticks(rotation = 45, fontsize = 9)

# Set Y axis
plt.gca().yaxis.set_major_formatter(FuncFormatter(percentage)) # Formatting y-axis as percentage
plt.ylabel('Drawdown (%)')
plt.yticks(fontsize = 9)

# Set title, etc.
plt.title('Bitcoin (BTC) Drawdown by Percentage', fontsize = 12)

# Display the plot
plt.grid(True)
plt.tight_layout()
plt.show()
No description has been provided for this image

Correlations¶

In [101]:
all_fund_mon_ret = iwv.copy()
all_fund_mon_ret['iShares Russell 3000 ETF (IWV)'] = all_fund_mon_ret['adj_close'].pct_change()
all_fund_mon_ret.drop(columns = {'adj_close'}, inplace = True)

# index_dfs = [(efa, 'EFA'), (eem, 'EEM'), (acwx, 'ACWX'), (agg, 'AGG'), (spti, 'SPTI'), (vglt, 'VGLT'), (tip, 'TIP'), 
#              (LF98TRUU, 'LF98TRUU'), (SPBDLLB, 'SPBDLLB'), (NPPI0DIV, 'NPPI0DIV'), (LPX50TR, 'LPX50TR'),
#              (emb, 'EMB'), (hdg, 'HDG'),(psp, 'PSP'), (cpi, 'CPI'), (gsg, 'GSG'), (gld, 'GLD'), (btc, 'BTC')]

index_dfs = [(efa, 'iShares MSCI EAFE ETF (EFA)'),
             (eem, 'iShares MSCI Emerging Markets ETF (EEM)'), 
             (acwx, 'iShares MSCI ACWI ex U.S. ETF (ACWX)'), 
             (agg, 'US Aggregate Bond (AGG)'), 
             (spti, 'SPDR Portfolio Intermediate Term Treasury ETF (SPTI)'),
             (vglt, 'Vanguard Long-Term Treasury ETF (VGLT)'),
             (tip, 'iShares TIPS Bond ETF (TIP)'), 
             (LF98TRUU, 'Bloomberg US Corp HY TR Index (LF98TRUU)'),
             (SPBDLLB, 'Morningstar LSTA US Leveraged Loan 100 Index (SPBDLLB)'), 
             (NPPI0DIV, 'NCREIF Fund Index Open End Diversified Core (NPPI0DIV)'),
             (LPX50TR, 'LPX Listed Private Equity Index TR (LPX50TR)'),
             (emb, 'iShares J.P. Morgan USD Emerging Markets Bond ETF (EMB)'),
             (hdg, 'ProShares Hedge Replication ETF (HDG)'),
             (psp, 'Invesco Global Listed Private Equity ETF (PSP)'), 
             (cpi, 'CPI'), 
             (gsg, 'iShares S&P GSCI Commodity-Indexed Trust (GSG)'), 
             (gld, 'SPDR Gold Shares ETF (GLD)'), 
             (btc, 'Bitcoin Total Return (BTCUSD)')]

for df, name in index_dfs:
    corr_df = df.copy()
    corr_df[name] = corr_df['adj_close'].pct_change()
    corr_df.drop(columns = {'adj_close'}, inplace = True)
    try:
        corr_df.drop(columns = {'adj_close_orig'}, inplace = True)
    except:
        pass
    all_fund_mon_ret = pd.merge(all_fund_mon_ret, corr_df, left_on = 'Date', right_on = 'Date')

all_fund_mon_ret.columns
Out[101]:
Index(['iShares Russell 3000 ETF (IWV)', 'iShares MSCI EAFE ETF (EFA)',
       'iShares MSCI Emerging Markets ETF (EEM)',
       'iShares MSCI ACWI ex U.S. ETF (ACWX)', 'US Aggregate Bond (AGG)',
       'SPDR Portfolio Intermediate Term Treasury ETF (SPTI)',
       'Vanguard Long-Term Treasury ETF (VGLT)', 'iShares TIPS Bond ETF (TIP)',
       'Bloomberg US Corp HY TR Index (LF98TRUU)',
       'Morningstar LSTA US Leveraged Loan 100 Index (SPBDLLB)',
       'NCREIF Fund Index Open End Diversified Core (NPPI0DIV)',
       'LPX Listed Private Equity Index TR (LPX50TR)',
       'iShares J.P. Morgan USD Emerging Markets Bond ETF (EMB)',
       'ProShares Hedge Replication ETF (HDG)',
       'Invesco Global Listed Private Equity ETF (PSP)', 'CPI',
       'iShares S&P GSCI Commodity-Indexed Trust (GSG)',
       'SPDR Gold Shares ETF (GLD)', 'Bitcoin Total Return (BTCUSD)'],
      dtype='object')
In [102]:
all_fund_mon_ret = all_fund_mon_ret[all_fund_mon_ret.index >= '2015-06-30']
display(all_fund_mon_ret.head(1))
display(all_fund_mon_ret.tail(1))
iShares Russell 3000 ETF (IWV) iShares MSCI EAFE ETF (EFA) iShares MSCI Emerging Markets ETF (EEM) iShares MSCI ACWI ex U.S. ETF (ACWX) US Aggregate Bond (AGG) SPDR Portfolio Intermediate Term Treasury ETF (SPTI) Vanguard Long-Term Treasury ETF (VGLT) iShares TIPS Bond ETF (TIP) Bloomberg US Corp HY TR Index (LF98TRUU) Morningstar LSTA US Leveraged Loan 100 Index (SPBDLLB) NCREIF Fund Index Open End Diversified Core (NPPI0DIV) LPX Listed Private Equity Index TR (LPX50TR) iShares J.P. Morgan USD Emerging Markets Bond ETF (EMB) ProShares Hedge Replication ETF (HDG) Invesco Global Listed Private Equity ETF (PSP) CPI iShares S&P GSCI Commodity-Indexed Trust (GSG) SPDR Gold Shares ETF (GLD) Bitcoin Total Return (BTCUSD)
Date
2015-06-30 -0.017 -0.031 -0.029 -0.032 -0.011 -0.006 -0.036 -0.010 -0.015 -0.014 0.013 -0.005 -0.018 -0.008 -0.052 0.003 -0.002 -0.015 0.143
iShares Russell 3000 ETF (IWV) iShares MSCI EAFE ETF (EFA) iShares MSCI Emerging Markets ETF (EEM) iShares MSCI ACWI ex U.S. ETF (ACWX) US Aggregate Bond (AGG) SPDR Portfolio Intermediate Term Treasury ETF (SPTI) Vanguard Long-Term Treasury ETF (VGLT) iShares TIPS Bond ETF (TIP) Bloomberg US Corp HY TR Index (LF98TRUU) Morningstar LSTA US Leveraged Loan 100 Index (SPBDLLB) NCREIF Fund Index Open End Diversified Core (NPPI0DIV) LPX Listed Private Equity Index TR (LPX50TR) iShares J.P. Morgan USD Emerging Markets Bond ETF (EMB) ProShares Hedge Replication ETF (HDG) Invesco Global Listed Private Equity ETF (PSP) CPI iShares S&P GSCI Commodity-Indexed Trust (GSG) SPDR Gold Shares ETF (GLD) Bitcoin Total Return (BTCUSD)
Date
2023-06-30 0.068 0.045 0.044 0.046 -0.004 -0.013 0.000 -0.003 0.017 0.018 -0.009 0.013 0.025 0.017 0.047 0.002 0.044 -0.022 0.120
In [103]:
display_correlation(all_fund_mon_ret)
MIN Correlation pair is ('Vanguard Long-Term Treasury ETF (VGLT)', 'iShares S&P GSCI Commodity-Indexed Trust (GSG)')
MAX Correlation pair is ('iShares MSCI EAFE ETF (EFA)', 'iShares MSCI ACWI ex U.S. ETF (ACWX)')
No description has been provided for this image
In [ ]:
 
In [ ]: