Conventional economic theory says that long term bonds are more sensitive to interest rate changes than short term bonds. Intuitively, we should see higher realized volatility in funds that track these types of bonds. Here, I want to test that if as you move from short duration bonds to higher and higher duration bonds, then the difference of mean volatility grows larger and larger. To do this, I selected Blackrock ETF’s of various lengths listed below:
SHY, 1-3 years. IEI, 3-7 years. IEF, 7-10 years. TLH, 10-20 years. TLT, 20+ years.
Comparing difference of means is fairly simple to do from a frequentist standpoint; the t-test is a straightforward procedure that does this. However, to better estimate difference of means I want to use a Bayesian approach and follow the BEST procedure, which was developed by Dr. John Krushke at Indiana University. A quick overview is that rather testing and rejecting null hypotheses, the Bayesian approach estimates distributions for group means and their differences. This is more robust and provides a richer picture of the underlying data.To learn more, I suggest reading the PyMC article as well as the original Krushke paper. In this article, I will use the Krushke package that he wrote himself.
To estimate realized volatility, there are a few ways to do it but I followed the “Realized Volatility Puzzle” method from Harel Jacobsen (article linked below) and decided on the Garman-Klass Volatility estimator over a 10 day window.
Here are the results:
SHY v IEI
library(tidyr)
library(TTR)
## Warning: package 'TTR' was built under R version 4.1.3
library(BEST)
## Warning: package 'BEST' was built under R version 4.1.3
## Loading required package: HDInterval
## Warning: package 'HDInterval' was built under R version 4.1.3
shy1 = riingo_prices("shy", start_date = "01-01-2010", end_date = "07-04-2022")
iei = riingo_prices("iei", start_date = "01-01-2010", end_date = "07-04-2022")
# measuring 10 day realized volatility
# using garman klaus volatility
# https://medium.com/swlh/the-realized-volatility-puzzle-588a74ab3896
n = 10
shy1_vol = volatility(data.frame(shy1[6],shy1[,3:5]), n = n, N = 252, mean0 = FALSE)
shy1_vol = drop_na(as.data.frame(shy1_vol))
iei_vol = volatility(data.frame(iei[6],iei[,3:5]), n = n, N = 252, mean0 = FALSE)
iei_vol = drop_na(as.data.frame(iei_vol))
shy_v_iei = BESTmcmc(shy1_vol$shy1_vol*100, iei_vol$iei_vol*100,
parallel = TRUE, numSavedSteps = 1000)
## Waiting for parallel processing to complete...
## done.
summary(shy_v_iei)
## mean median mode HDI% HDIlo HDIup compVal %>compVal
## mu1 0.717 0.717 0.718 95 0.700 0.732
## mu2 2.627 2.627 2.628 95 2.589 2.669
## muDiff -1.910 -1.910 -1.917 95 -1.947 -1.866 0 0
## sigma1 0.347 0.347 0.346 95 0.334 0.361
## sigma2 0.942 0.943 0.944 95 0.909 0.977
## sigmaDiff -0.595 -0.596 -0.598 95 -0.629 -0.563 0 0
## nu 3.410 3.408 3.423 95 3.140 3.723
## log10nu 0.532 0.533 0.535 95 0.497 0.571
## effSz -2.690 -2.691 -2.699 95 -2.787 -2.596 0 0
SHY v IEF
library(BEST)
library(TTR)
library(tidyr)
shy2 = riingo_prices("shy", start_date = "01-01-2010", end_date = "07-04-2022")
ief = riingo_prices("ief", start_date = "01-01-2010", end_date = "07-04-2022")
# measuring 10 day realized volatility
# using garman klaus volatility
# https://medium.com/swlh/the-realized-volatility-puzzle-588a74ab3896
n = 10
shy2_vol = volatility(data.frame(shy2[6],shy2[,3:5]), n = n, N = 252, mean0 = FALSE)
shy2_vol = drop_na(as.data.frame(shy2_vol))
ief_vol = volatility(data.frame(ief[6],ief[,3:5]), n = n, N = 252, mean0 = FALSE)
ief_vol = drop_na(as.data.frame(ief_vol))
shy_v_ief = BESTmcmc(shy2_vol$shy2_vol*100, ief_vol$ief_vol*100,
parallel = TRUE, numSavedSteps = 1000)
## Waiting for parallel processing to complete...done.
summary(shy_v_ief)
## mean median mode HDI% HDIlo HDIup compVal %>compVal
## mu1 0.717 0.717 0.718 95 0.701 0.731
## mu2 4.786 4.786 4.785 95 4.720 4.849
## muDiff -4.069 -4.068 -4.066 95 -4.131 -4.000 0 0
## sigma1 0.348 0.348 0.348 95 0.335 0.361
## sigma2 1.518 1.518 1.519 95 1.459 1.570
## sigmaDiff -1.169 -1.170 -1.167 95 -1.221 -1.110 0 0
## nu 3.434 3.434 3.447 95 3.155 3.742
## log10nu 0.535 0.536 0.538 95 0.501 0.575
## effSz -3.697 -3.695 -3.689 95 -3.846 -3.565 0 0
SHY v TLH
library(BEST)
library(TTR)
library(tidyr)
shy3 = riingo_prices("shy", start_date = "01-01-2010", end_date = "07-04-2022")
tlh = riingo_prices("tlh", start_date = "01-01-2010", end_date = "07-04-2022")
# measuring 10 day realized volatility
# using garman klaus volatility
# https://medium.com/swlh/the-realized-volatility-puzzle-588a74ab3896
n = 10
shy3_vol = volatility(data.frame(shy3[6],shy3[,3:5]), n = n, N = 252, mean0 = FALSE)
shy3_vol = drop_na(as.data.frame(shy3_vol))
tlh_vol = volatility(data.frame(tlh[6],tlh[,3:5]), n = n, N = 252, mean0 = FALSE)
tlh_vol = drop_na(as.data.frame(tlh_vol))
shy_v_tlh = BESTmcmc(shy3_vol$shy3_vol*100, tlh_vol$tlh_vol*100,
parallel = TRUE, numSavedSteps = 1000)
## Waiting for parallel processing to complete...done.
summary(shy_v_tlh)
## mean median mode HDI% HDIlo HDIup compVal %>compVal
## mu1 0.713 0.713 0.712 95 0.698 0.730
## mu2 7.127 7.128 7.130 95 7.031 7.237
## muDiff -6.414 -6.414 -6.413 95 -6.521 -6.316 0 0
## sigma1 0.341 0.341 0.341 95 0.328 0.354
## sigma2 2.315 2.316 2.312 95 2.229 2.396
## sigmaDiff -1.974 -1.976 -1.986 95 -2.050 -1.886 0 0
## nu 3.109 3.100 3.077 95 2.889 3.376
## log10nu 0.492 0.491 0.488 95 0.461 0.528
## effSz -3.878 -3.878 -3.887 95 -4.022 -3.728 0 0
SHY v TLT
library(BEST)
library(TTR)
library(tidyr)
shy4 = riingo_prices("shy", start_date = "01-01-2010", end_date = "07-04-2022")
tlt = riingo_prices("tlt", start_date = "01-01-2010", end_date = "07-04-2022")
# measuring 10 day realized volatility
# using garman klaus volatility
# https://medium.com/swlh/the-realized-volatility-puzzle-588a74ab3896
n = 10
shy4_vol = volatility(data.frame(shy4[6],shy4[,3:5]), n = n, N = 252, mean0 = FALSE)
shy4_vol = drop_na(as.data.frame(shy4_vol))
tlt_vol = volatility(data.frame(tlt[6],tlt[,3:5]), n = n, N = 252, mean0 = FALSE)
tlt_vol = drop_na(as.data.frame(tlt_vol))
shy_v_tlt = BESTmcmc(shy4_vol$shy4_vol*100, tlt_vol$tlt_vol*100,
parallel = TRUE, numSavedSteps = 1000)
## Waiting for parallel processing to complete...done.
summary(shy_v_tlt)
## mean median mode HDI% HDIlo HDIup compVal %>compVal
## mu1 0.716 0.716 0.715 95 0.701 0.731
## mu2 11.120 11.119 11.109 95 10.983 11.274
## muDiff -10.405 -10.404 -10.406 95 -10.550 -10.260 0 0
## sigma1 0.345 0.345 0.342 95 0.332 0.357
## sigma2 3.394 3.393 3.397 95 3.276 3.519
## sigmaDiff -3.049 -3.048 -3.046 95 -3.171 -2.931 0 0
## nu 3.319 3.308 3.296 95 3.090 3.605
## log10nu 0.521 0.520 0.519 95 0.490 0.557
## effSz -4.315 -4.312 -4.296 95 -4.489 -4.168 0 0
As you can see, it is clear that each ETF has different amounts of volatility, and that the longer duration ETF has the higher mean volatility.This confirms the economic tuition that long term bonds are more sensitive to interest rates, and thus leads to higher volatility.
Hopefully this article gave insight as to what the BEST process is and why it is more informative than a standard t-test. While the t-test would have worked for a simple example like the one done here, for more nuanced questions I would default to using the BEST process, with more informative priors.
Sources:
https://www.pymc.io/projects/examples/en/latest/case_studies/BEST.html https://jkkweb.sitehost.iu.edu/articles/Kruschke2013JEPG.pdf https://medium.com/swlh/the-realized-volatility-puzzle-588a74ab3896
Data:
Tiingo