SharedPhase_1-3_Mementum_Spillover.ipynbOpen in CoCalc
Week7_Demo

# [FE800] Group 4: Phase 1:3 - Total and Residual Momentum Spillover¶

import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from dateutil import relativedelta
import calendar
import matplotlib.pyplot as plt
from matplotlib import style
import math


# Functions¶

## Ranking Equity Return function¶

def rank_port_decile(equity_data , formation_date, num_month, strat_type = 0):

form_date = datetime.strptime(formation_date, '%Y-%m-%d')
avg_date =  form_date - relativedelta.relativedelta(months = num_month)
check_last_day = calendar.monthrange(avg_date.year, avg_date.month)
avg_date = datetime(avg_date.year, avg_date.month, check_last_day[-1])

equity_data_ = equity_data[(equity_data.date >= avg_date) & (equity_data.date <= form_date)]
equity_data_ = equity_data_.drop_duplicates()
try:
equity_data_ = equity_data_[equity_data_.RETX != 'C']
except:
print('There is no C')
equity_data_.RETX = equity_data_.RETX.astype('float64')
equity_data_.RET = equity_data_.RET.astype('float64')

#residual
if strat_type != 0:
equity_data_.RETX = equity_data_.RETX - equity_data_.RF

#equity_group_mean_ret = equity_data_.groupby('TICKER').agg({'RETX': 'mean'})
equity_group_mean_ret = equity_data_.groupby('TICKER').agg({'RETX': 'prod'}) #cumulative
equity_group_mean_ret = equity_group_mean_ret.subtract(1) #cumulative
equity_group_mean_ret_sort = equity_group_mean_ret.sort_values(by=['RETX'])
equity_group_mean_ret_sort['GROUP'] = 0

frac = math.floor(equity_group_mean_ret_sort.shape[0]/10)

num_line = equity_group_mean_ret_sort.shape[0]
left = num_line%frac

group_num = [frac+1]*(left)+[frac]*(10-left)
group = ['P10', 'P09', 'P08', 'P07', 'P06', 'P05', 'P04', 'P03', 'P02', 'P01']

i, j = 0, group_num[0]
while j <= num_line:

equity_group_mean_ret_sort.ix[(j-group_num[i]):j , 'GROUP'] = group[i]
i +=1
if(i == 10):
break
j = j + group_num[i]

return(equity_group_mean_ret_sort)


## Generating Ranking Summary table function¶

def rank_table(rank_port, universe, strat_type = 0):

rank_port_sort = rank_port
rank_port_sort['TICKER'] = rank_port_sort.index
rank_port_sort = rank_port.sort_values(by=['GROUP', 'TICKER'])
rank_port_sort_sum = rank_port_sort.groupby('GROUP').agg({'TICKER':'count', 'RETX':'mean'})
rank_port_sort_sum.loc['Total'] = rank_port_sort_sum.sum()

strat_name = np.where(strat_type == 0, 'Total', 'Residual')

fig, ax = plt.subplots()
fig.set_figheight(5)
fig.set_figwidth(8)
ax.set_title('Ranking {0} Equity Returns \n From Winner to Loser Portfolios - {1}'.format(strat_name, universe), fontsize=15, weight='bold')
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
ax.axis('off')
ax.axis('tight')

data = rank_port_sort_sum.round(6).values
columns = ['Company numbers', 'Average returns']
rows = rank_port_sort_sum.index

rcolors = plt.cm.BuPu(np.linspace(0, 0.5, len(rows)))
ccolors = rcolors[::-1]
clcolors = []
for i in range(0, 10):
clcolors.append(["w","w"])
#clcolors.append(["#92a4cd","#92a4cd"])
clcolors.append(["#8c95c6","#8c95c6"])

plt.table(cellText = data, cellColours=clcolors,
rowLabels=rows,
colLabels=columns,rowColours=rcolors, colColours=ccolors, loc='center')
fig.tight_layout()



## Running Total Momentum Spillover Strategy function¶

def momentum_strategy(bond_data, rank_port, start_month, end_month, TMT = 2):

bond_data = pd.merge(bond_data, rank_port[['GROUP']], how = 'left', left_on = 'company_symbol', right_index=True)
bond_data = bond_data[bond_data.TMT >= TMT]
bond_data = bond_data.dropna(subset=['RET_EOM', 'DURATION'])

bond_average_month = bond_data.groupby(['month', 'company_symbol', 'GROUP']).agg({'PRICE_EOM':'mean','RET_EOM':'mean', 'RATING_NUM':'mean', 'DURATION':'mean'})

