Benchmark Bond Portfolio Returns using R code

[This article was first published on K & L Fintech Modeling, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

This post calculates the holding period returns of four benchmark zero coupon bonds portfolios such as bullet, barbell, ladder and buy-and-hold using R code



Benchmark Bond Portfolio Strategies


Bond portfolio strategies are too many to enumerate. But there are benchmark bond strategies which are frequently introduced to textbook. Among them are bullet, barbell, ladder and buy-and-hold portfolios. For these bond portfolios, we are going to calculate monthly and cumulative returns. 

We assume the case of zero coupon bond portfolio for empirical exercises. Without target duration constraints, differences between zero coupon bond and coupon bearing bond are not large. It is well known that the coupon bond is the portfolio of zero coupon bonds.
  1. Bullet : maintain one bond with its maturity fixed
  2. Barbell : maintain two bonds with each maturities fixed and equal weights(1/2)
  3. Ladder : maintain all relevant bonds with each maturity fixed and equal weights(1/n)
  4. Buy and Hold : buy and hold one bond until its maturity and maintain this trade periodically

Return Calculation


At first, we need to calculate monthly returns of each bond. After getting these monthly returns, we can construct monthly returns of 4 portfolios.

The price of a pure discount (zero coupon) bond with maturity \(\tau\) at time t is the discounted value of 1 receivable \(\tau\) periods later. \[\begin{align} P_t^{[\tau]} =exp⁡(-\tau \times s_t^{[\tau]}) \end{align}\] Here, \(s_t^{[\tau]}\) is the spot rate.

Using the log-return expression, the monthly holding period return is as follows.

\[\begin{align} r_t^{[\tau]}& = \log {P_t^{\left[\tau-\frac{1}{12}\right]}} – \log {P_{t-1}^{\left[\tau \right]}} \\ &= \left(\tau-\frac{1}{12}\right) \times s_t^{\left[\tau-\frac{1}{12}\right]} – \tau \times s_{t-1}^{\left[\tau\right]} \end{align}\]
However, yield curve is reported for some major relevant maturities such as 1-year or 3-year, and so on. In most cases, \(s_t^{\left[\tau-\frac{1}{12}\right]}\) are not observed. Therefore, we need to interpolate these unobserved spot rates using observed spot rates. If \(s_t^{\left[\tau-\frac{1}{12}\right]}\) are interpolated using spline or linear interpolation or Nelson-Siegel model, we can apply the above log-return expression to these interpolated spot rates. For our study, we use the spline interpolation using splinefun R function.

Now it is ready to use the time \(t-1\) spot rates with maturities \(1\), \(3\), \(5\), \(7\), and \(10\) and the time \(t\) spot rates with maturities \(1-\frac{1}{12}\), \(3-\frac{1}{12}\), \(5-\frac{1}{12}\), \(7-\frac{1}{12}\), and \(10-\frac{1}{12}\) to calculate monthly returns for each selected maturity.


Monthly Returns of Portfolio


Given time series of monthly spot rate for each maturity, monthly returns of each bullet portfolio is the same as the previous monthly returns of each column respectively. It is only to read each column which corresponds each maturity respectively.

It is also easy to calculate monthly returns for barbell, and ladder. For barbell, 1- and 10-year returns are averaged. For ladder, returns of all 5 maturities are averaged. But for Buy-and-Hold, some caution is needed.

Buy-and-Hold (BH) strategy 1) buy a bond with issuance maturity and 2) hold it until maturity. Therefore, as soon as redemption is made, new BH strategy starts with a full issuance maturity. This means that remaining maturity shows periodic behavior. For example, time variation of remaining maturity of 3-year BH portfolio is as follows.

\[\begin{align} 2 &\rightarrow 1.92 \rightarrow 1.83 \rightarrow… \\ &\rightarrow 0.17 \rightarrow 0.08 \rightarrow 2 \rightarrow 1.92 \rightarrow … \end{align}\]
The following figure shows the pattern of remaining maturity of BH portfolio with selected maturities.
buy and hold bond portfolio maturity pattern

