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_data_.RETX = equity_data_.RETX.add(1) #cumulative
    
    #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)
    plt.subplots_adjust(left=0.5, top=0.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_['value_return'] = bond_hold_value_['value_return'].add(1)
    
    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_value___['return'] = bond_hold_value___['return'].add(1)
    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)
    plt.subplots_adjust(left=0.5, top=0.8)
    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)
    plt.subplots_adjust(left=0.5, top=0.8)
    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_['value_return'] = bond_hold_value_['value_return'].add(1)
    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_value___['return'] = bond_hold_value___['return'].add(1)
    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['return'] = bond_hold_equal['return'].add(1)
    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)