bond_month = pd.DataFrame(columns = ['month', 'ticker', 'port_group', 'price', 'return', 'avg_rating', 'duration'])
bond_month['month'] = [i[0] for i in bond_average_month.index]
bond_month['ticker'] = [i[1] for i in bond_average_month.index]
bond_month['port_group'] = [i[2] for i in bond_average_month.index]
bond_month['price'] = bond_average_month['PRICE_EOM'].values
bond_month['return'] = bond_average_month['RET_EOM'].values
bond_month['avg_rating'] = bond_average_month['RATING_NUM'].values
bond_month['duration'] = bond_average_month['DURATION'].values
bond_hold = bond_month[(bond_month.month >= start_month) & (bond_month.month <= end_month)]

bond_sum_price = bond_hold.groupby(['port_group', 'month']).agg({'price':'sum'})
bond_sum_price['port_group'] = [i[0] for i in bond_sum_price.index]
bond_sum_price['month'] = [i[1] for i in bond_sum_price.index]

bond_hold_value = pd.merge(bond_hold, bond_sum_price, how = 'left', left_on = ['port_group', 'month'], right_on=['port_group', 'month'])
bond_hold_value.columns = ['month', 'ticker', 'port_group', 'price', 'return', 'avg_rating', 'duration','port_total_weight']
bond_hold_value['port_weight'] = bond_hold_value['price'].divide(bond_hold_value['port_total_weight'])
bond_hold_value['value_return'] = bond_hold_value['return'].mul(bond_hold_value['port_weight'])
bond_hold_value['value_price'] = bond_hold_value['price'].mul(bond_hold_value['port_weight'])
bond_hold_value['value_rating'] = bond_hold_value['avg_rating'].mul(bond_hold_value['port_weight'])
bond_hold_value['value_duration'] = bond_hold_value['duration'].mul(bond_hold_value['port_weight'])

# Value portfolio
bond_hold_value_ = bond_hold_value.groupby(['port_group', 'month', 'ticker']).agg({'value_return':'sum', 'value_price':'sum', 'value_duration':'sum', 'value_rating':'sum'})
bond_hold_value_['port_group'] = [i[0] for i in bond_hold_value_.index]
bond_hold_value_['month'] = [i[1] for i in bond_hold_value_.index]
bond_hold_value_['ticker'] = [i[2] for i in bond_hold_value_.index]

bond_hold_value__ = bond_hold_value_.groupby(['port_group', 'ticker']).agg({'value_return':'prod', 'value_price':lambda x: x.iloc[-1],'value_duration':lambda x: x.iloc[-1], 'value_rating':lambda x: x.iloc[-1]})
bond_hold_value__['port_group'] = [i[0] for i in bond_hold_value__.index]
bond_hold_value__['ticker'] = [i[1] for i in bond_hold_value__.index]
bond_hold_value__['value_return'] = bond_hold_value__['value_return'].subtract(1)

bond_hold_valueW = bond_hold_value__.groupby(['port_group']).agg({'value_return':'sum', 'value_price':'sum','value_duration':'sum', 'value_rating':'sum'})
bond_hold_valueW.loc['P01-P10'] = bond_hold_valueW.ix[0,0] - bond_hold_valueW.ix[-1,0]
bond_hold_valueW.loc['P01-P05'] = bond_hold_valueW.ix[0,0] - bond_hold_valueW.ix[4,0]

#Equal portfolio
bond_hold_value___ = bond_hold_value
bond_hold_equalW_ = bond_hold_value___.groupby(['port_group', 'ticker']).agg({'return':'prod', 'price':lambda x: x.iloc[-1], 'avg_rating':lambda x: x.iloc[-1], 'duration': lambda x: x.iloc[-1]})
bond_hold_equalW_['port_group'] = [i[0] for i in bond_hold_equalW_.index]
bond_hold_equalW_['ticker'] = [i[1] for i in bond_hold_equalW_.index]
bond_hold_equalW_['return'] = bond_hold_equalW_['return'].subtract(1)
bond_hold_equalW = bond_hold_equalW_.groupby('port_group').agg({'return':'mean', 'price':'mean', 'avg_rating':'mean', 'duration': 'mean', 'ticker': lambda x: x.nunique()})
bond_hold_equalW.loc['P01-P10'] = bond_hold_equalW.ix[0,0] - bond_hold_equalW.ix[-1,0]
bond_hold_equalW.loc['P01-P05'] = bond_hold_equalW.ix[0,0] - bond_hold_equalW.ix[4,0]

result_ = bond_hold_valueW.merge(bond_hold_equalW, left_index=True, right_index=True)
text1 = 'value_wight'+'('+str(start_month)+','+str(end_month)+')'
text2 = 'equal_weight'+'('+str(start_month)+','+str(end_month)+')'
result__ = pd.DataFrame(columns = [text1, text2, 'value_price', 'equal_price', 'value_rating', 'equal_rating', 'value_duration', 'equal_duration', 'com_num'])
result__[text1] = result_.iloc[:, 0]
result__[text2] = result_.iloc[:, 4]
result__['value_price'] = result_.iloc[:, 1]
result__['equal_price'] = result_.iloc[:, 5]
result__['value_rating'] = result_.iloc[:, 3]
result__['equal_rating'] = result_.iloc[:, 6]
result__['value_duration'] = result_.iloc[:, 2]
result__['equal_duration'] = result_.iloc[:, 7]
result__['com_num'] = result_.iloc[:, 8]
result__.ix[10:,2:] = 0