Hence, monthly return series for BH portfolios for each maturity are defined in in full interpolation monthly return grid. As time passes, position of current spot rates are moved to the left by \(\frac{1}{12}\) in full interpolated spot rate grid until its maturity approaches zero. Of course, after redemption take places, position of current spot rate goes back to the original position which corresponds to the issuance maturity.


R code for Bond Portfolio


The following R code demonstrates the calculation of monthly and cumulative returns of 4 benchmark bond portfolio using Diebold, Rudebusch, and Aruoba (2006) data,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#=========================================================================#
# Financial Econometrics & Derivatives, ML/DL using R, Python, Tensorflow  
# by Sang-Heon Lee 
#
# https://kiandlee.blogspot.com
#————————————————————————-#
# Benchmark Bond Portfolio Return Calculation
#=========================================================================#
 
library(readxl)
library(xlsx)
library(RColorBrewer)
 
graphics.off()  # clear all graphs
rm(list = ls()) # remove all files from your workspace
 
setwd(“D:/SHLEE/a_blog_ki_and_Lee/bond_portfolio”)
 
#——————————————————
# 0) Read DRA (2006) spot yield curve data
#——————————————————
 
    fname < “dra_spot_cc_data.xlsx”
    
    # maturity, date, spot rate
    vmatm   < read_excel(fname, “spotcc”,“B1:R1”,   col_names = FALSE)
    df.ym   < read_excel(fname, “spotcc”,“A2:A480”, col_names = FALSE)
    df.spot < read_excel(fname, “spotcc”,“B2:R480”, col_names = FALSE)
 
    # maturity as decimal, spot as y
    vmatm < as.numeric(vmatm); vmaty < vmatm/12
    y     < as.matrix(df.spot/100); 
    colnames(y) < vmatm
    
    # counting 
    nmat < length(vmaty); 
    ny < nrow(y); nr < ny  1# number of yields and returns
 
#——————————————————
# 1) Interpolate monthly spot rates using cubic spline
#——————————————————
 
    # .ip : interpolated
    
    # use 0 for 1-month return temporarily
    matm.ip     < (0:max(vmatm))
    max.matm.ip < max(matm.ip)
 
    # use apply() for row-wise interpolation
    # output => collection of column vector => so transpose
    y.ip < t(apply(y, 1
                function (x) { 
                  # make interpolation function
                  fs<splinefun(vmatm,x); 
                  # apply fs function to each row
                  fs(matm.ip)
                }
             ))
    
