[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.
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,

 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 #=========================================================================## 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 graphsrm(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, ny–1, 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[t–1,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[t–1,] = 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(5, 4, 4, 6) + 0.1)    matplot(r.ip[,c(120, 60, 36, 12, 3)], 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(120, 60, 36, 12, 3),“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]–(t–1)%%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) <– “Barbell(1,10)”    colnames(port.ret) <– “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)    Colored by Color Scripter 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.

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.

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)