result = pd.DataFrame(data = 0, columns = result__.columns, index = ['P01', 'P02', 'P03', 'P04', 'P05', 'P06', 'P07', 'P08', 'P09', 'P10'])

for i in result__.index:
result.loc[i] = result__.loc[i]

return(result)


## Generating Total Momentum Spillover Stategy Summary table function¶

def m_performance_table(perf, universe, strat_type = 0):
strat_name = np.where(strat_type == 0, 'Total', 'Residual')

fig, ax = plt.subplots()
fig.set_figheight(5)
fig.set_figwidth(14)
ax.set_title('{0} Momentum Spillover Performance Table - {1}'.format(strat_name, universe), fontsize=15, weight='bold')
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
ax.axis('off')
ax.axis('tight')

data = perf.round(4).values
columns = perf.columns
rows = perf.index

rcolors = plt.cm.BuPu(np.linspace(0, 0.5, len(rows)))
ccolors = rcolors[::-1]
clcolors = []
for i in range(0, 10):
clcolors.append(["w","w","w","w","w","w","w","w","w"])
clcolors.append(["#92a4cd","#92a4cd","#92a4cd","#92a4cd","#92a4cd","#92a4cd", "#92a4cd", "#92a4cd", "#92a4cd"])
clcolors.append(["#8c95c6","#8c95c6","#8c95c6","#8c95c6","#8c95c6","#8c95c6", "#8c95c6", "#8c95c6", "#8c95c6"])

plt.table(cellText = data, cellColours=clcolors,
rowLabels=rows,
colLabels=columns,rowColours=rcolors, colColours=ccolors, loc='center')
fig.tight_layout()


## Momentum Spillover Strategy Performance Visualization function¶

def performance_plot(perf, avg_period, start, end, tmt, universe, strat_type = 0):

style.use('seaborn')

fig = plt.figure(figsize=(18, 15))
strat_name = np.where(strat_type == 0, 'Total', 'Residual')
fig.suptitle('{0} Momentum Spillover Performance - {1}'.format(strat_name, universe), fontsize=20, fontweight='bold')

plt.figtext(0.5,0.95, "Average {0}-month equity returns \n Holding bond portfolios from {1} to {2} \n Time-to-maturity over {3}".format(avg_period, start, end, tmt), ha="center", va="top", fontsize=14, color="black")

ax1 = plt.subplot2grid((30, 22), (0, 0), rowspan=10, colspan=13)
ax1.plot(perf.iloc[0:10, 0], label="{0}".format(perf.columns[0]), color='SteelBlue')
ax1.annotate("{0:0.5f}".format(perf.iloc[0,0]), xy=(0, perf.iloc[0,0]), xytext=(8, 6), xycoords=('axes fraction', 'data'), textcoords='offset points')
ax1.annotate("{0:0.5f}".format(perf.iloc[-3,0]), xy=(1, perf.iloc[-3,0]), xytext=(8, 6), xycoords=('axes fraction', 'data'), textcoords='offset points')

ax1.plot(perf.iloc[0:10, 1], label="{0}".format(perf.columns[1]), color='IndianRed')
ax1.annotate("{0:0.5f}".format(perf.iloc[0,1]), xy=(0, perf.iloc[0,1]), xytext=(8, 6), xycoords=('axes fraction', 'data'), textcoords='offset points')
ax1.annotate("{0:0.5f}".format(perf.iloc[-3,1]), xy=(1, perf.iloc[-3,1]), xytext=(8, 6), xycoords=('axes fraction', 'data'), textcoords='offset points')
ax1.set_title('{0} momemtum Spillover Curves from Winner to Loser Portfolios'.format(strat_name), fontsize=15, weight='bold')
ax1.set_ylabel('Cumulative return')
ax1.legend()

ax2 = plt.subplot2grid((30, 22), (0, 15), rowspan=10, colspan=6)

index = np.arange(2)
bar_width = 0.35
opacity = 0.8

rect1 = ax2.bar(index - bar_width/2, perf.iloc[10:12,0].values, bar_width, color='SkyBlue', label="{0}".format(perf.columns[0]))
for rect in rect1:
height1 = rect.get_height()
ax2.text(rect.get_x() + rect.get_width()/2., 1.01*height1,
"{0:0.5f}".format(height1),
ha='center', va='bottom')

rect2 = ax2.bar(index + bar_width/2, perf.iloc[10:12,1].values, bar_width, color='IndianRed', alpha=opacity, label="{0}".format(perf.columns[1]))
for rect in rect2:
height2 = rect.get_height()
ax2.text(rect.get_x() + rect.get_width()/2., 1.01*height2,
"{0:0.5f}".format(height2),
ha='center', va='bottom')

