The dashboard which this vignette supports can be found here: Dashboard
Also the full code is on Github
Imagine a container full of particles bouncing around. We can measure the energy of the entire container and the amount of energy of each individual particle. But what if we want to measure the effect of one particle’s energy on other particles? The entire system? If we assume there is one particle with the highest amount of energy we can conclude the particles nearest to it derive a some of their energy from that highest energy particle and those particles pass it to the ones near them and so on. If we want to predict the energy of one of these particles we would be misled if we did not consider the particles nearby and only analyzed each particle individually as an independent observation.
If we replace particles with stock prices, and energy with volatility of stock returns, then our container becomes an index of stocks. We expect stocks within the same industry to be correlated with each other (i.e. to share energy), and some stocks to “share” their volatility with some more than others. Measuring and predicting the volatility in returns of each stock independently will similarly miss other drivers as in the particle example.
Standard statistical and econometric methods such (vector) autoregressions assume (or impose) independence of variables, but it will be shown below that relaxing the assumption of independence does not improve a method’s forecast but it does enable researchers to better isolate other drivers of volatility in stock price return volatility. It is clear that in the world of finance, especially the stock market, the choice of variables matters because financial variables are often highly correlated and it is feasible to imagine the price oil being correlated with Google’s stock price for instance. Hence it is very important that the selection of variables be correlated with reason.
This vignette combines and replicates the work done in Dieblod and Yilmaz 2009, DY 2010, and DY 2011, as well as scaling the methods for quick application and replication. The DY papers describe how to quantify in an index the amount of forecast error that can be attributed to shocks of the error terms of each dependent variable in vector autoregression. Consider a set of stocks for which we want to estimate and forecast either returns or volatility, and control for the systematic, or common, factor driving each one, then whatever cannot be explained by common factors is idiosyncratic error. Then an interesting question to ask is whether these idiosyncratic errors of some stocks can effect the forecast others. In other words, do idiosyncratic errors spillover into common factors’ ability to forecast? These “spillovers” are found using standard time-series techniques for finding impulse-response functions and forecast error variance decompositions. In addition, DY 2011 show how the effects of these shocks can be represented as a network when one relaxes the assumptions that vector autoregressions have normally distributed, i.i.d. error terms and that shocks to each variable must be orthogonal to the other shocks. In the spirit of DY 2011, stocks of large banks and AIG will be the subjects of analysis. Like all the DY publications, the analysis will focus on connections and spillovers of volatility.
This vignette will proceed as follows: Section 2 will describe the data source and summarize the data; Section 3 will motivate the concepts of spillovers and connectedness using a analysis of the entire sample of data; Section 4 will use a rolling window over the time-series data, applying the same concepts from the full sample analysis to derive an index of spillover/conncectedness and measure behavior of the network over time; Section 5 will apply the methods developed to study the effect of the dollar exchange rate on the Collateralized Loan Obligation (CLO) market building on research from Niepmann & Schmidt-Eisenlohr.
The data is pulled from Yahoo! Finance using the getSymbols() function from the quantmod package, which includes Open, Close, High, and Low prices for a user specified set of stocks/indexes over a chosen period of time measured in days. The data is similar to that in DY 2010, so measures of stock return volatility used in the analysis and app are similar to that publication. First daily variance for stock \(i\) at time \(t\) is estimated using high and low prices:
\[\begin{equation} \label{eq:variance} \sigma^2_{it} = 0.361[ln(P^{max}_{i,t}) - ln(P^{min}_{i,t-1}))]^2 \end{equation}\]To analyze the volatility of closed-end funds the minimum price is observed at \(t-1\), since High, Low, Open, and Close prices on any given day are the same for these funds. Hence, returns and volatilities for these funds would be zero when using prices recorded on the same day.
Since volatilities tend to be skewed, it is common practice to use log-volatilities which closely approximate a normal distribution. However, to control for instances when volatility is zero, \(sinh^{-1}\) is used instead of the natural logarithm (\(sinh^{-1}(x) = log(2x)\)).
\[\begin{equation} \sigma_{it} = sinh^{-1}(\sqrt{252*\sigma^2_{it}}) \label{eq:stdev} \end{equation}\]Using the quantmod getSymbols function, read in the set of stock prices for Barclays (BK), Bank of Nova Scotia (BNS), TD Bank (TD), Well Fargo (WFC), Goldman Sachs (GS), JP Morgan Chase & Co. (JPM), Morgan Stanley (MS), Bank of America (BAC), Credit Suisse (CS), Deutsche Bank (DB), HSBC, Citibank (C), and AIG over time period spanning January 2, 2006 to December 30 2016. This time period capture the run up, aftermath, and recovery of the 2008 financial crisis. The daily volatilities which are shown in Figure (1) and summarized in Table 1.
## [1] "AIG" "BAC" "BK" "BNS" "C" "CS" "DB" "GS" "HSBC" "JPM"
## [11] "MS" "TD" "WFC"
| Name | Count | Mean | Median | SD | Min | Max |
|---|---|---|---|---|---|---|
| BK | 2767 | 0.2414055 | 0.1739952 | 0.2355072 | 0 | 2.348819 |
| JPM | 2767 | 0.2412604 | 0.1746596 | 0.2310831 | 0 | 1.992054 |
| GS | 2767 | 0.2413190 | 0.1802763 | 0.2276783 | 0 | 2.215029 |
| C | 2767 | 0.2922059 | 0.1889913 | 0.3121762 | 0 | 2.643312 |
| MS | 2767 | 0.3061973 | 0.2229228 | 0.2916601 | 0 | 2.954439 |
| BNS | 2767 | 0.1815797 | 0.1339244 | 0.1753930 | 0 | 1.553637 |
| AIG | 2767 | 0.3065092 | 0.1883449 | 0.3500704 | 0 | 3.567131 |
| TD | 2767 | 0.1742599 | 0.1295371 | 0.1651353 | 0 | 1.646948 |
| HSBC | 2767 | 0.1610396 | 0.1141410 | 0.1648465 | 0 | 1.679011 |
| DB | 2767 | 0.2509589 | 0.1842325 | 0.2367057 | 0 | 1.873443 |
| BAC | 2767 | 0.2811012 | 0.1944293 | 0.2905163 | 0 | 2.469604 |
| CS | 2767 | 0.2287849 | 0.1654587 | 0.2200668 | 0 | 1.848902 |
| WFC | 2767 | 0.2406566 | 0.1612006 | 0.2528949 | 0 | 2.152670 |
chart.TimeSeries(vol.data,lwd=2,auto.grid=F,ylab="Annualized Log Volatility",xlab="Time",
main="Log Volatility",lty=1,
legend.loc="topright")
Figure 1: Annualized Log Volatility
In time-series analysis (and most regressions generally) the most interesting information is found in the distribution of the error terms, especially the variance. In a multivariate model the distribution of error terms embeds the assumptions made regarding correlations of dependent variables (i.e. a diagonal covariance matrix assumes independence). In financial econometrics forecasting stock price returns or volatility entails two components - systematic and idiosyncratic - codified as a regression:
\[\begin{align} \sigma_{it} = \beta \sigma_{M,t-1} + \epsilon_{it} \label{eq:beta} \end{align}\]Where \(\sigma_{it}\) is the volatility of stock \(i\) at time \(t\), \(\sigma_M\) is the market volatility (i.e. the volatility on the S&P 500, Dow Jones, etc.), \(\beta\) is a measure of the systematic portion of a stock’s volatility, or the strength/measure of its relationship with the broader market, and \(\epsilon\) is the idiosyncratic portion that pertains to stock \(i\). The interesting question is how responsive is the future volatility \(\sigma_{i,t+1}\), at time \(t+1\), to a shock to \(\epsilon_{it}\) ? And in a system of several different volatilities, how does a shock to the idiosyncratic term, \(\epsilon_{jt}\) , of variable \(j\) at time \(t\) effect \(\sigma_{i,t+1}\) for variable \(i\) at time \(t+1\) ? In other words, we are interested in finding whether and how idiosyncratic risks of one stock’s volatility effect the future volatilities of other stocks. These effects are known as spillovers or connectedness.
To derive a measure of these spillovers, the calculations proceed in four steps:
where \(\sigma_{t}\) and \(\sigma_{t-l}\) are vectors of length \(N\), each \(\beta_{t-l}\) is an \(NxN\) coefficient matrix, and \(\epsilon_t\) is a vector of error terms with a distribution \(N(0,\sigma^2)\).
Where each \(\phi\) is a matrix of coefficients which measure the magnitudes of each impulse. Here an impulse is defined as a unitary shock to \(\epsilon_{t-i}\).
where \(e_i\) and \(e_j\) are basis vectors with unity at \(i\) and \(j\) respectively, \(A\) is a matrix of \(\phi\) cefficients. The impulse response is the cumulative error of forecast from a shock to variable \(i\) from variable \(j\) at time \(t-l\).
The numerator is just the impulse response and the denominator is the total forecast error in a system of variables and \(\Sigma\) is the error covariance marix. The forecast error variance of varaible \(i\) attributable to variable \(j\) is the impulse respnse of \(j\) on \(i\) divided by the total forecast error of \(i\).
For textbook treatment of how this is done algebraically see Zivot, Cochrane Chapter 7, and Pesaran and Shin 1997 Section 2 for estimating both correlated an uncorrelated shocks.
The difference between spillovers and connectedness in the DY papers is the difference between assuming errors are orthogonal versus correlated. To estimate a model with correlated errors is as simple as feeding a VAR the set of variables one wants to analyze. To estimate a model with orthogonal errors using variables which are correlated requires imposing a structure on the VAR to achieve independent error terms. The vars package in R has VAR and SVAR functions vars The SVAR function estimates a Structural VAR by giving it a matrix that is structured according to a set of assumptions. For this excercise I used a lower triangular matrix of coefficients with 1s along the diagonal as the Sims critique stipulates. For a quick overview of Sims see Section 11.4.2 of Zivot.
vol_var = VAR(vol.data,p=3,type="none")
amat <- diag(ncol(vol.data))
amat[lower.tri(amat)] <- NA
vol_svar = SVAR(vol_var,Amat = amat,estmethod = "direct")
### extract residuals of the VAR
res_t <- residuals(vol_var)
svar_ecov <- vol_svar$Sigma.U
Generally speaking an type of autoregression of order \(p\) can be converted to a moving average of order \(q\) as long as the coefficients of the \(AR(p)\) are between -1 and 1. The vars package includes a function Phi which will do this conversion for us by specifying the ordoer \(q\) of MA we want - in this case 10. From this estimate the MA coefficients are retrieved, along with the residual errors from the VAR, for the next steps.
MA_lag <- 10
theta_temp <- Phi(vol_var,nstep = MA_lag)
svar_theta_temp <- Phi(vol_svar,nstep = MA_lag)
### extract MA coefficients
theta.list <- alply(theta_temp,3)
svar_theta_list <- alply(svar_theta_temp,3)
For the SVAR, the function fevd.matrix in the code snippet implements the IRF and FEVD calculations, taking the covariance matrix of SVAR residuals and MA coefficients as inputs. To find the IRF derive the lower-triangular Cholesky-decomposition of the residuals’ covariance matrix. The MA coefficient matrix is impact of the shocks and the lower triangular matrix imposes the independence of the shocks that is assumed in the SVAR.
Multiplying each lag of MA coefficients by the Cholesky matrix, the FEVD is estimated. Picking out the row of MA coefficients for variable \(j\) and multiplying those by the shocks from variable \(i\) as shown in the formula below. The results are shown in Table 2 - the Spillover Table. (Note: the rows sum to 1 as expected, so we know the calculations are correct)
\[\begin{equation} \theta^o_{ij} = \frac{\sum^n_{l=0} (e'_i A_l P e_j)^2}{\sum^n_{l=0} (e'_i A_l \Sigma A'_l e_i)} \end{equation}\]\(\theta^o_{ij}\) is the orthogonal FEVD, \(A_l\) is the MA coefficient matrix at lag \(l\) out of \(n\) lags, \(\Sigma\) is the residual covariance matrix, \(P\) is the lower triangular matrix, and \(e_i\) and \(e_j\) are basis vectors with unity at index \(i\) and \(j\), respectively. The numerator is the sum of all the forecast error over the time horizon \(n\) for variable \(i\) that can be attributed to variable \(j\), and the denominator is the total forecast error variance for variable \(i\).
| BK | JPM | GS | C | MS | BNS | AIG | TD | HSBC | DB | BAC | CS | WFC | From | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| BK | 69 | 6 | 3 | 1 | 4 | 3 | 3 | 2 | 0 | 1 | 1 | 1 | 7 | 31 |
| JPM | 4 | 68 | 3 | 1 | 3 | 4 | 1 | 3 | 0 | 0 | 1 | 1 | 11 | 32 |
| GS | 4 | 9 | 66 | 0 | 5 | 3 | 2 | 3 | 0 | 0 | 2 | 1 | 5 | 34 |
| C | 3 | 8 | 6 | 55 | 4 | 4 | 2 | 6 | 0 | 0 | 0 | 0 | 11 | 45 |
| MS | 7 | 8 | 2 | 1 | 59 | 4 | 3 | 7 | 0 | 0 | 0 | 1 | 8 | 41 |
| BNS | 3 | 4 | 3 | 1 | 3 | 79 | 1 | 1 | 0 | 0 | 1 | 1 | 4 | 21 |
| AIG | 3 | 7 | 6 | 0 | 2 | 3 | 55 | 7 | 1 | 0 | 1 | 0 | 14 | 45 |
| TD | 3 | 4 | 3 | 1 | 2 | 5 | 1 | 75 | 0 | 0 | 1 | 1 | 4 | 25 |
| HSBC | 2 | 3 | 3 | 1 | 2 | 4 | 1 | 5 | 74 | 0 | 0 | 1 | 4 | 26 |
| DB | 3 | 7 | 3 | 1 | 3 | 7 | 1 | 5 | 1 | 62 | 0 | 3 | 3 | 38 |
| BAC | 4 | 6 | 8 | 2 | 3 | 4 | 1 | 6 | 1 | 1 | 53 | 1 | 12 | 47 |
| CS | 3 | 6 | 2 | 0 | 3 | 5 | 1 | 5 | 1 | 2 | 1 | 65 | 4 | 35 |
| WFC | 4 | 7 | 6 | 2 | 2 | 2 | 1 | 3 | 2 | 1 | 1 | 1 | 67 | 33 |
| To | 44 | 75 | 47 | 10 | 35 | 49 | 17 | 52 | 8 | 7 | 9 | 12 | 86 | 35 |
The row sums (excluding the diagonal terms) of spillovers measure how much volatility the stock receives from the system. The column sums measure how much volatility each stock contributes to the system. Hence, each cell in the table is a measure of how much volatility from column \(i\) is given to row \(j\). One can estimate the net pairwise spillover by subtracting two spillovers. For instance, the spillover from Goldman Sachs (GS) to Morgan Stanley (MS) is 2, and from MS to GS is 5. Therefore, the net pairwise spillover from GS to MS is -3.
Connectedness requires similar calculations, except that the shocks from IRF are no longer assumed orthogonal. To achieve this, the lower triangular matrix \(P\) for \(\theta^o_{ij}\) in the numerator is replaced by the covariance matrix of the residuals. The generalized FEVD equation is then:
\[\begin{equation} \theta^g_{ij} = \frac{\sigma^{-1}_{ii} \sum^n_{l=0} (e'_i A_l \Sigma e_j)^2}{\sum^n_{l=0} (e'_i A_l \Sigma A'_l e_i)} \label{eq:gfevd} \end{equation}\]The interpretation of the numerator and denominator is the same as in the orthogonal case. The Connectedness Table is shown in Table 3.
| BK | JPM | GS | C | MS | BNS | AIG | TD | HSBC | DB | BAC | CS | WFC | From | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| BK | 10.62 | 7.42 | 6.61 | 12.10 | 12.58 | 3.14 | 10.46 | 2.93 | 2.85 | 6.96 | 10.28 | 5.92 | 8.13 | 89.38 |
| JPM | 7.29 | 9.50 | 6.72 | 12.97 | 12.18 | 3.17 | 9.64 | 2.94 | 3.00 | 6.89 | 11.11 | 5.75 | 8.84 | 90.50 |
| GS | 7.17 | 7.38 | 10.58 | 11.32 | 14.41 | 3.16 | 9.18 | 2.90 | 3.06 | 7.73 | 9.43 | 6.57 | 7.13 | 89.42 |
| C | 6.44 | 7.09 | 5.87 | 17.90 | 11.19 | 2.92 | 10.67 | 2.78 | 2.90 | 6.47 | 11.96 | 5.52 | 8.29 | 82.10 |
| MS | 7.27 | 7.18 | 7.63 | 11.69 | 16.81 | 2.99 | 9.84 | 2.73 | 2.93 | 7.39 | 10.06 | 6.31 | 7.18 | 83.19 |
| BNS | 6.91 | 6.93 | 6.25 | 11.63 | 11.37 | 7.02 | 9.28 | 5.05 | 3.36 | 7.88 | 9.63 | 7.00 | 7.70 | 92.98 |
| AIG | 5.85 | 5.95 | 4.62 | 12.24 | 9.81 | 2.81 | 25.34 | 2.56 | 2.83 | 5.50 | 10.08 | 4.67 | 7.73 | 74.66 |
| TD | 6.90 | 7.08 | 6.27 | 12.17 | 11.44 | 5.32 | 9.33 | 5.95 | 3.23 | 7.97 | 9.74 | 6.73 | 7.87 | 94.05 |
| HSBC | 6.39 | 6.55 | 6.07 | 12.21 | 11.00 | 3.59 | 9.23 | 3.08 | 7.24 | 9.47 | 10.02 | 7.93 | 7.22 | 92.76 |
| DB | 6.40 | 6.35 | 6.15 | 10.48 | 11.16 | 3.42 | 8.13 | 3.12 | 4.04 | 15.52 | 9.64 | 9.26 | 6.35 | 84.48 |
| BAC | 6.66 | 7.33 | 5.61 | 14.31 | 10.85 | 2.95 | 9.99 | 2.75 | 2.99 | 6.87 | 15.41 | 5.52 | 8.76 | 84.59 |
| CS | 6.47 | 6.37 | 6.35 | 10.93 | 11.52 | 3.42 | 8.54 | 3.07 | 3.79 | 10.82 | 9.48 | 12.70 | 6.55 | 87.30 |
| WFC | 7.29 | 7.61 | 5.73 | 13.81 | 10.91 | 3.07 | 10.07 | 2.93 | 3.01 | 6.19 | 12.07 | 5.22 | 12.08 | 87.92 |
| To | 81.04 | 83.24 | 73.89 | 145.84 | 138.41 | 39.96 | 114.36 | 36.84 | 37.98 | 90.14 | 123.50 | 76.40 | 91.74 | 87.18 |
The interpretation of connectedness is slightly different from spillovers. The calculation of net pairwise connectedness is the same as spillovers. In addition, DY 2011 demonstrates why the connectedness table can be interpreted as a network in which each entry represents a weight of connections between the nodes. The degree of each node is measured as the column/row sums off-diagonal terms. The row sums are the from-degrees, column sums are to-degrees of each node. The total connectedness (bottom-right entry in the table) is the average degree of the network (to or from since the row sums equal column sums). Since generalized FEVD matrices do not have row/columns sums equal to one, the entries in the connectedness table have been normalized to that end.
DY 2011 demonstrates that net pairwise FEVD (transpose of the connectedness table less the connectedness table) defines a weighted, directed network. Thus the connectedness table can be transformed into a net pairwise connectedness network as shown Figure (2) (To see how the network evolves over time, use the app here and click on the Network Visual tab.). Each edge shown in the graph represents a node giving volatility to another node. The connections with strength in the top tenth percentile are shown in the graph below. The four largest contributors in the network are Citibank, AIG, Morgan Stanley, and Bank of America. Citibank had been the largest bank by assets and was largely exposed to derivatives marekt, as was AIG to the extent that it went bankrupt and had to taken over by the US Treasury, Bank of America bought brankrupt Merrill Lynch, and Morgan Stanley had a similar business model to Merrill Lynch and was likely expected to go bankrupt as well during the crisis.
Figure 2: Full Sample, Net Pairwise Connectedness
To apply the methods shown above to a rolling window, it is necessary to only define the size of the rolling window, the size of the increment to move the window, the order \(q\) of \(MA(q)\), and the number lags in the VAR/SVAR, then loop over the data set. The function vector_autoreg in the code snippet takes in the an .xts data frame, window size, increment, AR lag order, and MA lag order. The example below uses a window of 100 days, incrementing 10 days, with \(p = 3\) and \(q = 10\). It also calls on function “make.table” (to make the spillover and connectedness table) and “normalize” (to normalize the connectedness table) which can be found in the function script on the GitHub page.
############## CONNECTEDNESS ################
vol.conn.index.out <- rolling_var[["conn"]]
### unlist output data
vol.conn.index.df <- data.frame(unlist(vol.conn.index.out))
colnames(vol.conn.index.df) = c("Index")
### .xts object
vol.conn.index.df$Date <- as.POSIXct(rownames(vol.conn.index.df),format="%Y-%m-%d")
vol.conn.index.xts <- xts(vol.conn.index.df[,-2],order.by=vol.conn.index.df[,2])
############## SPILLOVER ###################
fevd.list.out <- rolling_var[["spill"]]
fevd.df <- data.frame(unlist(fevd.list.out))
colnames(fevd.df) = c("Index")
fevd.df$Date <- as.POSIXct(rownames(fevd.df),format="%Y-%m-%d")
fevd.xts <- xts(fevd.df[,-2],order.by=fevd.df[,2])
### compare the two index measures
indice = merge.all(vol.conn.index.xts,fevd.xts)
colnames(indice) = c("Connectedness","Spillover")
chart.TimeSeries(indice,lwd=2,auto.grid=F,ylab="Index",xlab="Time",
main="Comparing Spillover Index levels",lty=1,
legend.loc="topright")
Figure 3: Spillover(Orthogonal) and Connectedness(Non-Orthogonal) Indexes
Figure (3) shows the percentage of the 10-day ahead forecast error which can be explained by spillovers/connectedness. Mathematically, for each matrix calculated at each point in time the sum of all the off-diagonal entries are divided by the sum of the entire matrix. Surpisingly, the indexes are very correlated, hence the use of orthogonal shocks yields a similar amount of forecast error as correlated shocks in this instance.
knitr::include_graphics("C:\\Users\\Rhamey\\Desktop\\financeR\\ConnectedNess\\financial_crisis_20008_10_01.png")
Figure 4: Financial Interconnectedness: 2008-10-01
knitr::include_graphics("C:\\Users\\Rhamey\\Desktop\\financeR\\ConnectedNess\\coronavirus_03_09_2020.png")
Figure 5: Financial Interconnectedness: 2020-03-09
Using the rolling window it is possible to see the network at various points in time. Figure (4) shows the network formation at the beginning of the 2008 financial crisis. AIG’s central role in underwriting mortgage derivatives to large banks stands out. Figure (5) shows a similar dynamic with subtle changes. AIG’s strong connection to Credit Suisse (CS) can be explianed by recent issues of mortgage backed securities from AIG using CS’s underwriting platform. Moreover, AIG’s exposure to insurance claims in the wake of the coronoavirus likely adds risk to other future liabilities.
Using the time-series of connectedness and spillover tables it is also possible to see how much volatility each stock gave or received, and which ones were net givers/receivers of volatility over time as in Figures (6) - (8). As with the indexes, the red lines are the spillover measures and the black lines are connectedness.
These results show the consequences of the assumptions behind the SVAR and VAR models. During the run up to the 2008 financial crisis and in its wake, AIG underwrote several option contracts for various asset-backed securities with the large banks and its volatility “giving” (the black line) spikes during that time period. However, the SVAR (red line) does not pick up on that connection, unlike the “non-orthogonal” VAR, and instead estimates a lower net volatility spillover of AIG stock price to the other volatilities. A likely reason is that SVARs are sensitive to the ordering of variables used to satisfy the normality assumption, and, perhaps, a different ordering would deliver a different result.
net.df.list <- rolling_var[["net_df"]]
spill.df.list <- rolling_var[["spillover_table"]]
net.cols <- names(net.df.list)
to.selected.net.df.list <- llply(net.df.list,function(df){df[["To"]]})
from.selected.net.df.list <- llply(net.df.list,function(df){df[["From"]]})
net.selected.net.df.list <- llply(net.df.list,function(df){df[["Net"]]})
to.selected.net.df <- as.data.frame(do.call(rbind,lapply(to.selected.net.df.list, function(x) t(data.frame(x)))))
from.selected.net.df <- as.data.frame(do.call(rbind,lapply(from.selected.net.df.list, function(x) t(data.frame(x)))))
net.selected.net.df <- as.data.frame(do.call(rbind,lapply(net.selected.net.df.list, function(x) t(data.frame(x)))))
to.selected.net.df <- to.selected.net.df %>% mutate(Date = as.POSIXct(net.cols,format="%Y-%m-%d"))
from.selected.net.df <- from.selected.net.df %>% mutate(Date = as.POSIXct(net.cols,format="%Y-%m-%d"))
net.selected.net.df <- net.selected.net.df %>% mutate(Date = as.POSIXct(net.cols,format="%Y-%m-%d"))
spill.cols <- names(spill.df.list)
to.selected.spill.df.list <- llply(spill.df.list,function(df){df[["To"]]})
from.selected.spill.df.list <- llply(spill.df.list,function(df){df[["From"]]})
net.selected.spill.df.list <- llply(spill.df.list,function(df){df[["Net"]]})
to.selected.spill.df <- as.data.frame(do.call(rbind,lapply(to.selected.spill.df.list, function(x) t(data.frame(x)))))
from.selected.spill.df <- as.data.frame(do.call(rbind,lapply(from.selected.spill.df.list, function(x) t(data.frame(x)))))
net.selected.spill.df <- as.data.frame(do.call(rbind,lapply(net.selected.spill.df.list, function(x) t(data.frame(x)))))
to.selected.spill.df <- to.selected.spill.df %>% mutate(Date = as.POSIXct(spill.cols,format="%Y-%m-%d"))
from.selected.spill.df <- from.selected.spill.df %>% mutate(Date = as.POSIXct(spill.cols,format="%Y-%m-%d"))
net.selected.spill.df <- net.selected.spill.df %>% mutate(Date = as.POSIXct(spill.cols,format="%Y-%m-%d"))
to.net.melt <- melt(to.selected.net.df,id="Date",value.name = "Connectedness")
to.spill.melt <- melt(to.selected.spill.df,id="Date",value.name = "Spillover")
to.tbl <- merge(to.net.melt,to.spill.melt,by=c("Date","variable"))
from.net.melt <- melt(from.selected.net.df,id="Date",value.name = "Connectedness")
from.spill.melt <- melt(from.selected.spill.df,id="Date",value.name = "Spillover")
from.tbl <- merge(from.net.melt,from.spill.melt,by=c("Date","variable"))
net.net.melt <- melt(net.selected.net.df,id="Date",value.name = "Connectedness")
net.spill.melt <- melt(net.selected.spill.df,id="Date",value.name = "Spillover")
net.tbl <- merge(net.net.melt,net.spill.melt,by=c("Date","variable"))
ggplot(to.tbl) +
geom_line(aes(x=Date,y=Connectedness),colour="black") +
geom_line(aes(x=Date,y=Spillover),colour="red") +
facet_wrap(~variable,scales="free_y") +
theme(legend.position = "bottom")
Figure 6: Volatility Giving
ggplot(from.tbl) +
geom_line(aes(x=Date,y=Connectedness),colour="black") +
geom_line(aes(x=Date,y=Spillover),colour="red") +
facet_wrap(~variable,scales="free_y") +
theme(legend.position = "bottom")
Figure 7: Volatility Receiving
ggplot(net.tbl) +
geom_line(aes(x=Date,y=Connectedness),colour="black") +
geom_line(aes(x=Date,y=Spillover),colour="red") +
facet_wrap(~variable,scales="free_y") +
theme(legend.position = "bottom")
Figure 8: Net Volatility Connectedness
Using the connectedness measure, it is also possible to compare two stocks and see which one was the net giver or receiver. For instance Figure (9) shows that Morgan Stanley “gave” volatility to Goldman Sachs for most of the time considered.
sender <- "MS"
receiver <- "GS"
net_stuff <- rolling_var[["net_df"]]
tables <- llply(net_stuff,function(stuff) stuff[["table"]])
pairwise_data <- ldply(tables,function(table){
sender_index <- which(colnames(table) == sender)
receiver_index <- which(colnames(table) == receiver)
sender_shock <- table[receiver_index,sender_index]
receiver_shock <- table[sender_index,receiver_index]
net_shock <- sender_shock - receiver_shock
})
colnames(pairwise_data) <- c("Time","Connectedness")
pairwise_data <- pairwise_data %>% mutate(Time = as.POSIXct(Time,format="%Y-%m-%d"))
ggplot(pairwise_data,aes(x=Time,y=Connectedness,group=1)) +
geom_line() +
geom_hline(yintercept=0,color="red",linetype="dashed")+
ggtitle(paste(sender,"to", receiver)) +
xlab("Time") +
ylab("Connectedness")
Figure 9: Pairwise Connectedness between Morgan Stanley(MS) and Goldman Sachs(GS)
Research from Niepmann & Schmidt-Eisenlohr shows that when the US dollar appreciates business loan terms tighten. The main reason for this is that banks often package and sell their business loans in capital markets to Collateralized Loan Obligations (CLO), insurance companies, mutual funds, hedge funds, and sometimes other banks, which typically have exposure to foreign exchange markets. Hence, when the dollar appreciates these entities will buy fewer business loans, and this lack of demand causes banks to tighten loan terms so as to offer better returns in the capital markets. According to Niepmann & Schmidt-Eisenlohr, this phenomenon is strongest among emerging market currencies. This development is significant for several reasons, but perhaps the most salient point is that according to economic theory currency appreciation is associated with lower asset reterns (i.e. lower interest rates), as Figure (10) from Krugman, Obstfeld, and Melitz demonstrates:
knitr::include_graphics("C:\\Users\\Rhamey\\Desktop\\financeR\\ConnectedNess\\krugman_obstfeld.png")
Figure 10: Exchange Rates, Interest Rates, and Money Supply
Using the methodology above and few proxies for relevant financial assets (CLOs, exchange rates, money markets, and oil) we can get an idea of how connected loan demand and foreign exchange are. According to theory when interest rates fall the dollar will appreciate and lower returns, hence a proxy investment for dollar exchange rate, relative to EMs and developed markets, and money market returns, to measure money demand, are necessary. Also, a market price index for CLOs will help estimate returns for buyers and sellers of this asset relative to foreign exchange rates. The price of oil has been found to be an important factor driving exchange rates, thus the price of WTI could also be helpful. Finally, Citibank, JP Morgan, and Wells Fargo are among the largest sellers (buyer in Citibank’s case) as discussed in a note from S&P.
Luckily there is an ETF for everything as Table 4 shows.
| Ticker | Description |
|---|---|
| BSCK | Money Market Proxy (ETF) |
| VCSH | Money Market Proxy (ETF) |
| USO | Light, sweet crude (ETF) |
| LEMB | Emerging Market Currencies (excl. China) (ETF) |
| FXCH | Chinese/USD Yuan (ETF) |
| UUP | USD Index relative to developed economies (ETF) |
| FFRHX | Fidelity Floating Rate High Income Fund (Closed-End Fund) |
| OXLC | Oxford Lane Corporation (Closed End Fund) |
| C | Citibank |
| WFC | Wells Fargo |
| JPM | JP Morgan Chase & Co. |
Using the same inputs for the rolling window size, window increment, AR lag, MA order, spillovers and connectedness are calculated over the time period spanning April 2, 2012 to March 13, 2020. Charts summarisng the (log) price, cumulative returns, annualized daily volatility, and daily returns are shown in Figures 11 - 14
## [1] "BSCK" "C" "FFRHX" "FXCH" "JPM" "LEMB" "OXLC" "USO" "UUP"
## [10] "VCSH" "WFC"
In Figure (11), the black line in the graph that falls precipitously is USO (oil), the other black line is UUP (USD exhange rate with other developed economies), and the green line at the bottom is Oxford Lane Capital, a purchaser of CLO securities. Of course the dollar and oil are negatively correlated, but it should be surprising that the prices of oil and OXLC move together. Moreover, it appears that as the dollar appreciates against the yen (FXHC, top blue line) and other emerging markets (LEMB, pink line) OXLC also falls in price.
chart.TimeSeries(all_data[["log_price"]],lwd=2,auto.grid=F,ylab="Log Prices",xlab="Time",
main="Log Prices",lty=1,
legend.loc="topright")
Figure 11: Log Prices
chart.TimeSeries(all_data[["cum_returns"]],lwd=2,auto.grid=F,ylab="Cumulative Returns",xlab="Time",
main="Returns (%)",lty=1,
legend.loc="topright")
Figure 12: Cumulative Returns
chart.TimeSeries(all_data[["return_vol"]],lwd=2,auto.grid=F,ylab="Annualized Log Volatility",xlab="Time",
main="Log Volatility",lty=1,
legend.loc="topright")
Figure 13: Annualized Volatility
chart.TimeSeries(all_data[["returns"]],lwd=2,auto.grid=F,ylab="Return (%)",xlab="Time",
main="Daily Return Percentage",lty=1,
legend.loc="topright")
Figure 14: Daily Returns
vol_var = VAR(vol.data,p=3,type="none")
amat <- diag(ncol(vol.data))
amat[lower.tri(amat)] <- NA
### extract residuals of the VAR
res_t <- residuals(vol_var)
MA_lag <- 10
theta_temp <- Phi(vol_var,nstep = MA_lag)
### extract MA coefficients
theta.list <- alply(theta_temp,3)
dir.mat <- directional.matrix(res_t,theta.list)
rownames(dir.mat) <- colnames(vol.data)
colnames(dir.mat) <- rownames(dir.mat)
D <- normalize(dir.mat)*100
df.list <- make.table(D,rownames(D),start_date)
df.list <- round(df.list[["table"]],2)
kable(df.list,caption = "Monetary Transmission Connectedness") %>% kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"))
| UUP | JPM | OXLC | FXCH | BSCK | LEMB | C | VCSH | USO | FFRHX | WFC | From | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| UUP | 3.01 | 14.11 | 13.89 | 0.12 | 0.05 | 1.85 | 17.78 | 0.23 | 34.53 | 0.91 | 13.51 | 96.99 |
| JPM | 0.51 | 19.64 | 10.25 | 0.07 | 0.02 | 0.83 | 26.13 | 0.06 | 25.29 | 0.30 | 16.90 | 80.36 |
| OXLC | 0.84 | 16.38 | 16.13 | 0.17 | 0.06 | 1.38 | 22.92 | 0.19 | 28.96 | 0.69 | 12.28 | 83.87 |
| FXCH | 1.30 | 15.70 | 11.71 | 0.45 | 0.13 | 2.24 | 21.00 | 0.46 | 35.23 | 1.40 | 10.39 | 99.55 |
| BSCK | 0.96 | 18.41 | 10.26 | 0.20 | 0.07 | 1.25 | 23.01 | 0.25 | 30.99 | 0.89 | 13.72 | 99.93 |
| LEMB | 0.90 | 18.20 | 10.49 | 0.19 | 0.06 | 1.54 | 23.36 | 0.24 | 30.57 | 0.85 | 13.60 | 98.46 |
| C | 0.79 | 17.63 | 10.77 | 0.15 | 0.06 | 1.27 | 24.67 | 0.18 | 30.00 | 0.68 | 13.80 | 75.33 |
| VCSH | 0.93 | 18.82 | 10.11 | 0.20 | 0.06 | 1.12 | 23.42 | 0.24 | 30.06 | 0.84 | 14.20 | 99.76 |
| USO | 0.99 | 15.21 | 11.55 | 0.15 | 0.09 | 1.71 | 21.22 | 0.29 | 36.91 | 0.95 | 10.94 | 63.09 |
| FFRHX | 1.00 | 20.57 | 9.64 | 0.14 | 0.04 | 0.71 | 24.03 | 0.17 | 25.27 | 0.69 | 17.74 | 99.31 |
| WFC | 0.68 | 17.89 | 10.33 | 0.12 | 0.04 | 1.01 | 24.25 | 0.13 | 28.56 | 0.52 | 16.46 | 83.54 |
| To | 8.88 | 172.91 | 108.99 | 1.51 | 0.63 | 13.38 | 227.14 | 2.19 | 299.47 | 8.04 | 137.07 | 89.11 |
net.mat = t(D) - D
net.melt1 <- melt(net.mat)
net.melt <- net.melt1 %>% filter(value != 0 )
colnames(net.melt)[3] <- "weight"
### calculate each percentile of the net pairwise connectedness values
### and choose only the top 10%
net.quant <- quantile(net.melt$weight,prob=seq(0,1,by=0.01))
new.net1 <- net.melt[net.melt$weight >= net.quant[90],]
new.net <- new.net1[new.net1[,1] != new.net1[,2],]
### create igraph graph
net.network <- graph.data.frame(new.net,direct=T)
#net.network <- set_edge_attr(net.network, "weight", value= new.net$weight)
E(net.network)$weight <- as.numeric(new.net$weight)
### set graph nodes
#V(net.network)
### set edge colors
E(net.network)$color <- ifelse(E(net.network)$weight >= net.quant[99],"black",
ifelse(E(net.network)$weight < net.quant[99] & E(net.network)$weight >= net.quant[95],"red",
ifelse(E(net.network)$weight < net.quant[95] & E(net.network)$weight >= net.quant[90],"orange","blue")))
### set node size
V(net.network)$size <- degree(net.network)/.5
plot(net.network,layout=layout.circle(net.network),
main="Firm Connectedness \n (intra day return volatility)",
xlab = "black: 1st percentile \n red: 5th percentile \n orange: 10th percentile \n node size: number of edeges connected to the node")
Figure 15: Monetary Transmission Network
Figure (15) shows the connections in the top tenth percentile. Table 5 shows that the price of oil is the main driver of this system, followed by Citibank, JPM, and OXLC. Overall, the network illustrates the expected behavior of the systeme based on what we know about its players and some interesting nuances. While Citibank and OXLC purchase CLOs from the likes of JPM it is the volatility of C, OXLC, and USO (oil) that appear to drive the volatilities of exchange rates and money markets as Niepmann & Schmidt-Eisenlohr described. Along with oil prices we could add CLO demand to the list of drivers of exchange rates and money market returns.
knitr::include_graphics("C:\\Users\\Rhamey\\Desktop\\financeR\\ConnectedNess\\oil_clo_fx_12_2014.png")
Figure 16: Oil Sell Off: 2014-12-10
knitr::include_graphics("C:\\Users\\Rhamey\\Desktop\\financeR\\ConnectedNess\\oil_clo_fx_03_05_2020.png")
Figure 17: Coronavirus Selloff: 2020-03-05
The relationships shown in Figure (15) are also evident in Figure (16) which shows the effects oil selloff from 2014 year end. OXLC is driving volatilities in money markets and USD/RMB exchange rates and USO volatility is spilling over into everything else. Figure (16) shows the same network just as the market selloff from coronavirus was starting. Somewhat surprisingly Citibank is primary driver of volatility spillovers. USO and OXLC only effect one other node each - money markets and USD/RMB exhange rate. Citibank is a large bank and likely has large exposures across all asset classes. However, in the context of this network it is the largest purchaser of CLOs which have a higher likelihood of default in the wake of a pandemic, and neither JPM nor WFC, large banks themselves, large sources of volatility spillovers. Perhaps, this analysis is saying something about the percieved risk in Citibank’s balance sheet in the current crisis relative to other banks, a claim which is also finds support in Figure (5).
Financial stocks are highly correlated, but it should come as a surprise that these variables are as connected as the index in Figure (18) shows. However, the Connectedness Index is also very volatile here, falling from just over 80% to under 70% between 2015 - 2017, before jumping up above 80 again.
############## CONNECTEDNESS ################
vol.conn.index.out <- monetary_transmission[["conn"]]
### unlist output data
vol.conn.index.df <- data.frame(unlist(vol.conn.index.out))
colnames(vol.conn.index.df) = c("Index")
### .xts object
vol.conn.index.df$Date <- as.POSIXct(rownames(vol.conn.index.df),format="%Y-%m-%d")
vol.conn.index.xts <- xts(vol.conn.index.df[,-2],order.by=vol.conn.index.df[,2])
############## SPILLOVER ###################
fevd.list.out <- monetary_transmission[["spill"]]
fevd.df <- data.frame(unlist(fevd.list.out))
colnames(fevd.df) = c("Index")
fevd.df$Date <- as.POSIXct(rownames(fevd.df),format="%Y-%m-%d")
fevd.xts <- xts(fevd.df[,-2],order.by=fevd.df[,2])
### compare the two index measures
indice = merge.all(vol.conn.index.xts,fevd.xts)
colnames(indice) = c("Connectedness","Spillover")
chart.TimeSeries(indice,lwd=2,auto.grid=F,ylab="Index",xlab="Time",
main="Comparing Spillover Index levels",lty=1,
legend.loc="topright")
Figure 18: Monetary Transmission Connectedness Index
Figure (19) of the net volatility spillovers shows why even impsoing independence on correlated variables is not always feasible. The prices of VCSH, FFRHX, and BSCK show hardly any signs of life, but the FEVD from the orthogonal SVAR of these ETFs is high and volatile. In almost every other instance, the SVAR assigns an asset as giver (receiver) of volatility when the non-orthogonal VAR assigns the same asset as a receiver (giver). After considering these graphs, it is all the more surprising that both the Connectedness and Spillover indexes track each other as well as they do. Overall, using a non-orthogonal VAR does not necessarily reduce the total amount of error in a forecast but it does capture the key drivers of forecast errors.
net.df.list <- monetary_transmission[["net_df"]]
spill.df.list <- monetary_transmission[["spillover_table"]]
net.cols <- names(net.df.list)
net.selected.net.df.list <- llply(net.df.list,function(df){df[["Net"]]})
net.selected.net.df <- as.data.frame(do.call(rbind,lapply(net.selected.net.df.list, function(x) t(data.frame(x)))))
net.selected.net.df <- net.selected.net.df %>% mutate(Date = as.POSIXct(net.cols,format="%Y-%m-%d"))
spill.cols <- names(spill.df.list)
net.selected.spill.df.list <- llply(spill.df.list,function(df){df[["Net"]]})
net.selected.spill.df <- as.data.frame(do.call(rbind,lapply(net.selected.spill.df.list, function(x) t(data.frame(x)))))
net.selected.spill.df <- net.selected.spill.df %>% mutate(Date = as.POSIXct(spill.cols,format="%Y-%m-%d"))
net.net.melt <- melt(net.selected.net.df,id="Date",value.name = "Connectedness")
net.spill.melt <- melt(net.selected.spill.df,id="Date",value.name = "Spillover")
net.tbl <- merge(net.net.melt,net.spill.melt,by=c("Date","variable"))
ggplot(net.tbl) +
geom_line(aes(x=Date,y=Connectedness),colour="black") +
geom_line(aes(x=Date,y=Spillover),colour="red") +
facet_wrap(~variable,scales="free_y") +
theme(legend.position = "bottom")
Figure 19: Monetary Transmission Connectedness