- Details
- Parent Category: Economics Assignments' Solutions
We Helped With This Economics Assignment: Have A Similar One?
Short Assignment Requirements
Assignment Description
ECON 426 Project 6 – TNX SPX Divergence
CW
February 26, 2019
Project #6 – Divergence between the 10-year Treasury Bond and the S&P 500
Due: Monday March 4, 2019
It is a truism in macro-finance that when there is a divergence between S&P 500 stock prices and the 10-year
Treasury Bond yield, the bond market is always right. The presumption is that the stock market catches down (or up) to the bond market. That’s the “always right” portion of the statement.
In this project you are to test this hypothesis. Calculate divergences between the two securities, then see how long these divergences take to close. You need to make assumptions about what constitutes divergence and re-convergence (i.e., closure).
Give a summary – or better, distribution – of these closure times. You may want to do this for different time periods, and different divergence thresholds.
Finally, assess whether it’s bonds or stocks that are moving the most to converge each divergence.
In your write-up, before describing your methodology and results, please provide an explanation for why the hypothesized “bond market always right.” You don’t have to refer to any equity valuation theories, but you may.
What follows is some R code to do a decent chunk of the background work for you. It’s decently written, but not totally awesome. I’ve left out a bunch of plot labels and titles. Use this code, or not, as you wish. I’m posting the knitted version of this as a PDF, plus also the code as a .RMD (R-markdown) file.
If you DON’T know R, I’d suggest partnering with one or more people who do. If you have a particular request for a plot, etc. let me know and I’ll try to accommodate you. You can do a lot of this in Excel, actually, but then you’d be replicating work I’ve already done.
If you DO know R but need help with your code, let me know. Or ask Ken Z. or Cassie K. or Seth U. to help you – it’d be good for them.
library(tidyquant) library(gridExtra) library(scales) # to access breaks/formatting functions |
Get stock pairs
Here I import the U.S. Treatury 10-year bond yield (symbol ˆTNX), and the S&P 500 tracking ETF (symbol SPY). I can’t easily import the actual S&P 500 index data, and don’t feel like trying.
stock_prices <- c("^TNX", "SPY") %>%
tq_get(get = "stock.prices", from = "2017-01-01", to = "2018-12-31") %>%
group_by(symbol)
You can see here that I’m importing data from the beginning of 2017 through 2018. You can change this as you wish. These are daily data by default. To look at them, run: .
Convert to daily returns
stock_pairs <- stock_prices %>% tq_transmute(select = adjusted, mutate_fun = periodReturn, period = "daily", type = "log", col_rename = "returns") %>% spread(key = symbol, value = returns) # Rename ^TNX to TNX names(stock_pairs)[2] <- "TNX" |
Plot the relationship between SPY and ˆTNX. You don’t have to do this.
stock_pairs %>% ggplot(aes(x = SPY, y = TNX)) + geom_point(color = palette_light()[[1]], alpha = 0.5) + geom_smooth(method = "lm") + labs(title = "Visualizing Returns Relationship of Stock and Bond Returns") + theme_tq() |
Visualizing Returns Relationship of Stock and Bond Returns
SPY
lm(TNX ~ SPY, data = stock_pairs) %>%
summary()
##
## Call:
## lm(formula = TNX ~ SPY, data = stock_pairs)
##
## Residuals:
## Min 1Q Median 3Q Max ## -0.052041 -0.007874 -0.000353 0.008119 0.038733
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.00009899 0.00057768 0.171 0.864
## SPY 0.45615455 0.07066178 6.455 0.000000000256 *** ## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.01292 on 499 degrees of freedom
## Multiple R-squared: 0.07708, Adjusted R-squared: 0.07523
## F-statistic: 41.67 on 1 and 499 DF, p-value: 0.000000000256
regr_fun <- function(data) { coef(lm(TNX ~ SPY, data = timetk::tk_tbl(data, silent = TRUE))) } stock_pairs <- stock_pairs %>% tq_mutate(mutate_fun = rollapply, width = 90, FUN = regr_fun, by.column = FALSE, col_rename = c("coef.0", "coef.1")) stock_pairs %>% ggplot(aes(x = date, y = coef.1)) + geom_line(size = 1, color = palette_light()[[1]]) + geom_hline(yintercept = 0.6138327, size = 1, color = palette_light()[[2]]) + labs(title = "TNX ~ SPY: Visualizing Rolling Regression Coefficient", x = "") + theme_tq() -> plot1 |
Note that they actually are very poorly correlated over this date range. There’s a correlation of 0.376.
Indexing cumulative returns to a starting “wealth.index” value of 100
This is the more important part. I’m supposing that you purchase $100 of both securities at the beginning of the period under review, then am tracking cumulative return.
stock_prices %>% tq_transmute(adjusted, periodReturn, period = "daily", type = "log", col_rename = "returns") %>% |
mutate(wealth.index = 100 * cumprod(1 + returns)) %>% ggplot(aes(x = date, y = wealth.index, color = symbol)) + geom_line(size = 1) + labs(title = "TNX and SPY: Bond Yield vs. Stock Prices") + theme_tq() + scale_color_tq() -> plot2 grid.arrange(plot1, plot2, ncol = 1) |
## Warning: Removed 89 rows containing missing values (geom_path).
TNX ~ SPY: Visualizing Rolling Regression Coefficient
TNX and SPY: Bond Yield vs. Stock Prices
symbol ^TNX
SPY
On the bottom panel you can see how the values of the two holdings vary over time. This are cumulative returns. Now I calculate the daily divergence between SPY and TNX returns; positive means SPY returns are larger (more positive).
stock_pairs$diverge <- (stock_pairs$SPY - stock_pairs$TNX) # Calculating the rolling sum of the divergence between the two securities. stock_pairs <- stock_pairs %>% tq_mutate(select = diverge, mutate_fun = rollapply, width = 30, FUN = sum, by.column = FALSE, col_rename = c("divergence")) |
Plotting the Divergence
stock_pairs %>% ggplot(aes(x=date,y=divergence)) + geom_line(size=1, color = "purple") + theme_tq() + scale_color_tq() + scale_x_date(date_breaks = "3 months", date_labels = plot3 |
"%b %y") -> plot3
## Warning: Removed 29 rows containing missing values (geom_path).
Feb 17 May 17 Aug 17 | Nov 17 Feb 18 date | May 18 | Aug 18 | Nov 18 | Feb 1 |
grid.arrange(plot2, plot3, ncol = 1) |
|
|
|
|
|
## Warning: Removed 29 rows containing missing values (geom_path).
TNX and SPY: Bond Yield vs. Stock Prices
Identifying the divergences and closures
Here you need to identify your threshold for what constitutes a divergence, and then a convergence. If you take a divergence to be 10% and consider it closed once the divergence gets back to 0%, then you can identify the first divergence/closure from the 2nd panel of the above plot.
In this case, June 30, 2017 is when it closes.
stock_pairs[120:200,]
## # A tibble: 81 x 7
## date TNX SPY coef.0 coef.1 diverge divergence
## <date> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> | ||
## 1 2017-06-23 -0.00419 0.00119 -0.00245 1.62 0.00538 | 0.133 | |
## 2 2017-06-26 -0.00327 0.000658 -0.00254 | 1.60 0.00393 | 0.111 |
## 3 2017-06-27 0.0281 -0.00809 -0.00178 | 1.39 -0.0362 | 0.0709 |
## 4 2017-06-28 0.0104 0.00891 -0.00167 | 1.40 -0.00150 | 0.0664 |
## 5 2017-06-29 0.0205 -0.00883 -0.00117 | 1.26 -0.0293 | 0.00529 |
## 6 2017-06-30 0.0153 0.00186 -0.00100 | 1.27 -0.0135 | -0.00455 |
## 7 2017-07-03 0.0189 0.00169 -0.000671 | 1.28 -0.0172 | -0.0229 |
## 8 2017-07-05 -0.00513 0.00231 -0.000410 | 1.29 0.00744 | -0.0165 |
## 9 2017-07-06 0.0153 -0.00919 -0.000297 | 1.15 -0.0245 | -0.0296 |
## 10 2017-07-07 0.00966 0.00646 -0.000257 ## # ... with 71 more rows | 1.15 -0.00319 | -0.0435 |
Graphically:
stock_pairs[120:200,] %>% ggplot(aes(x=date,y=divergence)) + geom_line(size=1, color = theme_tq() + scale_color_tq() + scale_x_date(date_breaks = "2 weeks", date_labels = plot3.1 |
"purple") +
"%d %b") -> plot3.
Next Steps
1. Figure out how to automatically identify divergences/closures. This isn’t that hard.
2. Figure out how long each divergence/closure episode lasts. This also isn’t hard, if you do the first part right.
3. Calculate the probability of divergences closing within various time frames. Hint: plot distribution of divergence closure times.
4. Change around the thresholds you’re using, or the date ranges, or both.
5. (Part of the actual assignment, but a bit harder) Identify the manner in which divergences are closed – does SPY move to match back up with the ˆTNX cumulative return, or vice versa? It’d usually be a combination of both.
6. (Completely optional, but fun) Ignoring trading commissions and slippage, calculate your average annual return from putting on this divergence trade. At the start of the divergence go long ˆTNX (or short the corresponding T-bond ETF, or long the corresponding inverse T-bond ETF) and short the SPY. Or do the reverse if it’s the bond cumulative return that’s below the SPY cumulative return. Be clear about whether you are reinvesting your proceeds or not. [Note: this is a little bit of a trick question. Think about it before you do any fancy calculations.]