ax2.set_xticks(index)
ax2.set_xticklabels((perf.index[-2], perf.index[-1]))
ax2.set_ylabel('Long-short Cumulative return')
ax2.set_title('Long-short Performance', fontsize=15, weight='bold')
ax2.legend()

ax3 = plt.subplot2grid((30, 22), (12, 0), rowspan=6, colspan=10)
ax3.plot(perf.iloc[0:10, 2], 'o-',label="Value price", color='orchid')
ax3.plot(perf.iloc[0:10, 3], 'o-',label="Equal price", color='turquoise')
ax3.set_title('Bond Prices from Winner to Loser Portfolios', fontsize=15, weight='bold')
for i in range(0,10):
ax3.text(i,1.001*perf.iloc[i, 2], '{0:.2f}'.format(perf.iloc[i, 2]), ha='center', va='bottom')
ax3.text(i,1.001*perf.iloc[i, 3], '{0:.2f}'.format(perf.iloc[i, 3]), ha='center', va='bottom')
ax3.legend()

ax4 = plt.subplot2grid((30, 22), (12, 11), rowspan=6, colspan=10)
ax4.plot(perf.iloc[0:10, 4], 'o-', label="Value credit rating", color='orchid')
ax4.plot(perf.iloc[0:10, 5], 'o-', label="Equal credit rating", color='turquoise')
ax4.set_title('Credit Rating from Winner to Loser Portfolios', fontsize=15, weight='bold')
for i in range(0,10):
ax4.text(i,1.001*perf.iloc[i, 4], '{0:.2f}'.format(perf.iloc[i, 4]), ha='center', va='bottom')
ax4.text(i,1.001*perf.iloc[i, 5], '{0:.2f}'.format(perf.iloc[i, 5]), ha='center', va='bottom')
ax4.legend()

ax5 = plt.subplot2grid((30, 22), (20, 0), rowspan=6, colspan=10)
ax5.plot(perf.iloc[0:10, 6], 'o-', label="Value durations", color='orchid')
ax5.plot(perf.iloc[0:10, 7], 'o-', label="Equal durations", color='turquoise')
ax5.set_title('Durations from Winner to Loser Portfolios', fontsize=15, weight='bold')
for i in range(0,10):
ax5.text(i,1.001*perf.iloc[i, 6], '{0:.2f}'.format(perf.iloc[i, 6]), ha='center', va='bottom')
ax5.text(i,1.001*perf.iloc[i, 7], '{0:.2f}'.format(perf.iloc[i, 7]), ha='center', va='bottom')
ax5.legend()

ax6 = plt.subplot2grid((30, 22), (20, 11), rowspan=6, colspan=10)
rect3 = ax6.bar(perf.index[0:10].values, perf.iloc[0:10, 8], bar_width, color='deepskyblue', label="Number of companies")
for rect in rect3:
height3 = rect.get_height()
ax6.text(rect.get_x() + rect.get_width()/2., 1.01*height3,
"{0}".format(int(height3)),
ha='center', va='bottom')

ax6.set_title('Total Company Numbers from Winner to Loser Portfolios', fontsize=15, weight='bold')



## Performance comparison function¶

def comparison_performance(equity_data, bond_data, holding_range, formation_date, equity_range, TMT=2, strat_type = 0):
rank1_port = rank_port_decile(equity_data, formation_date, equity_range[0], strat_type=strat_type)
rank2_port = rank_port_decile(equity_data, formation_date, equity_range[1], strat_type=strat_type)
rank3_port = rank_port_decile(equity_data, formation_date, equity_range[2], strat_type=strat_type)

avg_period = (equity_range[0], equity_range[1], equity_range[2])

perf1_1 = momentum_strategy(bond_data, rank1_port, holding_range[0][0], holding_range[0][1], TMT=TMT)
perf1_1_ = perf1_1.iloc[:, 0:2].add(1).pow(12/(holding_range[0][1] - holding_range[0][0] + 1)).subtract(1)
perf1_2 = momentum_strategy(bond_data, rank1_port, holding_range[1][0], holding_range[1][1], TMT=TMT)
perf1_2_ = perf1_2.iloc[:, 0:2].add(1).pow(12/(holding_range[1][1] - holding_range[1][0] + 1)).subtract(1)
perf1_3 = momentum_strategy(bond_data, rank1_port, holding_range[2][0], holding_range[2][1], TMT=TMT)
perf1_3_ = perf1_3.iloc[:, 0:2].add(1).pow(12/(holding_range[2][1] - holding_range[2][0] + 1)).subtract(1)
perf1_4 = momentum_strategy(bond_data, rank1_port, holding_range[3][0], holding_range[3][1], TMT=TMT)
perf1_4_ = perf1_4.iloc[:, 0:2].add(1).pow(12/(holding_range[3][1] - holding_range[3][0] + 1)).subtract(1)
perf11 = [perf1_1_.iloc[-2, 0], perf1_1_.iloc[-2, 1], perf1_2_.iloc[-2, 0], perf1_2_.iloc[-2, 1], perf1_3_.iloc[-2, 0], perf1_3_.iloc[-2, 1], perf1_4_.iloc[-2, 0], perf1_4_.iloc[-2, 1]]
perf12 = [perf1_1_.iloc[-1, 0], perf1_1_.iloc[-1, 1], perf1_2_.iloc[-1, 0], perf1_2_.iloc[-1, 1], perf1_3_.iloc[-1, 0], perf1_3_.iloc[-1, 1], perf1_4_.iloc[-1, 0], perf1_4_.iloc[-1, 1]]