#——————————————————
# 2) Calculate 1-month returns
#——————————————————
    
    # interpolated spot rate
    r.ip < matrix(0, ny1, length(matm.ip)1)
    
    # calculate monthly returns
    for (t in 2:ny) {
        
        # spot rates and maturities at t and (t-1)
        spot.t   < y.ip[t,1:max.matm.ip]
        spot.t_1 < y.ip[t1,2:(max.matm.ip+1)]
        mat.t    < matm.ip[1:max.matm.ip]/12
        mat.t_1  < matm.ip[2:(max.matm.ip+1)]/12
        
        # log-return expression
        r.ip[t1,] = log(exp(spot.t*mat.t)/exp(spot.t_1*mat.t_1))
    }
 
    # draw a graph for monthly returns
    x11(width=6, height=4); 
    par(mar =  c(5446+ 0.1)
    matplot(r.ip[,c(1206036123)], type = “l”, lty = 1
            col = c(brewer.pal(4“Paired”),“black”),
            ylab = “return”, xlab = “date”, lwd = 2,
            main = “Monthly Zero Coupon Bond Returns”)
    legend(“right”, inset = c(0.3,0), 
           legend = paste(c(1206036123),“M”), xpd = TRUE, 
           horiz = FALSE, col = c(brewer.pal(4“Paired”),“black”),
           lty = 1, bty = “n”, lwd = 2)
    
#——————————————————
# 3) Construct benchmark bond portfolios
#    – barbell, bullet, ladder, Buy-and-Hold
#——————————————————
    
    # selected maturity
    maty_bm < c(1,3,5,7,10)
    matm_bm < maty_bm*12
    nc < length(maty_bm)     # number of selected maturities
    
    # 1. Barbell (1, 10 year)
    maty1 < c(1,10)
    col1.ret < rowMeans(r.ip[1:nr, maty1*12])
    col1.dur < matrix(1/2, nr,2)%*%maty1
    
    # 2. Bullet 
    col2.ret < r.ip[1:nr, matm_bm]
    colnames(col2.ret) < paste0(“Bullet(“, maty_bm, “)”)
    col2.dur < matrix(1,nr,nc)%*%diag(maty_bm); 
    colnames(col2.dur) < colnames(col2.ret)
    
    # 3. Ladder (equal weights for all maturities)
    col3.ret < rowMeans(r.ip[1:nr, matm_bm])
    col3.dur < matrix(1/nc, nr, nc)%*%maty_bm
    
    # 4. Buy-and-Hold
    bah.ret < bah.maty < bah.matm < matrix(0,nr, nc)
    for (t in 1:nr) { for(j in 1:nc) {
            
        # As t move forward, remaining maturity decreases.
        # When remaining maturity is zero, 
        # a new bond is purchased
        # the remaining maturity is set to the issuance maturity.
        matm < matm_bm[j](t1)%%matm_bm[j]
        
        bah.matm[t,j] < matm
        bah.maty[t,j] < matm/12
        bah.ret[t,j] < r.ip[t, matm]
    }}
    col4.ret < bah.ret; col4.dur < bah.maty; 
    colnames(col4.ret) < paste0(“BH(“, maty_bm,“)”)
    colnames(col4.dur) < colnames(col4.ret)
    
    # collect portfolio returns
    port.ret < cbind(col1.ret,col2.ret,col3.ret,col4.ret)
    colnames(port.ret)[1< “Barbell(1,10)”
    colnames(port.ret)[7< “Ladder”
    
#——————————————————
# 4) Cumulative Returns
#——————————————————
    
    port.cum.ret < port.ret*0;
    
    # cumulative return
    for(i in 1:ncol(port.ret)) {
        port.cum.ret[,i] < cumprod(1+port.ret[,i])1
    }
 
#——————————————————
# 5) Performance Statistics
#——————————————————
    
    # Use data.frame not matrix when using sapply
    out.port < sapply(as.data.frame(port.ret),
        function(x) {
            # average, stdev, Sharpe
            col1 < mean(x)*100*12
            col2 < sd(x)*100*sqrt(12)
            col3 < col1/col2
            return(c(avg=col1, stdev = col2, Sharpe = col3))})
    
    # print out
    round(out.port[,1:3],2)
    round(out.port[,4:6],2)
    round(out.port[,7:12],2)
    
cs


The following figure shows the monthly returns of 5-maturity bond which is, indeed, that of bullet portfolio. We can find that the longer the maturity of a bond, the more sensitive is its return to a change in interest rates. Therefore, it is not easy to forecast future return of a long-term bond.

monthly zero coupon bond returns

To investigate the performance of bond portfolio strategy, it is useful to calculate the cumulative returns as follows. We can find a stylized fact that long term bond shows the higher returns and higher volatility.

cumulative returns of benchmark bond portfolio

Using mean and standard deviation, we can calculate the Sharpe ratio which is the risk-adjusted return as follows. (In fact, it is typical to use the excess return when calculating the Sharpe ratio and therefore this is the same as we assume 0% risk-free rate)


From this post, we have constructed some benchmark bond portfolio and calculate its monthly returns and cumulative performances. 

This analysis can be also applied to coupon bond portfolio. For more information, refer to Deguest, Fabozzi, Martellini, and Milhau (2018).

Reference


Deguest, R., F. Fabozzi, L. Martellini, and V Milhau (2018), “Bond Portfolio Optimization in the Presence of Duration Constraints,” The Journal of Fixed Income 28, 6-26

Diebold, F. X., G. D. Rudebusch, and S. B. Aruoba (2006), “The Macroeconomy and the Yield Curve: A Dynamic Latent Factor Approach,” Journal of Econometrics 131, 309-338. \(\blacksquare\)

To leave a comment for the author, please follow the link and comment on their blog: K & L Fintech Modeling.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)