perf2_1 = momentum_strategy(bond_data, rank2_port, holding_range[0][0], holding_range[0][1], TMT=TMT)
perf2_1_ = perf2_1.iloc[:, 0:2].add(1).pow(12/(holding_range[0][1] - holding_range[0][0] + 1)).subtract(1)
perf2_2 = momentum_strategy(bond_data, rank2_port, holding_range[1][0], holding_range[1][1], TMT=TMT)
perf2_2_ = perf2_2.iloc[:, 0:2].add(1).pow(12/(holding_range[1][1] - holding_range[1][0] + 1)).subtract(1)
perf2_3 = momentum_strategy(bond_data, rank2_port, holding_range[2][0], holding_range[2][1], TMT=TMT)
perf2_3_ = perf2_3.iloc[:, 0:2].add(1).pow(12/(holding_range[2][1] - holding_range[2][0] + 1)).subtract(1)
perf2_4 = momentum_strategy(bond_data, rank2_port, holding_range[3][0], holding_range[3][1], TMT=TMT)
perf2_4_ = perf2_4.iloc[:, 0:2].add(1).pow(12/(holding_range[3][1] - holding_range[3][0] + 1)).subtract(1)
perf21 = [perf2_1_.iloc[-2, 0], perf2_1_.iloc[-2, 1], perf2_2_.iloc[-2, 0], perf2_2_.iloc[-2, 1], perf2_3_.iloc[-2, 0], perf2_3_.iloc[-2, 1], perf2_4_.iloc[-2, 0], perf2_4_.iloc[-2, 1]]
perf22 = [perf2_1_.iloc[-1, 0], perf2_1_.iloc[-1, 1], perf2_2_.iloc[-1, 0], perf2_2_.iloc[-1, 1], perf2_3_.iloc[-1, 0], perf2_3_.iloc[-1, 1], perf2_4_.iloc[-1, 0], perf2_4_.iloc[-1, 1]]

perf3_1 = momentum_strategy(bond_data, rank3_port, holding_range[0][0], holding_range[0][1], TMT=TMT)
perf3_1_ = perf3_1.iloc[:, 0:2].add(1).pow(12/(holding_range[0][1] - holding_range[0][0] + 1)).subtract(1)
perf3_2 = momentum_strategy(bond_data, rank3_port, holding_range[1][0], holding_range[1][1], TMT=TMT)
perf3_2_ = perf3_2.iloc[:, 0:2].add(1).pow(12/(holding_range[1][1] - holding_range[1][0] + 1)).subtract(1)
perf3_3 = momentum_strategy(bond_data, rank3_port, holding_range[2][0], holding_range[2][1], TMT=TMT)
perf3_3_ = perf3_3.iloc[:, 0:2].add(1).pow(12/(holding_range[2][1] - holding_range[2][0] + 1)).subtract(1)
perf3_4 = momentum_strategy(bond_data, rank3_port, holding_range[3][0], holding_range[3][1], TMT=TMT)
perf3_4_ = perf3_4.iloc[:, 0:2].add(1).pow(12/(holding_range[3][1] - holding_range[3][0] + 1)).subtract(1)
perf31 = [perf3_1_.iloc[-2, 0], perf3_1_.iloc[-2, 1], perf3_2_.iloc[-2, 0], perf3_2_.iloc[-2, 1], perf3_3_.iloc[-2, 0], perf3_3_.iloc[-2, 1], perf3_4_.iloc[-2, 0], perf3_4_.iloc[-2, 1]]
perf32 = [perf3_1_.iloc[-1, 0], perf3_1_.iloc[-1, 1], perf3_2_.iloc[-1, 0], perf3_2_.iloc[-1, 1], perf3_3_.iloc[-1, 0], perf3_3_.iloc[-1, 1], perf3_4_.iloc[-1, 0], perf3_4_.iloc[-1, 1]]

perf = [perf11, perf12, perf21, perf22, perf31, perf32]

table_index = []
for i in avg_period:
for j in ['P01-P10', 'P01-P05']:
table_index.append('Average {0}-month return:{1}'.format(i, j))

column_name = ['Value {0}'.format(holding_range[0]), 'Equal {0}'.format(holding_range[0]), 'Value {0}'.format(holding_range[1]) , 'Equal {0}'.format(holding_range[1]), 'Value {0}'.format(holding_range[2]), 'Equal {0}'.format(holding_range[2]), 'Value {0}'.format(holding_range[3]), 'Equal {0}'.format(holding_range[3])]
comparison_table = pd.DataFrame(data = perf,index = table_index, columns = column_name)
return(comparison_table)


## Generating performance comparison table function¶

def comparison_table(com_perf, universe, strat_type = 0):

fig, ax = plt.subplots()
fig.set_figheight(5)
fig.set_figwidth(12)
strat_name = np.where(strat_type == 0, 'Total', 'Residual')
ax.set_title('{0} Momentum Spillover Annualized Return Comparison Performance Table - {1}'.format(strat_name, universe), fontsize=15, weight='bold')
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
ax.axis('off')
ax.axis('tight')

data = com_perf.round(5).values
columns = com_perf.columns
rows = com_perf.index

rcolors = plt.cm.BuPu(np.linspace(0, 0.5, len(rows)))
ccolors = plt.cm.BuPu(np.linspace(0, 0.5, len(columns)))[::-1]
clcolors = []
for i in range(0, 6):
maxval  = max(data[i])
maxidx = [index for index, val in enumerate(data[i]) if val == maxval]
clist = ["w","w","w","w","w","w","w","w"]
clist[maxidx[0]] = "tomato"
clcolors.append(clist)

plt.table(cellText = data, cellColours=clcolors,
rowLabels=rows,
colLabels=columns,
rowColours=rcolors,
colColours=ccolors, loc='center')
fig.tight_layout()



## Yearly holding comparision function¶

def comparison_holding(equity_data, bond_data, form_date, num_month, strat_type = 0, TMT = 2):

start_month = 1
end_month = 12

rank_port = rank_port_decile(equity_data, form_date, num_month, strat_type)

bond_data = pd.merge(bond_data, rank_port[['GROUP']], how = 'left', left_on = 'company_symbol', right_index=True)
bond_data = bond_data[bond_data.TMT >= TMT]
bond_data = bond_data.dropna(subset=['RET_EOM', 'DURATION'])

bond_average_month = bond_data.groupby(['month', 'company_symbol', 'GROUP']).agg({'PRICE_EOM':'mean','RET_EOM':'mean', 'RATING_NUM':'mean', 'DURATION':'mean'})

bond_month = pd.DataFrame(columns = ['month', 'ticker', 'port_group', 'price', 'return', 'avg_rating', 'duration'])
bond_month['month'] = [i[0] for i in bond_average_month.index]
bond_month['ticker'] = [i[1] for i in bond_average_month.index]
bond_month['port_group'] = [i[2] for i in bond_average_month.index]
bond_month['price'] = bond_average_month['PRICE_EOM'].values
bond_month['return'] = bond_average_month['RET_EOM'].values
bond_month['avg_rating'] = bond_average_month['RATING_NUM'].values
bond_month['duration'] = bond_average_month['DURATION'].values
bond_hold = bond_month[(bond_month.month >= start_month) & (bond_month.month <= end_month)]

bond_sum_price = bond_hold.groupby(['port_group', 'month']).agg({'price':'sum'})
bond_sum_price['port_group'] = [i[0] for i in bond_sum_price.index]
bond_sum_price['month'] = [i[1] for i in bond_sum_price.index]

bond_hold_value = pd.merge(bond_hold, bond_sum_price, how = 'left', left_on = ['port_group', 'month'], right_on=['port_group', 'month'])
bond_hold_value.columns = ['month', 'ticker', 'port_group', 'price', 'return', 'avg_rating', 'duration','port_total_weight']
bond_hold_value['port_weight'] = bond_hold_value['price'].divide(bond_hold_value['port_total_weight'])
bond_hold_value['value_return'] = bond_hold_value['return'].mul(bond_hold_value['port_weight'])
bond_hold_value['value_price'] = bond_hold_value['price'].mul(bond_hold_value['port_weight'])
bond_hold_value['value_rating'] = bond_hold_value['avg_rating'].mul(bond_hold_value['port_weight'])
bond_hold_value['value_duration'] = bond_hold_value['duration'].mul(bond_hold_value['port_weight'])

# Value portfolio
bond_hold_value_ = bond_hold_value.groupby(['port_group', 'month']).agg({'value_return':'sum', 'value_price':'sum', 'value_duration':'sum', 'value_rating':'sum'})
bond_hold_value_['port_group'] = [i[0] for i in bond_hold_value_.index]
bond_hold_value_['month'] = [i[1] for i in bond_hold_value_.index]
bond_hold_value_['cumulative_return'] = bond_hold_value_.groupby(['port_group']).cumprod()['value_return']
bond_hold_value_['cumulative_return']  = bond_hold_value_['cumulative_return'] .subtract(1)
bond_hold_value_['value_return']  = bond_hold_value_['value_return'] .subtract(1)

#Equal portfolio
bond_hold_value___ = bond_hold_value
bond_hold_equal = bond_hold_value___.groupby(['port_group', 'month']).agg({'return':'mean', 'price':'mean', 'duration':'mean', 'avg_rating':'mean'})
bond_hold_equal['port_group'] = [i[0] for i in bond_hold_equal.index]
bond_hold_equal['month'] = [i[1] for i in bond_hold_equal.index]
bond_hold_equal['cumulative_return'] = bond_hold_equal.groupby(['port_group']).cumprod()['return']
bond_hold_equal['cumulative_return']  = bond_hold_equal['cumulative_return'] .subtract(1)
bond_hold_equal['return']  = bond_hold_equal['return'] .subtract(1)

col_name = []
port = ['P01', 'P02', 'P03', 'P04', 'P05', 'P06', 'P07', 'P08', 'P09', 'P10']
for i in ['value', 'equal']:
for j in port:
col_name.append('{0}_{1}'.format(i, j))

result = pd.DataFrame(data = 0, columns = col_name, index = [i for i in range(1, 13)])

for i in port:
result.ix[:, '{0}_{1}'.format('value', i)] = bond_hold_value_[bond_hold_value_['port_group'] == i]['cumulative_return'].values

for i in port:
result.ix[:, '{0}_{1}'.format('equal', i)] = bond_hold_equal[ bond_hold_equal['port_group'] == i]['cumulative_return'].values

return(result)


## Yearly holding comparision visualization function¶

def comparison_holding_plot(com_hold, universe, avg_period, TMT, strat_type = 0):
style.use('seaborn')

fig = plt.figure(figsize=(15, 15))
strat_name = np.where(strat_type == 0, 'Total', 'Residual')
fig.suptitle('{0} Momentum Spillover Holding Comparison - {1}'.format(strat_name, universe), fontsize=20, fontweight='bold')

plt.figtext(0.5,0.95, "Average {0}-month equity returns \n Time-to-maturity over {1}".format(avg_period, TMT), ha="center", va="top", fontsize=14, color="black")

ax1 = plt.subplot2grid((30, 22), (0, 0), rowspan=12, colspan=22)
ax1.plot(com_hold.iloc[:, 0], 'o-', label="{0}".format(com_hold.columns[0]), color='lightsalmon')
ax1.plot(com_hold.iloc[:, 4], 'o-', label="{0}".format(com_hold.columns[4]), color='greenyellow')
ax1.plot(com_hold.iloc[:, 9], 'o-', label="{0}".format(com_hold.columns[9]), color='skyblue')

for i in range(0, 12):
ax1.text(com_hold.index[i],1.02*com_hold.iloc[i, 0], '{0:.5f}'.format(com_hold.iloc[i, 0]), ha='center', va='bottom')
ax1.text(com_hold.index[i],1.02*com_hold.iloc[i, 4], '{0:.5f}'.format(com_hold.iloc[i, 4]), ha='center', va='bottom')
ax1.text(com_hold.index[i],1.02*com_hold.iloc[i, 9], '{0:.5f}'.format(com_hold.iloc[i, 9]), ha='center', va='bottom')

ax1.set_title('Cumulative Returns of Value Wighted Portfolios Per Year', fontsize=15, weight='bold')
ax1.set_ylabel('Cumulative return')
ax1.legend()

ax2 = plt.subplot2grid((30, 22), (14, 0), rowspan=12, colspan=22, sharex=ax1)
ax2.plot(com_hold.iloc[:, 10], 'o-', label="{0}".format(com_hold.columns[10]), color='lightsalmon')
ax2.plot(com_hold.iloc[:, 14], 'o-', label="{0}".format(com_hold.columns[14]), color='greenyellow')
ax2.plot(com_hold.iloc[:, 19], 'o-', label="{0}".format(com_hold.columns[19]), color='skyblue')

for i in range(0, 12):
ax2.text(com_hold.index[i],1.02*com_hold.iloc[i, 10], '{0:.5f}'.format(com_hold.iloc[i, 10]), ha='center', va='bottom')
ax2.text(com_hold.index[i],1.02*com_hold.iloc[i, 14], '{0:.5f}'.format(com_hold.iloc[i, 14]), ha='center', va='bottom')
ax2.text(com_hold.index[i],1.02*com_hold.iloc[i, 19], '{0:.5f}'.format(com_hold.iloc[i, 19]), ha='center', va='bottom')

ax2.set_title('Cumulative Returns of Equal Wighted Portfolios Per Year', fontsize=15, weight='bold')
ax2.set_ylabel('Cumulative return')
ax2.set_xlabel('Months')
ax2.legend()


# Backtesting¶

## Import equity data step¶

equity_data_IG = pd.read_csv('CRSP 2012-2013 - IG.csv')
equity_data_IG.date = pd.to_datetime(equity_data_IG.date, format = '%Y%m%d')

equity_data_BBB = pd.read_csv('CRSP 2012-2013 - BBB.csv')
equity_data_BBB.date = pd.to_datetime(equity_data_BBB.date, format = '%Y%m%d')

equity_data_HY = pd.read_csv('CRSP 2012-2013 - HY.csv')
equity_data_HY.date = pd.to_datetime(equity_data_HY.date, format = '%Y%m%d')


## Import bond data step¶

bond_data_IG = pd.read_csv('WRDS bond return 2013 - IG.csv')
bond_data_IG.DATE = pd.to_datetime(bond_data_IG.DATE, format = '%d-%b-%y')

bond_data_IG['month'] = [i.month for i in bond_data_IG.DATE]
bond_data_IG.YIELD = bond_data_IG.YIELD.str.replace('%', '').astype('float').divide(100.0)
bond_data_IG.RET_EOM = bond_data_IG.RET_EOM.str.replace('%', '').astype('float').divide(100.0)

bond_data_BBB = pd.read_csv('WRDS bond return 2013 - BBB.csv')
bond_data_BBB.DATE = pd.to_datetime(bond_data_BBB.DATE, format = '%d-%b-%y')

bond_data_BBB['month'] = [i.month for i in bond_data_BBB.DATE]
bond_data_BBB.YIELD = bond_data_BBB.YIELD.str.replace('%', '').astype('float').divide(100.0)
bond_data_BBB.RET_EOM = bond_data_BBB.RET_EOM.str.replace('%', '').astype('float').divide(100.0)

bond_data_HY = pd.read_csv('WRDS bond return 2013 - HY.csv')
bond_data_HY.DATE = pd.to_datetime(bond_data_HY.DATE, format = '%d-%b-%y')

bond_data_HY['month'] = [i.month for i in bond_data_HY.DATE]
bond_data_HY.YIELD = bond_data_HY.YIELD.str.replace('%', '').astype('float').divide(100.0)
bond_data_HY.RET_EOM = bond_data_HY.RET_EOM.str.replace('%', '').astype('float').divide(100.0)


## Select formation date of identifying systematic strategy¶

formation_date = '2012-12-31'


## Backtesting on IG¶

Select time-to-maturity of bonds and data universe

TMT_IG = 28
universe_IG = 'IG'


Execute Cumulative Return Holding Per Year Comparison function on IG universe

avg_period_IG = 6
com_hold_IG = comparison_holding(equity_data_IG, bond_data_IG, formation_date, avg_period_IG, TMT = TMT_IG)
comparison_holding_plot(com_hold_IG, universe_IG, avg_period_IG, TMT = TMT_IG)


Observe that the systematic pattern could be in the first quarter after formation date. Hence, select the systematic ranges of bond holding to test as below.

holding_range_IG = [(1, 1), (1, 2), (2, 4), (6, 12)]
equity_range = (3, 6, 12)

s_type_IG = 0
com_perf_IG = comparison_performance(equity_data_IG, bond_data_IG, holding_range_IG, formation_date, equity_range, TMT=TMT_IG, strat_type = s_type_IG)
comparison_table(com_perf_IG, universe_IG, s_type_IG)


Using 6-month equity return and holding bond porfolios from 1 to 1 with time-to-maturity of bonds over 28 years shows the highest annualized return and momentum effect. We will see more detail of this systematic strategy.

avg_period_IG = 6

rank_port_IG = rank_port_decile(equity_data_IG, formation_date, avg_period_IG, s_type_IG)
rank_table(rank_port_IG, universe_IG, s_type_IG)

hfrom_IG = 1
hend_IG = 1
tmt_IG = 28

perf_IG = momentum_strategy(bond_data_IG, rank_port_IG, hfrom_IG, hend_IG, TMT=TMT_IG)
m_performance_table(perf_IG, universe_IG, s_type_IG)

performance_plot(perf_IG, avg_period_IG, hfrom_IG, hend_IG, tmt_IG, universe_IG, s_type_IG)


The performance of this systematic strategy on IG universe shows fair momentum with gaining around 2% return.

## Backtesting on BBB¶

Select time-to-maturity of bonds and data universe

TMT_BBB = 5
universe_BBB = 'BBB'


Execute Cumulative Return Holding Per Year Comparison function on BBB universe

avg_period_BBB = 3
com_hold_BBB = comparison_holding(equity_data_BBB, bond_data_BBB, formation_date, avg_period_BBB, strat_type = 1, TMT = TMT_BBB)
comparison_holding_plot(com_hold_BBB, universe_BBB, avg_period_BBB, TMT = TMT_BBB, strat_type = 1)